diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c87a305 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +firmware/configurable_firmata/* linguist-vendored=true +esp32_firmata/* linguist-vendored=true \ No newline at end of file diff --git a/firmware/configurable_firmata/examples/ConfigurableFirmata/ConfigurableFirmata.ino b/firmware/configurable_firmata/examples/ConfigurableFirmata/ConfigurableFirmata.ino new file mode 100644 index 0000000..e9eb126 --- /dev/null +++ b/firmware/configurable_firmata/examples/ConfigurableFirmata/ConfigurableFirmata.ino @@ -0,0 +1,441 @@ +/* + Firmata is a generic protocol for communicating with microcontrollers + from software on a host computer. It is intended to work with + any host computer software package. + + To download a host software package, please clink on the following link + to open the download page in your default browser. + + https://github.com/firmata/ConfigurableFirmata#firmata-client-libraries + + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2014 Nicolas Panel. All rights reserved. + Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated: September 16th, 2017 +*/ + +/* + README + + This is an example use of ConfigurableFirmata. The easiest way to create a configuration is to + use http://firmatabuilder.com and select the communication transport and the firmata features + to include and an Arduino sketch (.ino) file will be generated and downloaded automatically. + + To manually configure a sketch, copy this file and follow the instructions in the + ETHERNET CONFIGURATION OPTION (if you want to use Ethernet instead of Serial/USB) and + FIRMATA FEATURE CONFIGURATION sections in this file. +*/ + +#include "ConfigurableFirmata.h" + +/*============================================================================== + * ETHERNET CONFIGURATION OPTION + * + * By default Firmata uses the Serial-port (over USB) of the Arduino. ConfigurableFirmata may also + * comunicate over ethernet using tcp/ip. To configure this sketch to use Ethernet instead of + * Serial, uncomment the approprate includes for your particular hardware. See STEPS 1 - 5 below. + * If you want to use Serial (over USB) then skip ahead to the FIRMATA FEATURE CONFIGURATION + * section further down in this file. + * + * If you enable Ethernet, you will need a Firmata client library with a network transport that can + * act as a server in order to establish a connection between ConfigurableFirmataEthernet and the + * Firmata host application (your application). + * + * To use ConfigurableFirmata with Ethernet you will need to have one of the following + * boards or shields: + * + * - Arduino Ethernet shield (or clone) + * - Arduino Ethernet board (or clone) + * - Arduino Yun + * + * If you are using an Arduino Ethernet shield you cannot use the following pins on + * the following boards. Firmata will ignore any requests to use these pins: + * + * - Arduino Uno or other ATMega328 boards: (D4, D10, D11, D12, D13) + * - Arduino Mega: (D4, D10, D50, D51, D52, D53) + * - Arduino Leonardo: (D4, D10) + * - Arduino Due: (D4, D10) + * - Arduino Zero: (D4, D10) + * + * If you are using an ArduinoEthernet board, the following pins cannot be used (same as Uno): + * - D4, D10, D11, D12, D13 + *============================================================================*/ + +// STEP 1 [REQUIRED] +// Uncomment / comment the appropriate set of includes for your hardware (OPTION A, B or C) + +/* + * OPTION A: Configure for Arduino Ethernet board or Arduino Ethernet shield (or clone) + * + * To configure ConfigurableFirmata to use the an Arduino Ethernet Shield or Arduino Ethernet + * Board (both use the same WIZ5100-based Ethernet controller), uncomment the SPI and Ethernet + * includes below. + */ +//#include +//#include + + +/* + * OPTION B: Configure for a board or shield using an ENC28J60-based Ethernet controller, + * uncomment out the UIPEthernet include below. + * + * The UIPEthernet-library can be downloaded + * from: https://github.com/ntruchsess/arduino_uip + */ +//#include + + +/* + * OPTION C: Configure for Arduino Yun + * + * The Ethernet port on the Arduino Yun board can be used with Firmata in this configuration. + * To execute StandardFirmataEthernet on Yun uncomment the Bridge and YunClient includes below. + * + * NOTE: in order to compile for the Yun you will also need to comment out some of the includes + * and declarations in the FIRMATA FEATURE CONFIGURATION section later in this file. Including all + * features exceeds the RAM and Flash memory of the Yun. Comment out anything you don't need. + * + * On Yun there's no need to configure local_ip and mac address as this is automatically + * configured on the linux-side of Yun. + * + * Establishing a connection with the Yun may take several seconds. + */ +//#include +//#include + +#if defined ethernet_h || defined UIPETHERNET_H || defined _YUN_CLIENT_H_ +#define NETWORK_FIRMATA + +// STEP 2 [REQUIRED for all boards and shields] +// replace with IP of the server you want to connect to, comment out if using 'remote_host' +#define remote_ip IPAddress(192, 168, 0, 1) +// OR replace with hostname of server you want to connect to, comment out if using 'remote_ip' +// #define remote_host "server.local" + +// STEP 3 [REQUIRED unless using Arduino Yun] +// Replace with the port that your server is listening on +#define remote_port 3030 + +// STEP 4 [REQUIRED unless using Arduino Yun OR if not using DHCP] +// Replace with your board or Ethernet shield's IP address +// Comment out if you want to use DHCP +#define local_ip IPAddress(192, 168, 0, 6) + +// STEP 5 [REQUIRED unless using Arduino Yun] +// replace with Ethernet shield mac. Must be unique for your network +const byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0x07, 0x02}; +#endif + +/*============================================================================== + * FIRMATA FEATURE CONFIGURATION + * + * Comment out the include and declaration for any features that you do not need + * below. + * + * WARNING: Including all of the following features (especially if also using + * Ethernet) may exceed the Flash and/or RAM of lower memory boards such as the + * Arduino Uno or Leonardo. + *============================================================================*/ + +#include +DigitalInputFirmata digitalInput; + +#include +DigitalOutputFirmata digitalOutput; + +#include +AnalogInputFirmata analogInput; + +#include +AnalogOutputFirmata analogOutput; + +#include +#include +ServoFirmata servo; +// ServoFirmata depends on AnalogOutputFirmata +#if defined ServoFirmata_h && ! defined AnalogOutputFirmata_h +#error AnalogOutputFirmata must be included to use ServoFirmata +#endif + +#include +#include +I2CFirmata i2c; + +#include +OneWireFirmata oneWire; + +// StepperFirmata is deprecated as of ConfigurableFirmata v2.10.0. Please update your +// client implementation to use the new, more full featured and scalable AccelStepperFirmata. +#include +StepperFirmata stepper; + +#include +AccelStepperFirmata accelStepper; + +#include +SerialFirmata serial; + +#include +FirmataExt firmataExt; + +#include +FirmataScheduler scheduler; + +// To add Encoder support you must first install the FirmataEncoder and Encoder libraries: +// https://github.com/firmata/FirmataEncoder +// https://www.pjrc.com/teensy/td_libs_Encoder.html +// #include +// #include +// FirmataEncoder encoder; + +/*=================================================================================== + * END FEATURE CONFIGURATION - you should not need to change anything below this line + *==================================================================================*/ + +// dependencies. Do not comment out the following lines +#if defined AnalogOutputFirmata_h || defined ServoFirmata_h +#include +#endif + +#if defined AnalogInputFirmata_h || defined I2CFirmata_h || defined FirmataEncoder_h +#include +FirmataReporting reporting; +#endif + +// dependencies for Network Firmata. Do not comment out. +#ifdef NETWORK_FIRMATA +#if defined remote_ip && defined remote_host +#error "cannot define both remote_ip and remote_host at the same time!" +#endif +#include +#ifdef _YUN_CLIENT_H_ +YunClient client; +#else +EthernetClient client; +#endif +#if defined remote_ip && !defined remote_host +#ifdef local_ip +EthernetClientStream stream(client, local_ip, remote_ip, NULL, remote_port); +#else +EthernetClientStream stream(client, IPAddress(0, 0, 0, 0), remote_ip, NULL, remote_port); +#endif +#endif +#if !defined remote_ip && defined remote_host +#ifdef local_ip +EthernetClientStream stream(client, local_ip, IPAddress(0, 0, 0, 0), remote_host, remote_port); +#else +EthernetClientStream stream(client, IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0), remote_host, remote_port); +#endif +#endif +#endif + +/*============================================================================== + * FUNCTIONS + *============================================================================*/ + +void systemResetCallback() +{ + // initialize a default state + + // pins with analog capability default to analog input + // otherwise, pins default to digital output + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_ANALOG(i)) { +#ifdef AnalogInputFirmata_h + // turns off pull-up, configures everything + Firmata.setPinMode(i, PIN_MODE_ANALOG); +#endif + } else if (IS_PIN_DIGITAL(i)) { +#ifdef DigitalOutputFirmata_h + // sets the output to 0, configures portConfigInputs + Firmata.setPinMode(i, OUTPUT); +#endif + } + } + +#ifdef FirmataExt_h + firmataExt.reset(); +#endif +} + +/*============================================================================== + * SETUP() + *============================================================================*/ + +void setup() +{ + /* + * ETHERNET SETUP + */ +#ifdef NETWORK_FIRMATA +#ifdef _YUN_CLIENT_H_ + Bridge.begin(); +#else +#ifdef local_ip + Ethernet.begin((uint8_t *)mac, local_ip); //start Ethernet +#else + Ethernet.begin((uint8_t *)mac); //start Ethernet using dhcp +#endif +#endif + delay(1000); +#endif + + /* + * FIRMATA SETUP + */ + Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + +#ifdef FirmataExt_h +#ifdef DigitalInputFirmata_h + firmataExt.addFeature(digitalInput); +#endif +#ifdef DigitalOutputFirmata_h + firmataExt.addFeature(digitalOutput); +#endif +#ifdef AnalogInputFirmata_h + firmataExt.addFeature(analogInput); +#endif +#ifdef AnalogOutputFirmata_h + firmataExt.addFeature(analogOutput); +#endif +#ifdef ServoFirmata_h + firmataExt.addFeature(servo); +#endif +#ifdef I2CFirmata_h + firmataExt.addFeature(i2c); +#endif +#ifdef OneWireFirmata_h + firmataExt.addFeature(oneWire); +#endif +#ifdef StepperFirmata_h + firmataExt.addFeature(stepper); +#endif +#ifdef AccelStepperFirmata_h + firmataExt.addFeature(accelStepper); +#endif +#ifdef SerialFirmata_h + firmataExt.addFeature(serial); +#endif +#ifdef FirmataReporting_h + firmataExt.addFeature(reporting); +#endif +#ifdef FirmataScheduler_h + firmataExt.addFeature(scheduler); +#endif +#ifdef FirmataEncoder_h + firmataExt.addFeature(encoder); +#endif +#endif + /* systemResetCallback is declared here (in ConfigurableFirmata.ino) */ + Firmata.attach(SYSTEM_RESET, systemResetCallback); + + // Network Firmata communicates with Ethernet-shields over SPI. Therefor all + // SPI-pins must be set to PIN_MODE_IGNORE. Otherwise Firmata would break SPI-communication. + // add Pin 10 and configure pin 53 as output if using a MEGA with Ethernetshield. + // No need to ignore pin 10 on MEGA with ENC28J60, as here pin 53 should be connected to SS: +#ifdef NETWORK_FIRMATA + + #ifndef _YUN_CLIENT_H_ + // ignore SPI and pin 4 that is SS for SD-Card on Ethernet-shield + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_SPI(i) + || 4 == i // SD Card on Ethernet shield uses pin 4 for SS + || 10 == i // Ethernet-shield uses pin 10 for SS + ) { + Firmata.setPinMode(i, PIN_MODE_IGNORE); + } + } + // pinMode(PIN_TO_DIGITAL(53), OUTPUT); configure hardware-SS as output on MEGA + pinMode(PIN_TO_DIGITAL(4), OUTPUT); // switch off SD-card bypassing Firmata + digitalWrite(PIN_TO_DIGITAL(4), HIGH); // SS is active low; + #endif + + #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + pinMode(PIN_TO_DIGITAL(53), OUTPUT); // configure hardware SS as output on MEGA + #endif + + // start up Network Firmata: + Firmata.begin(stream); +#else + // Uncomment to save a couple of seconds by disabling the startup blink sequence. + // Firmata.disableBlinkVersion(); + + // start up the default Firmata using Serial interface: + Firmata.begin(57600); +#endif + Firmata.parse(SYSTEM_RESET); // reset to default config +} + +/*============================================================================== + * LOOP() + *============================================================================*/ +void loop() +{ +#ifdef DigitalInputFirmata_h + /* DIGITALREAD - as fast as possible, check for changes and output them to the + * stream buffer using Firmata.write() */ + digitalInput.report(); +#endif + + /* STREAMREAD - processing incoming message as soon as possible, while still + * checking digital inputs. */ + while (Firmata.available()) { + Firmata.processInput(); +#ifdef FirmataScheduler_h + if (!Firmata.isParsingMessage()) { + goto runtasks; + } + } + if (!Firmata.isParsingMessage()) { +runtasks: scheduler.runTasks(); +#endif + } + + /* SEND STREAM WRITE BUFFER - TO DO: make sure that the stream buffer doesn't go over + * 60 bytes. use a timer to sending an event character every 4 ms to + * trigger the buffer to dump. */ + +#ifdef FirmataReporting_h + if (reporting.elapsed()) { +#ifdef AnalogInputFirmata_h + /* ANALOGREAD - do all analogReads() at the configured sampling interval */ + analogInput.report(); +#endif +#ifdef I2CFirmata_h + // report i2c data for all device with read continuous mode enabled + i2c.report(); +#endif +#ifdef FirmataEncoder_h + // report encoders positions if reporting enabled. + encoder.report(); +#endif + } +#endif +#ifdef StepperFirmata_h + stepper.update(); +#endif +#ifdef AccelStepperFirmata_h + accelStepper.update(); +#endif +#ifdef SerialFirmata_h + serial.update(); +#endif + +#if defined NETWORK_FIRMATA && !defined local_ip &&!defined _YUN_CLIENT_H_ + // only necessary when using DHCP, ensures local IP is updated appropriately if it changes + if (Ethernet.maintain()) { + stream.maintain(Ethernet.localIP()); + } +#endif +} diff --git a/firmware/configurable_firmata/examples/ConfigurableFirmataBLE/ConfigurableFirmataBLE.ino b/firmware/configurable_firmata/examples/ConfigurableFirmataBLE/ConfigurableFirmataBLE.ino new file mode 100644 index 0000000..4236336 --- /dev/null +++ b/firmware/configurable_firmata/examples/ConfigurableFirmataBLE/ConfigurableFirmataBLE.ino @@ -0,0 +1,382 @@ +/* + Firmata is a generic protocol for communicating with microcontrollers + from software on a host computer. It is intended to work with + any host computer software package. + + To download a host software package, please clink on the following link + to open the download page in your default browser. + + https://github.com/firmata/ConfigurableFirmata#firmata-client-libraries + + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2014 Nicolas Panel. All rights reserved. + Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated September 16th, 2017 +*/ + +/* + README + + This is an example use of ConfigurableFirmata with BLE. + + ConfigurableFirmataBLE enables the use of Firmata over a BLE connection. To configure your + connection, follow the instructions in the BLE CONFIGURATION section of this file. + + To use ConfigurableFirmataBLE you will need to have one of the following boards or shields: + - Arduino 101 (recommended) + - RedBear BLE Nano ** works with modifications ** + - RedBear BLE Shield v2 ** to be verfied ** + + If you are using an Arduino 101, make sure you have the Intel Curie Boards package v1.0.6 + or higer installed via the Arduino Boards Manager. +*/ + +#include "ConfigurableFirmata.h" + +// min cannot be < 0x0006. Adjust max if necessary +#define FIRMATA_BLE_MIN_INTERVAL 0x0006 // 7.5ms (7.5 / 1.25) +#define FIRMATA_BLE_MAX_INTERVAL 0x0018 // 30ms (30 / 1.25) + +/*============================================================================== + * BLE CONFIGURATION + * + * If you are using an Arduino 101, you do not need to make any changes to this + * section of the file unless you need a unique ble local name. If you are using + * another supported BLE board or shield, follow the instructions for the specific + * board or shield below. + *============================================================================*/ + +// change this to a unique name per board if running StandardFirmataBLE on multiple boards +// within the same physical space +#define FIRMATA_BLE_LOCAL_NAME "FIRMATA" + + +/* + * Arduino 101 + * + * Make sure you have the Intel Curie Boards package v1.0.6 or higher installed via the Arduino + * Boards Manager. + * + * Test script: https://gist.github.com/soundanalogous/927360b797574ed50e27 + */ +#ifdef _VARIANT_ARDUINO_101_X_ +#include +#include "utility/BLEStream.h" +BLEStream stream; +#endif + + +/* + * RedBearLab BLE Nano (with default switch settings) + * + * Blocked on this issue: https://github.com/RedBearLab/nRF51822-Arduino/pull/97 + * Works with modifications. See comments at top of the test script referenced below. + * When the RBL nRF51822-Arduino library issue is resolved, this should work witout + * any modifications. + * + * Test script: https://gist.github.com/soundanalogous/d39bb3eb36333a0906df + * + * Note: If you have changed the solder jumpers on the Nano you may encounter issues since + * the pins are currently mapped in Firmata only for the default (factory) jumper settings. + */ +#ifdef BLE_NANO +#include +#include "utility/BLEStream.h" +BLEStream stream; +#endif + +/* + * RedBearLab BLE Shield + * + * If you are using a RedBearLab BLE shield, uncomment the define below. + * Also, change the define for BLE_RST if you have the jumper set to pin 7 rather than pin 4. + * + * You will need to use the shield with an Arduino Zero, Due, Mega, or other board with sufficient + * Flash and RAM. Arduino Uno, Leonardo and other ATmega328p and Atmega32u4 boards do not have + * enough memory to run StandardFirmataBLE. + * + * TODO: verify if this works and with which boards it works. + * + * Test script: https://gist.github.com/soundanalogous/927360b797574ed50e27 + */ +//#define REDBEAR_BLE_SHIELD + +#ifdef REDBEAR_BLE_SHIELD +#include +#include +#include "utility/BLEStream.h" + +#define BLE_REQ 9 +#define BLE_RDY 8 +#define BLE_RST 4 // 4 or 7 via jumper on shield + +BLEStream stream(BLE_REQ, BLE_RDY, BLE_RST); +#endif + + +#if defined(BLE_REQ) && defined(BLE_RDY) && defined(BLE_RST) +#define IS_IGNORE_BLE_PINS(p) ((p) == BLE_REQ || (p) == BLE_RDY || (p) == BLE_RST) +#endif + + +/*============================================================================== + * FIRMATA FEATURE CONFIGURATION + * + * Comment out the include and declaration for any features that you do not need + * below. + * + * WARNING: Including all of the following features (especially if also using + * Ethernet) may exceed the Flash and/or RAM of lower memory boards such as the + * Arduino Uno or Leonardo. + *============================================================================*/ + +#include +DigitalInputFirmata digitalInput; + +#include +DigitalOutputFirmata digitalOutput; + +#include +AnalogInputFirmata analogInput; + +#include +AnalogOutputFirmata analogOutput; + +#include +#include +ServoFirmata servo; +// ServoFirmata depends on AnalogOutputFirmata +#if defined ServoFirmata_h && ! defined AnalogOutputFirmata_h +#error AnalogOutputFirmata must be included to use ServoFirmata +#endif + +#include +#include +I2CFirmata i2c; + +#include +OneWireFirmata oneWire; + +// StepperFirmata is deprecated as of ConfigurableFirmata v2.10.0. Please update your +// client implementation to use the new, more full featured and scalable AccelStepperFirmata. +#include +StepperFirmata stepper; + +#include +AccelStepperFirmata accelStepper; + +#include +SerialFirmata serial; + +#include +FirmataExt firmataExt; + +#include +FirmataScheduler scheduler; + +// To add Encoder support you must first install the FirmataEncoder and Encoder libraries: +// https://github.com/firmata/FirmataEncoder +// https://www.pjrc.com/teensy/td_libs_Encoder.html +// #include +// #include +// FirmataEncoder encoder; + +/*=================================================================================== + * END FEATURE CONFIGURATION - you should not need to change anything below this line + *==================================================================================*/ + +// dependencies. Do not comment out the following lines +#if defined AnalogOutputFirmata_h || defined ServoFirmata_h +#include +#endif + +#if defined AnalogInputFirmata_h || defined I2CFirmata_h || defined FirmataEncoder_h +#include +FirmataReporting reporting; +#endif + + +/*============================================================================== + * FUNCTIONS + *============================================================================*/ + +void systemResetCallback() +{ + // initialize a default state + + // pins with analog capability default to analog input + // otherwise, pins default to digital output + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_ANALOG(i)) { +#ifdef AnalogInputFirmata_h + // turns off pull-up, configures everything + Firmata.setPinMode(i, PIN_MODE_ANALOG); +#endif + } else if (IS_PIN_DIGITAL(i)) { +#ifdef DigitalOutputFirmata_h + // sets the output to 0, configures portConfigInputs + Firmata.setPinMode(i, OUTPUT); +#endif + } + } + +#ifdef FirmataExt_h + firmataExt.reset(); +#endif +} + +/*============================================================================== + * SETUP() + *============================================================================*/ + +void ignorePins() +{ +#ifdef BLE_REQ + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_IGNORE_BLE_PINS(i)) { + Firmata.setPinMode(i, PIN_MODE_IGNORE); + } + } +#endif +} + +void initTransport() +{ + stream.setLocalName(FIRMATA_BLE_LOCAL_NAME); + // set the BLE connection interval - this is the fastest interval you can read inputs + stream.setConnectionInterval(FIRMATA_BLE_MIN_INTERVAL, FIRMATA_BLE_MAX_INTERVAL); + // set how often the BLE TX buffer is flushed (if not full) + stream.setFlushInterval(FIRMATA_BLE_MAX_INTERVAL); + + stream.begin(); +} + +void initFirmata() +{ + Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + +#ifdef FirmataExt_h +#ifdef DigitalInputFirmata_h + firmataExt.addFeature(digitalInput); +#endif +#ifdef DigitalOutputFirmata_h + firmataExt.addFeature(digitalOutput); +#endif +#ifdef AnalogInputFirmata_h + firmataExt.addFeature(analogInput); +#endif +#ifdef AnalogOutputFirmata_h + firmataExt.addFeature(analogOutput); +#endif +#ifdef ServoFirmata_h + firmataExt.addFeature(servo); +#endif +#ifdef I2CFirmata_h + firmataExt.addFeature(i2c); +#endif +#ifdef OneWireFirmata_h + firmataExt.addFeature(oneWire); +#endif +#ifdef StepperFirmata_h + firmataExt.addFeature(stepper); +#endif +#ifdef AccelStepperFirmata_h +firmataExt.addFeature(accelStepper); +#endif +#ifdef SerialFirmata_h + firmataExt.addFeature(serial); +#endif +#ifdef FirmataReporting_h + firmataExt.addFeature(reporting); +#endif +#ifdef FirmataScheduler_h + firmataExt.addFeature(scheduler); +#endif +#ifdef FirmataEncoder_h + firmataExt.addFeature(encoder); +#endif +#endif + /* systemResetCallback is declared here (in ConfigurableFirmata.ino) */ + Firmata.attach(SYSTEM_RESET, systemResetCallback); + + ignorePins(); + + // Initialize Firmata to use the BLE stream object as the transport. + Firmata.begin(stream); + Firmata.parse(SYSTEM_RESET); // reset to default config +} + +void setup() +{ + initTransport(); + + initFirmata(); +} + +/*============================================================================== + * LOOP() + *============================================================================*/ +void loop() +{ + // do not process data if no BLE connection is established + // poll will send the TX buffer at the specified flush interval or when the buffer is full + if (!stream.poll()) return; + +#ifdef DigitalInputFirmata_h + /* DIGITALREAD - as fast as possible, check for changes and output them to the + * stream buffer using Firmata.write() */ + digitalInput.report(); +#endif + + /* STREAMREAD - processing incoming message as soon as possible, while still + * checking digital inputs. */ + while (Firmata.available()) { + Firmata.processInput(); +#ifdef FirmataScheduler_h + if (!Firmata.isParsingMessage()) { + goto runtasks; + } + } + if (!Firmata.isParsingMessage()) { +runtasks: scheduler.runTasks(); +#endif + } + +#ifdef FirmataReporting_h + if (reporting.elapsed()) { +#ifdef AnalogInputFirmata_h + /* ANALOGREAD - do all analogReads() at the configured sampling interval */ + analogInput.report(); +#endif +#ifdef I2CFirmata_h + // report i2c data for all device with read continuous mode enabled + i2c.report(); +#endif +#ifdef FirmataEncoder_h + // report encoders positions if reporting enabled. + encoder.report(); +#endif + } +#endif +#ifdef StepperFirmata_h + stepper.update(); +#endif +#ifdef AccelStepperFirmata_h +accelStepper.update(); +#endif +#ifdef SerialFirmata_h + serial.update(); +#endif + +} diff --git a/firmware/configurable_firmata/examples/ConfigurableFirmataWiFi/ConfigurableFirmataWiFi.ino b/firmware/configurable_firmata/examples/ConfigurableFirmataWiFi/ConfigurableFirmataWiFi.ino new file mode 100644 index 0000000..702558e --- /dev/null +++ b/firmware/configurable_firmata/examples/ConfigurableFirmataWiFi/ConfigurableFirmataWiFi.ino @@ -0,0 +1,690 @@ +/* + Firmata is a generic protocol for communicating with microcontrollers + from software on a host computer. It is intended to work with + any host computer software package. + + To download a host software package, please clink on the following link + to open the download page in your default browser. + + https://github.com/firmata/ConfigurableFirmata#firmata-client-libraries + + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2014 Nicolas Panel. All rights reserved. + Copyright (C) 2015-2016 Jesse Frush. All rights reserved. + Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. + Copyright (C) 2016 Jens B. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated: September 16th, 2017 +*/ + +/* + README + + This is an example use of ConfigurableFirmata with WiFi. + + ConfigurableFirmataWiFi enables the use of Firmata over a TCP connection. It can be configured as + either a TCP server or TCP client. To configure your Wi-Fi connection, follow the instructions in + the WIFI CONFIGURATION section of this file. + + To use ConfigurableFirmataWiFi you will need to have one of the following + boards or shields: + + - Arduino MKR1000 board (recommended) + - ESP8266 WiFi board compatible with ESP8266 Arduino core + - Arduino WiFi Shield 101 + - Arduino WiFi Shield (or clone) + + Follow the instructions in the wifiConfig.h file (wifiConfig.h tab in Arduino IDE) to + configure your particular hardware. + + Dependencies: + - WiFi Shield 101 requires version 0.7.0 or higher of the WiFi101 library (available in Arduino + 1.6.8 or higher, or update the library via the Arduino Library Manager or clone from source: + https://github.com/arduino-libraries/WiFi101) + - ESP8266 requires the Arduino ESP8266 core v2.1.0 or higher which can be obtained here: + https://github.com/esp8266/Arduino + + In order to use the WiFi Shield 101 with Firmata you will need a board with at least 35k of Flash + memory. This means you cannot use the WiFi Shield 101 with an Arduino Uno or any other + ATmega328p-based microcontroller or with an Arduino Leonardo or other ATmega32u4-based + microcontroller. Some boards that will work are: + + - Arduino Zero + - Arduino Due + - Arduino 101 + - Arduino Mega + + NOTE: If you are using an Arduino WiFi (legacy) shield you cannot use the following pins on + the following boards. Firmata will ignore any requests to use these pins: + + - Arduino Uno or other ATMega328 boards: (D4, D7, D10, D11, D12, D13) + - Arduino Mega: (D4, D7, D10, D50, D51, D52, D53) + - Arduino Due, Zero or Leonardo: (D4, D7, D10) + + If you are using an Arduino WiFi 101 shield you cannot use the following pins on the following + boards: + + - Arduino Due or Zero: (D5, D7, D10) + - Arduino Mega: (D5, D7, D10, D50, D52, D53) +*/ + +#include "ConfigurableFirmata.h" + +/* + * Uncomment the #define SERIAL_DEBUG line below to receive serial output messages relating to your + * connection that may help in the event of connection issues. If defined, some boards may not begin + * executing this sketch until the Serial console is opened. + */ +//#define SERIAL_DEBUG +#include "utility/firmataDebug.h" + +#define MAX_CONN_ATTEMPTS 20 // [500 ms] -> 10 s + +/*============================================================================== + * WIFI CONFIGURATION + * + * You must configure your particular hardware. Follow the steps below. + * + * By default, ConfigurableFirmataWiFi is configured as a TCP server, to configure + * as a TCP client, see STEP 2. + *============================================================================*/ + +// STEP 1 [REQUIRED] +// Uncomment / comment the appropriate set of includes for your hardware (OPTION A, B or C) +// Arduino MKR1000 or ESP8266 are enabled by default if compiling for either of those boards. + +/* + * OPTION A: Configure for Arduino MKR1000 or Arduino WiFi Shield 101 + * + * This will configure ConfigurableFirmataWiFi to use the WiFi101 library, which works with the + * Arduino WiFi101 shield and devices that have the WiFi101 chip built in (such as the MKR1000). + * It is compatible with 802.11 B/G/N networks. + * + * If you are using the MKR1000 board, continue on to STEP 2. If you are using the WiFi 101 shield, + * follow the instructions below. + * + * To enable for the WiFi 101 shield, uncomment the #define WIFI_101 below and verify the + * #define ARDUINO_WIFI_SHIELD is commented out for OPTION B. + * + * IMPORTANT: You must have the WiFI 101 library installed. To easily install this library, open + * the library manager via: Arduino IDE Menus: Sketch > Include Library > Manage Libraries > filter + * search for "WiFi101" > Select the result and click 'install' + */ +//#define WIFI_101 + +//do not modify the following 11 lines +#if defined(ARDUINO_SAMD_MKR1000) && !defined(WIFI_101) +// automatically include if compiling for MRK1000 +#define WIFI_101 +#endif +#ifdef WIFI_101 +#include +#include "utility/WiFiClientStream.h" +#include "utility/WiFiServerStream.h" + #define WIFI_LIB_INCLUDED +#endif + +/* + * OPTION B: Configure for legacy Arduino WiFi shield + * + * This will configure ConfigurableFirmataWiFi to use the original WiFi library (deprecated) provided + * with the Arduino IDE. It is supported by the Arduino WiFi shield (a discontinued product) and + * is compatible with 802.11 B/G networks. + * + * To configure ConfigurableFirmataWiFi to use the legacy Arduino WiFi shield + * leave the #define below uncommented and ensure #define WIFI_101 is commented out for OPTION A. + */ +//#define ARDUINO_WIFI_SHIELD + +//do not modify the following 10 lines +#ifdef ARDUINO_WIFI_SHIELD +#include +#include "utility/WiFiClientStream.h" +#include "utility/WiFiServerStream.h" + #ifdef WIFI_LIB_INCLUDED + #define MULTIPLE_WIFI_LIB_INCLUDES + #else + #define WIFI_LIB_INCLUDED + #endif +#endif + +/* + * OPTION C: Configure for ESP8266 + * + * This will configure ConfigurableFirmataWiFi to use the ESP8266WiFi library for boards + * with an ESP8266 chip. It is compatible with 802.11 B/G/N networks. + * + * The appropriate libraries are included automatically when compiling for the ESP8266 so + * continue on to STEP 2. + * + * IMPORTANT: You must have the esp8266 board support installed. To easily install this board see + * the instructions here: https://github.com/esp8266/Arduino#installing-with-boards-manager. + */ +//do not modify the following 14 lines +#ifdef ESP8266 +// automatically include if compiling for ESP8266 +#define ESP8266_WIFI +#endif +#ifdef ESP8266_WIFI +#include +#include "utility/WiFiClientStream.h" +#include "utility/WiFiServerStream.h" + #ifdef WIFI_LIB_INCLUDED + #define MULTIPLE_WIFI_LIB_INCLUDES + #else + #define WIFI_LIB_INCLUDED + #endif +#endif + + +// STEP 2 [OPTIONAL for all boards and shields] +// By default the board/shield is configured as a TCP server. +// If you want to setup you board/shield as a TCP client, uncomment the following define and +// replace the REMOTE_SERVER_IP address below with the IP address of your remote server. +//#define REMOTE_SERVER_IP 10, 0, 0, 15 + + +// STEP 3 [REQUIRED for all boards and shields] +// replace this with your wireless network SSID +char ssid[] = "your_network_name"; + + +// STEP 4 [OPTIONAL for all boards and shields] +// If you want to use a static IP (v4) address, uncomment the line below. You can also change the IP. +// If the first line is commented out, the WiFi shield will attempt to get an IP from the DHCP server. +// If you are using a static IP with the ESP8266 then you must also uncomment the SUBNET and GATEWAY. +//#define STATIC_IP_ADDRESS 192,168,1,113 +//#define SUBNET_MASK 255,255,255,0 // REQUIRED for ESP8266_WIFI, optional for others +//#define GATEWAY_IP_ADDRESS 0,0,0,0 // REQUIRED for ESP8266_WIFI, optional for others + + +// STEP 5 [REQUIRED for all boards and shields] +// define your port number here, you will need this to open a TCP connection to your Arduino +#define NETWORK_PORT 3030 + + +// STEP 6 [REQUIRED for all boards and shields] +// determine your network security type (OPTION A, B, or C). Option A is the most common, and the +// default. + +/* + * OPTION A: WPA / WPA2 + * + * WPA is the most common network security type. A passphrase is required to connect to this type. + * + * To enable, leave #define WIFI_WPA_SECURITY uncommented below, set your wpa_passphrase value + * appropriately, and do not uncomment the #define values under options B and C + */ +#define WIFI_WPA_SECURITY + +#ifdef WIFI_WPA_SECURITY +char wpa_passphrase[] = "your_wpa_passphrase"; +#endif //WIFI_WPA_SECURITY + + +/* + * OPTION B: WEP (not supported for ESP8266) + * + * WEP is a less common (and regarded as less safe) security type. A WEP key and its associated + * index are required to connect to this type. + * + * To enable, Uncomment the #define below, set your wep_index and wep_key values appropriately, + * and verify the #define values under options A and C are commented out. + */ +//#define WIFI_WEP_SECURITY + +#ifdef WIFI_WEP_SECURITY +//The wep_index below is a zero-indexed value. +//Valid indices are [0-3], even if your router/gateway numbers your keys [1-4]. +byte wep_index = 0; +char wep_key[] = "your_wep_key"; +#endif //WIFI_WEP_SECURITY + + +/* + * OPTION C: Open network (no security) + * + * Open networks have no security, can be connected to by any device that knows the ssid, and are + * unsafe. + * + * To enable, uncomment #define WIFI_NO_SECURITY below and verify the #define values + * under options A and B are commented out. + */ +//#define WIFI_NO_SECURITY + +/*============================================================================== + * CONFIGURATION ERROR CHECK (don't change anything here) + *============================================================================*/ + +#ifdef MULTIPLE_WIFI_LIB_INCLUDES +#error "you may not define more than one wifi device type in wifiConfig.h." +#endif + +#ifndef WIFI_LIB_INCLUDED +#error "you must define a wifi device type in wifiConfig.h." +#endif + +#if ((defined(WIFI_NO_SECURITY) && (defined(WIFI_WEP_SECURITY) || defined(WIFI_WPA_SECURITY))) || (defined(WIFI_WEP_SECURITY) && defined(WIFI_WPA_SECURITY))) +#error "you may not define more than one security type at the same time in wifiConfig.h." +#endif //WIFI_* security define check + +#if !(defined(WIFI_NO_SECURITY) || defined(WIFI_WEP_SECURITY) || defined(WIFI_WPA_SECURITY)) +#error "you must define a wifi security type in wifiConfig.h." +#endif //WIFI_* security define check + +#if (defined(ESP8266_WIFI) && !(defined(WIFI_NO_SECURITY) || (defined(WIFI_WPA_SECURITY)))) +#error "you must choose between WIFI_NO_SECURITY and WIFI_WPA_SECURITY" +#endif + +/*============================================================================== + * WIFI STREAM (don't change anything here) + *============================================================================*/ + +#ifdef REMOTE_SERVER_IP + WiFiClientStream stream(IPAddress(REMOTE_SERVER_IP), NETWORK_PORT); +#else + WiFiServerStream stream(NETWORK_PORT); +#endif + +/*============================================================================== + * PIN IGNORE MACROS (don't change anything here) + *============================================================================*/ + +#if defined(WIFI_101) && !defined(ARDUINO_SAMD_MKR1000) +// ignore SPI pins, pin 5 (reset WiFi101 shield), pin 7 (WiFi handshake) and pin 10 (WiFi SS) +// also don't ignore SS pin if it's not pin 10. Not needed for Arduino MKR1000. +#define IS_IGNORE_PIN(p) ((p) == 10 || (IS_PIN_SPI(p) && (p) != SS) || (p) == 5 || (p) == 7) + +#elif defined(ARDUINO_WIFI_SHIELD) && defined(__AVR_ATmega32U4__) +// ignore SPI pins, pin 4 (SS for SD-Card on WiFi-shield), pin 7 (WiFi handshake) and pin 10 (WiFi SS) +// On Leonardo, pin 24 maps to D4 and pin 28 maps to D10 +#define IS_IGNORE_PIN(p) ((IS_PIN_SPI(p) || (p) == 4) || (p) == 7 || (p) == 10 || (p) == 24 || (p) == 28) + +#elif defined(ARDUINO_WIFI_SHIELD) +// ignore SPI pins, pin 4 (SS for SD-Card on WiFi-shield), pin 7 (WiFi handshake) and pin 10 (WiFi SS) +#define IS_IGNORE_PIN(p) ((IS_PIN_SPI(p) || (p) == 4) || (p) == 7 || (p) == 10) + +#elif defined(ESP8266_WIFI) && defined(SERIAL_DEBUG) +#define IS_IGNORE_PIN(p) ((p) == 1) + +#endif + +/*============================================================================== + * FIRMATA FEATURE CONFIGURATION + * + * Comment out the include and declaration for any features that you do not need + * below. + * + * WARNING: Including all of the following features (especially if also using + * Ethernet) may exceed the Flash and/or RAM of lower memory boards such as the + * Arduino Uno or Leonardo. + *============================================================================*/ + +#include +DigitalInputFirmata digitalInput; + +#include +DigitalOutputFirmata digitalOutput; + +#include +AnalogInputFirmata analogInput; + +#include +AnalogOutputFirmata analogOutput; + +#include +#include +ServoFirmata servo; +// ServoFirmata depends on AnalogOutputFirmata +#if defined ServoFirmata_h && ! defined AnalogOutputFirmata_h +#error AnalogOutputFirmata must be included to use ServoFirmata +#endif + +#include +#include +I2CFirmata i2c; + +#include +OneWireFirmata oneWire; + +// StepperFirmata is deprecated as of ConfigurableFirmata v2.10.0. Please update your +// client implementation to use the new, more full featured and scalable AccelStepperFirmata. +#include +StepperFirmata stepper; + +#include +AccelStepperFirmata accelStepper; + +#include +SerialFirmata serial; + +#include +FirmataExt firmataExt; + +#include +FirmataScheduler scheduler; + +// To add Encoder support you must first install the FirmataEncoder and Encoder libraries: +// https://github.com/firmata/FirmataEncoder +// https://www.pjrc.com/teensy/td_libs_Encoder.html +// #include +// #include +// FirmataEncoder encoder; + +/*=================================================================================== + * END FEATURE CONFIGURATION - you should not need to change anything below this line + *==================================================================================*/ + +// dependencies. Do not comment out the following lines +#if defined AnalogOutputFirmata_h || defined ServoFirmata_h +#include +#endif + +#if defined AnalogInputFirmata_h || defined I2CFirmata_h || defined FirmataEncoder_h +#include +FirmataReporting reporting; +#endif + + +#ifdef STATIC_IP_ADDRESS +IPAddress local_ip(STATIC_IP_ADDRESS); +#endif +#ifdef SUBNET_MASK +IPAddress subnet(SUBNET_MASK); +#endif +#ifdef GATEWAY_IP_ADDRESS +IPAddress gateway(GATEWAY_IP_ADDRESS); +#endif + +int connectionAttempts = 0; +bool streamConnected = false; + +/*============================================================================== + * FUNCTIONS + *============================================================================*/ + +void systemResetCallback() +{ + // initialize a default state + + // pins with analog capability default to analog input + // otherwise, pins default to digital output + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_ANALOG(i)) { +#ifdef AnalogInputFirmata_h + // turns off pull-up, configures everything + Firmata.setPinMode(i, PIN_MODE_ANALOG); +#endif + } else if (IS_PIN_DIGITAL(i)) { +#ifdef DigitalOutputFirmata_h + // sets the output to 0, configures portConfigInputs + Firmata.setPinMode(i, OUTPUT); +#endif + } + } + +#ifdef FirmataExt_h + firmataExt.reset(); +#endif +} + +/* + * Called when a TCP connection is either connected or disconnected. + * TODO: + * - report connected or reconnected state to host (to be added to protocol) + */ +void hostConnectionCallback(byte state) +{ + switch (state) { + case HOST_CONNECTION_CONNECTED: + DEBUG_PRINTLN( "TCP connection established" ); + break; + case HOST_CONNECTION_DISCONNECTED: + DEBUG_PRINTLN( "TCP connection disconnected" ); + break; + } +} + +void printWifiStatus() { + if ( WiFi.status() != WL_CONNECTED ) + { + DEBUG_PRINT( "WiFi connection failed. Status value: " ); + DEBUG_PRINTLN( WiFi.status() ); + } + else + { + // print the SSID of the network you're attached to: + DEBUG_PRINT( "SSID: " ); + DEBUG_PRINTLN( WiFi.SSID() ); + + // print your WiFi shield's IP address: + DEBUG_PRINT( "IP Address: " ); + IPAddress ip = WiFi.localIP(); + DEBUG_PRINTLN( ip ); + + // print the received signal strength: + DEBUG_PRINT( "signal strength (RSSI): " ); + long rssi = WiFi.RSSI(); + DEBUG_PRINT( rssi ); + DEBUG_PRINTLN( " dBm" ); + } +} + +/* + * ConfigurableFirmataWiFi communicates with WiFi shields over SPI. Therefore all + * SPI pins must be set to IGNORE. Otherwise Firmata would break SPI communication. + * Additional pins may also need to be ignored depending on the particular board or + * shield in use. + */ +void ignorePins() +{ +#ifdef IS_IGNORE_PIN + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_IGNORE_PIN(i)) { + Firmata.setPinMode(i, PIN_MODE_IGNORE); + } + } +#endif + + //Set up controls for the Arduino WiFi Shield SS for the SD Card +#ifdef ARDUINO_WIFI_SHIELD + // Arduino WiFi Shield has SD SS wired to D4 + pinMode(PIN_TO_DIGITAL(4), OUTPUT); // switch off SD card bypassing Firmata + digitalWrite(PIN_TO_DIGITAL(4), HIGH); // SS is active low; + +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + pinMode(PIN_TO_DIGITAL(53), OUTPUT); // configure hardware SS as output on MEGA +#endif + +#endif //ARDUINO_WIFI_SHIELD +} + +void initTransport() +{ + // This statement will clarify how a connection is being made + DEBUG_PRINT( "ConfigurableFirmataWiFi will attempt a WiFi connection " ); +#if defined(WIFI_101) + DEBUG_PRINTLN( "using the WiFi 101 library." ); +#elif defined(ARDUINO_WIFI_SHIELD) + DEBUG_PRINTLN( "using the legacy WiFi library." ); +#elif defined(ESP8266_WIFI) + DEBUG_PRINTLN( "using the ESP8266 WiFi library." ); + //else should never happen here as error-checking in wifiConfig.h will catch this +#endif //defined(WIFI_101) + + // Configure WiFi IP Address +#ifdef STATIC_IP_ADDRESS + DEBUG_PRINT( "Using static IP: " ); + DEBUG_PRINTLN( local_ip ); +#if defined(ESP8266_WIFI) || (defined(SUBNET_MASK) && defined(GATEWAY_IP_ADDRESS)) + stream.config( local_ip , gateway, subnet ); +#else + // you can also provide a static IP in the begin() functions, but this simplifies + // ifdef logic in this sketch due to support for all different encryption types. + stream.config( local_ip ); +#endif +#else + DEBUG_PRINTLN( "IP will be requested from DHCP ..." ); +#endif + + stream.attach(hostConnectionCallback); + + // Configure WiFi security and initiate WiFi connection +#if defined(WIFI_WEP_SECURITY) + DEBUG_PRINT( "Attempting to connect to WEP SSID: " ); + DEBUG_PRINTLN(ssid); + stream.begin(ssid, wep_index, wep_key); +#elif defined(WIFI_WPA_SECURITY) + DEBUG_PRINT( "Attempting to connect to WPA SSID: " ); + DEBUG_PRINTLN(ssid); + stream.begin(ssid, wpa_passphrase); +#else //OPEN network + DEBUG_PRINTLN( "Attempting to connect to open SSID: " ); + DEBUG_PRINTLN(ssid); + stream.begin(ssid); +#endif //defined(WIFI_WEP_SECURITY) + DEBUG_PRINTLN( "WiFi setup done" ); + + // Wait for connection to access point to be established. + while (WiFi.status() != WL_CONNECTED && ++connectionAttempts <= MAX_CONN_ATTEMPTS) { + delay(500); + DEBUG_PRINT("."); + } + printWifiStatus(); +} + +void initFirmata() +{ + Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + +#ifdef FirmataExt_h +#ifdef DigitalInputFirmata_h + firmataExt.addFeature(digitalInput); +#endif +#ifdef DigitalOutputFirmata_h + firmataExt.addFeature(digitalOutput); +#endif +#ifdef AnalogInputFirmata_h + firmataExt.addFeature(analogInput); +#endif +#ifdef AnalogOutputFirmata_h + firmataExt.addFeature(analogOutput); +#endif +#ifdef ServoFirmata_h + firmataExt.addFeature(servo); +#endif +#ifdef I2CFirmata_h + firmataExt.addFeature(i2c); +#endif +#ifdef OneWireFirmata_h + firmataExt.addFeature(oneWire); +#endif +#ifdef StepperFirmata_h + firmataExt.addFeature(stepper); +#endif +#ifdef AccelStepperFirmata_h +firmataExt.addFeature(accelStepper); +#endif +#ifdef SerialFirmata_h + firmataExt.addFeature(serial); +#endif +#ifdef FirmataReporting_h + firmataExt.addFeature(reporting); +#endif +#ifdef FirmataScheduler_h + firmataExt.addFeature(scheduler); +#endif +#ifdef FirmataEncoder_h + firmataExt.addFeature(encoder); +#endif +#endif + /* systemResetCallback is declared here (in ConfigurableFirmata.ino) */ + Firmata.attach(SYSTEM_RESET, systemResetCallback); + + ignorePins(); + + // Initialize Firmata to use the WiFi stream object as the transport. + Firmata.begin(stream); + Firmata.parse(SYSTEM_RESET); // reset to default config +} + + +/*============================================================================== + * SETUP() + *============================================================================*/ + +void setup() +{ + DEBUG_BEGIN(9600); + + initTransport(); + + initFirmata(); +} + +/*============================================================================== + * LOOP() + *============================================================================*/ +void loop() +{ +#ifdef DigitalInputFirmata_h + /* DIGITALREAD - as fast as possible, check for changes and output them to the + * stream buffer using Firmata.write() */ + digitalInput.report(); +#endif + + /* STREAMREAD - processing incoming message as soon as possible, while still + * checking digital inputs. */ + while (Firmata.available()) { + Firmata.processInput(); +#ifdef FirmataScheduler_h + if (!Firmata.isParsingMessage()) { + goto runtasks; + } + } + if (!Firmata.isParsingMessage()) { +runtasks: scheduler.runTasks(); +#endif + } + + // TODO - ensure that Stream buffer doesn't go over 60 bytes + +#ifdef FirmataReporting_h + if (reporting.elapsed()) { +#ifdef AnalogInputFirmata_h + /* ANALOGREAD - do all analogReads() at the configured sampling interval */ + analogInput.report(); +#endif +#ifdef I2CFirmata_h + // report i2c data for all device with read continuous mode enabled + i2c.report(); +#endif +#ifdef FirmataEncoder_h + // report encoders positions if reporting enabled. + encoder.report(); +#endif + } +#endif +#ifdef StepperFirmata_h + stepper.update(); +#endif +#ifdef AccelStepperFirmata_h +accelStepper.update(); +#endif +#ifdef SerialFirmata_h + serial.update(); +#endif + + // keep the WiFi connection live. Attempts to reconnect automatically if disconnected. + stream.maintain(); +} diff --git a/firmware/configurable_firmata/extras/LICENSE.txt b/firmware/configurable_firmata/extras/LICENSE.txt new file mode 100644 index 0000000..77cec6d --- /dev/null +++ b/firmware/configurable_firmata/extras/LICENSE.txt @@ -0,0 +1,458 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + diff --git a/firmware/configurable_firmata/keywords.txt b/firmware/configurable_firmata/keywords.txt new file mode 100644 index 0000000..d713ff1 --- /dev/null +++ b/firmware/configurable_firmata/keywords.txt @@ -0,0 +1,96 @@ +####################################### +# Syntax Coloring Map For Firmata +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +Firmata KEYWORD1 Firmata +callbackFunction KEYWORD1 callbackFunction +systemResetCallbackFunction KEYWORD1 systemResetCallbackFunction +stringCallbackFunction KEYWORD1 stringCallbackFunction +sysexCallbackFunction KEYWORD1 sysexCallbackFunction + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +printVersion KEYWORD2 +blinkVersion KEYWORD2 +printFirmwareVersion KEYWORD2 +setFirmwareVersion KEYWORD2 +setFirmwareNameAndVersion KEYWORD2 +available KEYWORD2 +processInput KEYWORD2 +isParsingMessage KEYWORD2 +parse KEYWORD2 +sendAnalog KEYWORD2 +sendDigital KEYWORD2 +sendDigitalPort KEYWORD2 +sendString KEYWORD2 +sendSysex KEYWORD2 +attach KEYWORD2 +detach KEYWORD2 +write KEYWORD2 +sendValueAsTwo7bitBytes KEYWORD2 +startSysex KEYWORD2 +endSysex KEYWORD2 +attachDelayTask KEYWORD2 +delayTask KEYWORD2 +getPinMode KEYWORD2 +setPinMode KEYWORD2 +getPinState KEYWORD2 +setPinState KEYWORD2 +writePort KEYWORD2 +readPort KEYWORD2 +disableBlinkVersion KEYWORD2 + + +####################################### +# Constants (LITERAL1) +####################################### + +FIRMATA_PROTOCOL_MAJOR_VERSION LITERAL1 +FIRMATA_PROTOCOL_MINOR_VERSION LITERAL1 +FIRMATA_PROTOCOL_BUGFIX_VERSION LITERAL1 + +FIRMATA_FIRMWARE_MAJOR_VERSION LITERAL1 +FIRMATA_FIRMWARE_MINOR_VERSION LITERAL1 +FIRMATA_FIRMWARE_BUGFIX_VERSION LITERAL1 + +MAX_DATA_BYTES LITERAL1 + +DIGITAL_MESSAGE LITERAL1 +ANALOG_MESSAGE LITERAL1 +REPORT_ANALOG LITERAL1 +REPORT_DIGITAL LITERAL1 +REPORT_VERSION LITERAL1 +SET_PIN_MODE LITERAL1 +SET_DIGITAL_PIN_VALUE LITERAL1 +SYSTEM_RESET LITERAL1 +START_SYSEX LITERAL1 +END_SYSEX LITERAL1 +REPORT_FIRMWARE LITERAL1 +STRING_DATA LITERAL1 + +PIN_MODE_ANALOG LITERAL1 +PIN_MODE_PWM LITERAL1 +PIN_MODE_SERVO LITERAL1 +PIN_MODE_SHIFT LITERAL1 +PIN_MODE_I2C LITERAL1 +PIN_MODE_ONEWIRE LITERAL1 +PIN_MODE_STEPPER LITERAL1 +PIN_MODE_ENCODER LITERAL1 +PIN_MODE_SERIAL LITERAL1 +PIN_MODE_PULLUP LITERAL1 +PIN_MODE_IGNORE LITERAL1 + +TOTAL_PINS LITERAL1 +TOTAL_ANALOG_PINS LITERAL1 +TOTAL_DIGITAL_PINS LITERAL1 +TOTAL_PIN_MODES LITERAL1 +TOTAL_PORTS LITERAL1 +ANALOG_PORT LITERAL1 +MAX_SERVOS LITERAL1 diff --git a/firmware/configurable_firmata/library.json b/firmware/configurable_firmata/library.json new file mode 100644 index 0000000..1f7d50e --- /dev/null +++ b/firmware/configurable_firmata/library.json @@ -0,0 +1,16 @@ +{ + "name": "ConfigurableFirmata", + "keywords": "interface, protocol", + "description": "This library implements the Firmata protocol as a set of plugins that can be used to create applications to remotely interface with an Arduino board.", + "repository": { + "type": "git", + "url": "https://github.com/firmata/ConfigurableFirmata.git" + }, + "version": "2.10.1", + "exclude": [ + "extras", + "test" + ], + "frameworks": "arduino", + "platforms": "*" +} diff --git a/firmware/configurable_firmata/library.properties b/firmware/configurable_firmata/library.properties new file mode 100644 index 0000000..1e4dbf5 --- /dev/null +++ b/firmware/configurable_firmata/library.properties @@ -0,0 +1,9 @@ +name=ConfigurableFirmata +version=2.10.1 +author=Firmata Developers +maintainer=https://github.com/firmata/ConfigurableFirmata +sentence=This library implements the Firmata protocol as a set of plugins that can be used to create applications to remotely interface with an Arduino board. +paragraph=ConfigurableFirmata is an implementation of the Firmata protocol that breaks features such as Digital Input, Digital Output, Analog Input, Analog Output, I2C, etc into individual classes making it easier to mix and match standard features with custom features. +category=Device Control +url=https://github.com/firmata/ConfigurableFirmata +architectures=* diff --git a/firmware/configurable_firmata/readme.md b/firmware/configurable_firmata/readme.md new file mode 100644 index 0000000..9c2a19a --- /dev/null +++ b/firmware/configurable_firmata/readme.md @@ -0,0 +1,73 @@ +# ConfigurableFirmata + +[![Join the chat at https://gitter.im/firmata/ConfigurableFirmata](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/firmata/ConfigurableFirmata?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Firmata is a protocol for communicating with microcontrollers from software on a host computer. The [protocol](https://github.com/firmata/protocol) can be implemented in firmware on any microcontroller architecture as well as software on any host computer software package. The arduino repository described here is a Firmata library for Arduino and Arduino-compatible devices. If you would like to contribute to Firmata, please see the [Contributing](#contributing) section below. + +## Installation + +- **If you are using Arduino IDE version 1.6.4 or higher** go to `Sketch > Include Library > Manage Libraries` and then search for "ConfigurableFirmata" and click on `Install` after tapping on the ConfigurableFirmata item in the filtered results. You can also use this same method to update ConfigurableFirmata in the future. +- **If you are using an older version of the Arduino IDE**, download or clone ConfigurableFirmata to your Arduino sketchbook library folder. This is typically `/Documents/Arduino/libraries/` on Mac or Linux or `\My Documents\Arduino\libraries\` on Windows. + +## Usage + +ConfigurableFirmata is a version of Firmata that breaks features such as Digital Input, Digital Output, Analog Input, Analog Output, I2C, etc into [individual classes](https://github.com/firmata/ConfigurableFirmata/tree/master/src) making it easier to mix and match standard features with custom features. + +The easiest way to use ConfigurableFirmata is with [firmatabuilder](http://firmatabuilder.com) which is a simple web application that generates an Arduino sketch based on a selection of Firmata features. Download the generated sketch, compile and upload it to your board. + +Another way to use ConfigurableFirmata is by adding or removing various include statements in the [ConfigurableFirmata.ino](https://github.com/firmata/ConfigurableFirmata/blob/master/examples/ConfigurableFirmata/ConfigurableFirmata.ino) example file. + +## Firmata Wrapper Libraries + +You can use the ConfigurableFirmata architecture to wrap 3rd party libraries to include +functionality not included in the base ConfigurableFirmata.ino example. See [FirmataEncoder](https://github.com/firmata/FirmataEncoder) for an example of a Firmata wrapper. To include a Firmata wrapper your +ino file, you must install both the sketch and the 3rd party library into your `/Arduino/libraries/` +directory (where all 3rd party libraries are installed). + +When creating a new Firmata wrapper library, you generally should not include the 3rd party +library it wraps. For example, the Encoder library that FirmataEncoder wraps is not included with +the FirmataEncoder library. + +If you create a wrapper library, prepend the name with 'Firmata'. Hence 'FirmataEncoder' in the +referenced example. This will keep the wrapper libraries together in the user's Arduino libraries +directory. + +A Firmata wrapper template library will be published soon along with instructions for creating +a wrapper library. + +## Firmata Client Libraries +Only a few Firmata client libraries currently support ConfigurableFirmata: + +* javascript + * [https://github.com/jgautier/firmata] + * [https://github.com/rwldrn/johnny-five] + * [http://breakoutjs.com] +* perl + * [https://github.com/ntruchsess/perl-firmata] + +*Additional Firmata client libraries may work as well. If you're a client library developer and have verified that you library works with ConfigurableFirmata, please [open an issue](https://github.com/firmata/ConfigurableFirmata/issues) with a request to add the link.* + +## Contributing + +If you discover a bug or would like to propose a new feature, please open a new [issue](https://github.com/firmata/ConfigurableFirmata/issues?sort=created&state=open). + +To contribute, fork this repository and create a new topic branch for the bug, feature or other existing issue you are addressing. Submit the pull request against the *master* branch. + +You must thoroughly test your contributed code. In your pull request, describe tests performed to ensure that no existing code is broken and that any changes maintain backwards compatibility with the existing api. Test on multiple Arduino board variants if possible. We hope to enable some form of automated (or at least semi-automated) testing in the future, but for now any tests will need to be executed manually by the contributor and reviewers. + +Use [Artistic Style](http://astyle.sourceforge.net/) (astyle) to format your code. Set the following rules for the astyle formatter: + +``` +style = "" +indent = "spaces" +indent-spaces = 2 +indent-classes = true +indent-switches = true +indent-cases = true +indent-col1-comments = true +pad-oper = true +pad-header = true +keep-one-line-statements = true +``` + +If you happen to use Sublime Text, [this astyle plugin](https://github.com/timonwong/SublimeAStyleFormatter) is helpful. Set the above rules in the user settings file. diff --git a/firmware/configurable_firmata/src/AccelStepperFirmata.cpp b/firmware/configurable_firmata/src/AccelStepperFirmata.cpp new file mode 100644 index 0000000..e58f15c --- /dev/null +++ b/firmware/configurable_firmata/src/AccelStepperFirmata.cpp @@ -0,0 +1,420 @@ +/* + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated: September 16th, 2017 +*/ + +#include +#include "AccelStepperFirmata.h" +#include "utility/AccelStepper.h" +#include "utility/MultiStepper.h" + +boolean AccelStepperFirmata::handlePinMode(byte pin, int mode) +{ + if (mode == PIN_MODE_STEPPER) { + if (IS_PIN_DIGITAL(pin)) { + pinMode(PIN_TO_DIGITAL(pin), OUTPUT); + return true; + } + } + return false; +} + +void AccelStepperFirmata::handleCapability(byte pin) +{ + if (IS_PIN_DIGITAL(pin)) { + Firmata.write(PIN_MODE_STEPPER); + Firmata.write(21); //21 bits used for number of steps + } +} + +// Send position data when it's requested or a move completes +void AccelStepperFirmata::reportPosition(byte deviceNum, bool complete) +{ + if (stepper[deviceNum]) { + byte data[5]; + long position = stepper[deviceNum]->currentPosition(); + encode32BitSignedInteger(position, data); + + Firmata.write(START_SYSEX); + Firmata.write(ACCELSTEPPER_DATA); + if (complete) { + Firmata.write(ACCELSTEPPER_MOVE_COMPLETE); + } else { + Firmata.write(ACCELSTEPPER_REPORT_POSITION); + } + Firmata.write(deviceNum); + Firmata.write(data[0]); + Firmata.write(data[1]); + Firmata.write(data[2]); + Firmata.write(data[3]); + Firmata.write(data[4]); + Firmata.write(END_SYSEX); + } +} + +void AccelStepperFirmata::reportGroupComplete(byte deviceNum) +{ + if (group[deviceNum]) { + Firmata.write(START_SYSEX); + Firmata.write(ACCELSTEPPER_DATA); + Firmata.write(MULTISTEPPER_MOVE_COMPLETE); + Firmata.write(deviceNum); + Firmata.write(END_SYSEX); + } +} + +/*============================================================================== + * SYSEX-BASED commands + *============================================================================*/ + +boolean AccelStepperFirmata::handleSysex(byte command, byte argc, byte *argv) +{ + if (command == ACCELSTEPPER_DATA) { + byte stepCommand, deviceNum, interface, wireCount, stepType; + byte stepOrMotorPin1, directionOrMotorPin2; + byte motorPin3 = 0, motorPin4 = 0, enablePin = 0, invertPins = 0; + long numSteps; + + unsigned int index = 0; + + stepCommand = argv[index++]; + deviceNum = argv[index++]; + + if (deviceNum < MAX_ACCELSTEPPERS) { + + if (stepCommand == ACCELSTEPPER_CONFIG) { + interface = argv[index++]; + wireCount = (interface & 0x70) >> 4; // upper 3 bits are the wire count + stepType = (interface & 0x0e) >> 1; // next 3 bits are the step type + stepOrMotorPin1 = argv[index++]; // Step pin for driver or MotorPin1 + directionOrMotorPin2 = argv[index++]; // Direction pin for driver or motorPin2 + + if (Firmata.getPinMode(directionOrMotorPin2) == PIN_MODE_IGNORE + || Firmata.getPinMode(stepOrMotorPin1) == PIN_MODE_IGNORE) { + return false; + } + + Firmata.setPinMode(stepOrMotorPin1, PIN_MODE_STEPPER); + Firmata.setPinMode(directionOrMotorPin2, PIN_MODE_STEPPER); + + if (!stepper[deviceNum]) { + numSteppers++; + } + + if (wireCount >= 3) { + motorPin3 = argv[index++]; + if (Firmata.getPinMode(motorPin3) == PIN_MODE_IGNORE) + return false; + Firmata.setPinMode(motorPin3, PIN_MODE_STEPPER); + } + + if (wireCount >= 4) { + motorPin4 = argv[index++]; + if (Firmata.getPinMode(motorPin4) == PIN_MODE_IGNORE) + return false; + Firmata.setPinMode(motorPin4, PIN_MODE_STEPPER); + } + + // If we have an enable pin + if (interface & 0x01) { + enablePin = argv[index++]; + if (Firmata.getPinMode(enablePin) == PIN_MODE_IGNORE) + return false; + } + + // Instantiate our stepper + if (wireCount == 1) { + stepper[deviceNum] = new AccelStepper(AccelStepper::DRIVER, stepOrMotorPin1, directionOrMotorPin2); + } else if (wireCount == 2) { + stepper[deviceNum] = new AccelStepper(AccelStepper::FULL2WIRE, stepOrMotorPin1, directionOrMotorPin2); + } else if (wireCount == 3 && stepType == STEP_TYPE_WHOLE) { + stepper[deviceNum] = new AccelStepper(AccelStepper::FULL3WIRE, stepOrMotorPin1, directionOrMotorPin2, motorPin3); + } else if (wireCount == 3 && stepType == STEP_TYPE_HALF) { + stepper[deviceNum] = new AccelStepper(AccelStepper::HALF3WIRE, stepOrMotorPin1, directionOrMotorPin2, motorPin3); + } else if (wireCount == 4 && stepType == STEP_TYPE_WHOLE) { + stepper[deviceNum] = new AccelStepper(AccelStepper::FULL4WIRE, stepOrMotorPin1, directionOrMotorPin2, motorPin3, motorPin4, false); + } else if (wireCount == 4 && stepType == STEP_TYPE_HALF) { + stepper[deviceNum] = new AccelStepper(AccelStepper::HALF4WIRE, stepOrMotorPin1, directionOrMotorPin2, motorPin3, motorPin4, false); + } + + // If there is still another byte to read we must be inverting some pins + if (argc >= index) { + invertPins = argv[index]; + if (wireCount == 1) { + stepper[deviceNum]->setPinsInverted(invertPins & 0x01, invertPins >> 1 & 0x01, invertPins >> 4 & 0x01); + } else { + stepper[deviceNum]->setPinsInverted(invertPins & 0x01, invertPins >> 1 & 0x01, invertPins >> 2 & 0x01, invertPins >> 3 & 0x01, invertPins >> 4 & 0x01); + } + } + + if (interface & 0x01) { + stepper[deviceNum]->setEnablePin(enablePin); + } + + /* + Default to no acceleration. We set the acceleration value high enough that our speed is + reached on the first step of a movement. + More info about this hack in ACCELSTEPPER_SET_ACCELERATION. + + The lines where we are setting the speed twice are necessary because if the max speed doesn't change + from the default value then our time to next step does not get computed after raising the acceleration. + */ + stepper[deviceNum]->setMaxSpeed(2.0); + stepper[deviceNum]->setMaxSpeed(1.0); + stepper[deviceNum]->setAcceleration(MAX_ACCELERATION); + + isRunning[deviceNum] = false; + + } + + else if (stepCommand == ACCELSTEPPER_STEP) { + numSteps = decode32BitSignedInteger(argv[2], argv[3], argv[4], argv[5], argv[6]); + + if (stepper[deviceNum]) { + stepper[deviceNum]->move(numSteps); + isRunning[deviceNum] = true; + } + + } + + else if (stepCommand == ACCELSTEPPER_ZERO) { + if (stepper[deviceNum]) { + stepper[deviceNum]->setCurrentPosition(0); + } + } + + else if (stepCommand == ACCELSTEPPER_TO) { + if (stepper[deviceNum]) { + numSteps = decode32BitSignedInteger(argv[2], argv[3], argv[4], argv[5], argv[6]); + stepper[deviceNum]->moveTo(numSteps); + isRunning[deviceNum] = true; + } + } + + else if (stepCommand == ACCELSTEPPER_ENABLE) { + if (stepper[deviceNum]) { + if (argv[2] == 0x00) { + stepper[deviceNum]->disableOutputs(); + } else { + stepper[deviceNum]->enableOutputs(); + } + } + } + + else if (stepCommand == ACCELSTEPPER_STOP) { + if (stepper[deviceNum]) { + stepper[deviceNum]->stop(); + isRunning[deviceNum] = false; + reportPosition(deviceNum, true); + } + } + + else if (stepCommand == ACCELSTEPPER_REPORT_POSITION) { + if (stepper[deviceNum]) { + reportPosition(deviceNum, false); + } + } + + else if (stepCommand == ACCELSTEPPER_SET_ACCELERATION) { + float decodedAcceleration = decodeCustomFloat(argv[2], argv[3], argv[4], argv[5]); + + if (stepper[deviceNum]) { + /* + + All firmata instances of accelStepper have an acceleration value. If a user does not + want acceleration we just set the acceleration value high enough so + that the chosen speed will be realized by the first step of a movement. + This simplifies some of the logic in StepperFirmata and gives us more flexibility + should an alternative stepper library become available at a future date. + */ + if (decodedAcceleration == 0.0) { + stepper[deviceNum]->setAcceleration(MAX_ACCELERATION); + } else { + stepper[deviceNum]->setAcceleration(decodedAcceleration); + } + /* + + */ + } + + } + + else if (stepCommand == ACCELSTEPPER_SET_SPEED) { + // Sets the maxSpeed for accelStepper. We do not use setSpeed here because + // all instances of accelStepper that have been created by firmata are + // using acceleration. More info about this hack in ACCELSTEPPER_SET_ACCELERATION. + float speed = decodeCustomFloat(argv[2], argv[3], argv[4], argv[5]); + + if (stepper[deviceNum]) { + stepper[deviceNum]->setMaxSpeed(speed); + } + } + + else if (stepCommand == MULTISTEPPER_CONFIG) { + if (!group[deviceNum]) { + numGroups++; + group[deviceNum] = new MultiStepper(); + } + + for (byte i = index; i < argc; i++) { + byte stepperNumber = argv[i]; + + if (stepper[stepperNumber]) { + groupStepperCount[deviceNum]++; + group[deviceNum]->addStepper(*stepper[stepperNumber]); + } + } + + groupIsRunning[deviceNum] = false; + } + + else if (stepCommand == MULTISTEPPER_TO) { + groupIsRunning[deviceNum] = true; + long positions[groupStepperCount[deviceNum]]; + + for (byte i = 0, offset = 0; i < groupStepperCount[deviceNum]; i++) { + offset = index + (i * 5); + positions[i] = decode32BitSignedInteger(argv[offset], argv[offset + 1], argv[offset + 2], argv[offset + 3], argv[offset + 4]); + } + + group[deviceNum]->moveTo(positions); + } + + else if (stepCommand == MULTISTEPPER_STOP) { + groupIsRunning[deviceNum] = false; + reportGroupComplete(deviceNum); + } + } + return true; + + } + return false; +} + +/*============================================================================== + * SETUP() + *============================================================================*/ + +void AccelStepperFirmata::reset() +{ + for (byte i = 0; i < MAX_ACCELSTEPPERS; i++) { + if (stepper[i]) { + free(stepper[i]); + stepper[i] = 0; + } + } + numSteppers = 0; + + for (byte i = 0; i < MAX_GROUPS; i++) { + if (group[i]) { + free(group[i]); + group[i] = 0; + } + } + numGroups = 0; +} + +/*============================================================================== + * Helpers + *============================================================================*/ + +float AccelStepperFirmata::decodeCustomFloat(byte arg1, byte arg2, byte arg3, byte arg4) +{ + long l4 = (long)arg4; + long significand = (long)arg1 | (long)arg2 << 7 | (long)arg3 << 14 | (l4 & 0x03) << 21; + float exponent = (float)(((l4 >> 2) & 0x0f) - 11); + bool sign = (bool)((l4 >> 6) & 0x01); + float result = (float)significand; + + if (sign) { + result *= -1; + } + + result = result * powf(10.0, exponent); + + return result; +} + +long AccelStepperFirmata::decode32BitSignedInteger(byte arg1, byte arg2, byte arg3, byte arg4, byte arg5) +{ + long result = (long)arg1 | (long)arg2 << 7 | (long)arg3 << 14 | (long)arg4 << 21 | (((long)arg5 << 28) & 0x07); + + if ((long)arg5 >> 3 == 0x01) { + result = result * -1; + } + + return result; +} + +void AccelStepperFirmata::encode32BitSignedInteger(long value, byte pdata[]) +{ + bool inv = false; + + if (value < 0) { + inv = true; + value = value * -1; + } + + pdata[0] = value & 0x7f; + pdata[1] = (value >> 7) & 0x7f; + pdata[2] = (value >> 14) & 0x7f; + pdata[3] = (value >> 21) & 0x7f; + pdata[4] = (value >> 28) & 0x7f; + + if (inv == true) { + pdata[4] = pdata[4] | 0x08; + } +} + +/*============================================================================== + * LOOP() + *============================================================================*/ +void AccelStepperFirmata::update() +{ + bool stepsLeft; + + if (numGroups > 0) { + // if one or more groups exist, update their position + for (byte i = 0; i < MAX_GROUPS; i++) { + if (group[i] && groupIsRunning[i] == true) { + stepsLeft = group[i]->run(); + + // send command to client application when stepping is complete + if (stepsLeft != true) { + groupIsRunning[i] = false; + reportGroupComplete(i); + } + + } + } + } + + if (numSteppers > 0) { + // if one or more stepper motors are used, update their position + for (byte i = 0; i < MAX_ACCELSTEPPERS; i++) { + if (stepper[i] && isRunning[i] == true) { + stepsLeft = stepper[i]->run(); + + // send command to client application when stepping is complete + if (!stepsLeft) { + isRunning[i] = false; + reportPosition(i, true); + } + + } + } + } + +} diff --git a/firmware/configurable_firmata/src/AccelStepperFirmata.h b/firmware/configurable_firmata/src/AccelStepperFirmata.h new file mode 100644 index 0000000..2f6ff2b --- /dev/null +++ b/firmware/configurable_firmata/src/AccelStepperFirmata.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef AccelStepperFirmata_h +#define AccelStepperFirmata_h + +#include +#include "utility/AccelStepper.h" +#include "utility/MultiStepper.h" +#include "FirmataFeature.h" + +#define MAX_ACCELSTEPPERS 10 // arbitrary value... may need to adjust +#define MAX_GROUPS 5 // arbitrary value... may need to adjust +#define MAX_ACCELERATION 1000000 // 1000^2 so that full speed is reached on first step +#define STEP_TYPE_WHOLE 0x00 +#define STEP_TYPE_HALF 0x01 +#define ACCELSTEPPER_CONFIG 0x00 +#define ACCELSTEPPER_ZERO 0x01 +#define ACCELSTEPPER_STEP 0x02 +#define ACCELSTEPPER_TO 0x03 +#define ACCELSTEPPER_ENABLE 0x04 +#define ACCELSTEPPER_STOP 0x05 +#define ACCELSTEPPER_REPORT_POSITION 0x06 +#define ACCELSTEPPER_LIMIT 0x07 +#define ACCELSTEPPER_SET_ACCELERATION 0x08 +#define ACCELSTEPPER_SET_SPEED 0x09 +#define ACCELSTEPPER_MOVE_COMPLETE 0x0a +#define MULTISTEPPER_CONFIG 0x20 +#define MULTISTEPPER_TO 0x21 +#define MULTISTEPPER_STOP 0x23 +#define MULTISTEPPER_MOVE_COMPLETE 0x24 + +class AccelStepperFirmata: public FirmataFeature +{ + public: + boolean handlePinMode(byte pin, int mode); + void handleCapability(byte pin); + void reportPosition(byte deviceNum, bool complete); + void reportGroupComplete(byte deviceNum); + boolean handleSysex(byte command, byte argc, byte *argv); + float decodeCustomFloat(byte arg1, byte arg2, byte arg3, byte arg4); + long decode28BitUnsignedInteger(byte arg1, byte arg2, byte arg3, byte arg4); + long decode32BitSignedInteger(byte arg1, byte arg2, byte arg3, byte arg4, byte arg5); + void encode32BitSignedInteger(long value, byte pdata[]); + void update(); + void reset(); + private: + AccelStepper *stepper[MAX_ACCELSTEPPERS]; + MultiStepper *group[MAX_GROUPS]; + bool isRunning[MAX_ACCELSTEPPERS]; + bool groupIsRunning[MAX_GROUPS]; + byte numSteppers; + byte numGroups; + byte groupStepperCount[MAX_GROUPS]; +}; + +#endif /* AccelStepperFirmata_h */ diff --git a/firmware/configurable_firmata/src/AnalogFirmata.cpp b/firmware/configurable_firmata/src/AnalogFirmata.cpp new file mode 100644 index 0000000..f2f3fe7 --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogFirmata.cpp @@ -0,0 +1,32 @@ +/* + AnalogFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#include +#include "AnalogFirmata.h" + +boolean handleAnalogFirmataSysex(byte command, byte argc, byte* argv) +{ + if (command == ANALOG_MAPPING_QUERY) { + Firmata.write(START_SYSEX); + Firmata.write(ANALOG_MAPPING_RESPONSE); + for (byte pin = 0; pin < TOTAL_PINS; pin++) { + Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127); + } + Firmata.write(END_SYSEX); + return true; + } + return false; +} diff --git a/firmware/configurable_firmata/src/AnalogFirmata.h b/firmware/configurable_firmata/src/AnalogFirmata.h new file mode 100644 index 0000000..69ae0fd --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogFirmata.h @@ -0,0 +1,24 @@ +/* + AnalogFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef AnalogFirmata_h +#define AnalogFirmata_h + +#include + +boolean handleAnalogFirmataSysex(byte command, byte argc, byte* argv); + +#endif diff --git a/firmware/configurable_firmata/src/AnalogInputFirmata.cpp b/firmware/configurable_firmata/src/AnalogInputFirmata.cpp new file mode 100644 index 0000000..4ac2377 --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogInputFirmata.cpp @@ -0,0 +1,109 @@ +/* + AnalogFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: November 22nd, 2015 +*/ + +#include +#include "AnalogFirmata.h" +#include "AnalogInputFirmata.h" + +AnalogInputFirmata *AnalogInputFirmataInstance; + +void reportAnalogInputCallback(byte analogPin, int value) +{ + AnalogInputFirmataInstance->reportAnalog(analogPin, value); +} + +AnalogInputFirmata::AnalogInputFirmata() +{ + AnalogInputFirmataInstance = this; + analogInputsToReport = 0; + Firmata.attach(REPORT_ANALOG, reportAnalogInputCallback); +} + +// ----------------------------------------------------------------------------- +/* sets bits in a bit array (int) to toggle the reporting of the analogIns + */ +//void FirmataClass::setAnalogPinReporting(byte pin, byte state) { +//} +void AnalogInputFirmata::reportAnalog(byte analogPin, int value) +{ + if (analogPin < TOTAL_ANALOG_PINS) { + if (value == 0) { + analogInputsToReport = analogInputsToReport & ~ (1 << analogPin); + } else { + analogInputsToReport = analogInputsToReport | (1 << analogPin); + // prevent during system reset or all analog pin values will be reported + // which may report noise for unconnected analog pins + if (!Firmata.isResetting()) { + // Send pin value immediately. This is helpful when connected via + // ethernet, wi-fi or bluetooth so pin states can be known upon + // reconnecting. + Firmata.sendAnalog(analogPin, analogRead(analogPin)); + } + } + } + // TODO: save status to EEPROM here, if changed +} + +boolean AnalogInputFirmata::handlePinMode(byte pin, int mode) +{ + if (IS_PIN_ANALOG(pin)) { + if (mode == PIN_MODE_ANALOG) { + reportAnalog(PIN_TO_ANALOG(pin), 1); // turn on reporting + if (IS_PIN_DIGITAL(pin)) { + pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver + } + return true; + } else { + reportAnalog(PIN_TO_ANALOG(pin), 0); // turn off reporting + } + } + return false; +} + +void AnalogInputFirmata::handleCapability(byte pin) +{ + if (IS_PIN_ANALOG(pin)) { + Firmata.write(PIN_MODE_ANALOG); + Firmata.write(10); // 10 = 10-bit resolution + } +} + +boolean AnalogInputFirmata::handleSysex(byte command, byte argc, byte* argv) +{ + return handleAnalogFirmataSysex(command, argc, argv); +} + +void AnalogInputFirmata::reset() +{ + // by default, do not report any analog inputs + analogInputsToReport = 0; +} + +void AnalogInputFirmata::report() +{ + byte pin, analogPin; + /* ANALOGREAD - do all analogReads() at the configured sampling interval */ + for (pin = 0; pin < TOTAL_PINS; pin++) { + if (IS_PIN_ANALOG(pin) && Firmata.getPinMode(pin) == PIN_MODE_ANALOG) { + analogPin = PIN_TO_ANALOG(pin); + if (analogInputsToReport & (1 << analogPin)) { + Firmata.sendAnalog(analogPin, analogRead(analogPin)); + } + } + } +} diff --git a/firmware/configurable_firmata/src/AnalogInputFirmata.h b/firmware/configurable_firmata/src/AnalogInputFirmata.h new file mode 100644 index 0000000..3c2f1bf --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogInputFirmata.h @@ -0,0 +1,42 @@ +/* + AnalogFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef AnalogInputFirmata_h +#define AnalogInputFirmata_h + +#include +#include "FirmataFeature.h" +#include "FirmataReporting.h" + +void reportAnalogInputCallback(byte analogPin, int value); + +class AnalogInputFirmata: public FirmataFeature +{ + public: + AnalogInputFirmata(); + void reportAnalog(byte analogPin, int value); + void handleCapability(byte pin); + boolean handlePinMode(byte pin, int mode); + boolean handleSysex(byte command, byte argc, byte* argv); + void reset(); + void report(); + + private: + /* analog inputs */ + int analogInputsToReport; // bitwise array to store pin reporting +}; + +#endif diff --git a/firmware/configurable_firmata/src/AnalogOutputFirmata.cpp b/firmware/configurable_firmata/src/AnalogOutputFirmata.cpp new file mode 100644 index 0000000..7239c4f --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogOutputFirmata.cpp @@ -0,0 +1,67 @@ +/* + AnalogFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated December 23rd, 2016 +*/ + +#include +#include "AnalogFirmata.h" +#include "AnalogOutputFirmata.h" + +AnalogOutputFirmata::AnalogOutputFirmata() +{ + Firmata.attach(ANALOG_MESSAGE, analogWriteCallback); +} + +void AnalogOutputFirmata::reset() +{ + +} + +boolean AnalogOutputFirmata::handlePinMode(byte pin, int mode) +{ + if (mode == PIN_MODE_PWM && IS_PIN_PWM(pin)) { + pinMode(PIN_TO_PWM(pin), OUTPUT); + ledcAttachPin(PIN_TO_PWM(pin), 0); + ledcWrite(0, 0); + //analogWrite(PIN_TO_PWM(pin), 0); + return true; + } + return false; +} + +void AnalogOutputFirmata::handleCapability(byte pin) +{ + if (IS_PIN_PWM(pin)) { + Firmata.write(PIN_MODE_PWM); + Firmata.write(DEFAULT_PWM_RESOLUTION); + } +} + +boolean AnalogOutputFirmata::handleSysex(byte command, byte argc, byte* argv) +{ + if (command == EXTENDED_ANALOG) { + if (argc > 1) { + int val = argv[1]; + if (argc > 2) val |= (argv[2] << 7); + if (argc > 3) val |= (argv[3] << 14); + analogWriteCallback(argv[0], val); + return true; + } + return false; + } else { + return handleAnalogFirmataSysex(command, argc, argv); + } +} diff --git a/firmware/configurable_firmata/src/AnalogOutputFirmata.h b/firmware/configurable_firmata/src/AnalogOutputFirmata.h new file mode 100644 index 0000000..cf1761e --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogOutputFirmata.h @@ -0,0 +1,38 @@ +/* + AnalogFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef AnalogOutputFirmata_h +#define AnalogOutputFirmata_h + +#include +#include "FirmataFeature.h" + +// analogWriteCallback is defined in AnalogWrite.h but must also be declared here in order +// for AnalogOutputFirmata to compile +void analogWriteCallback(byte pin, int value); + +class AnalogOutputFirmata: public FirmataFeature +{ + public: + AnalogOutputFirmata(); + void handleCapability(byte pin); + boolean handlePinMode(byte pin, int mode); + boolean handleSysex(byte command, byte argc, byte* argv); + void reset(); + private: +}; + +#endif diff --git a/firmware/configurable_firmata/src/AnalogWrite.h b/firmware/configurable_firmata/src/AnalogWrite.h new file mode 100644 index 0000000..08d695a --- /dev/null +++ b/firmware/configurable_firmata/src/AnalogWrite.h @@ -0,0 +1,53 @@ +/* + AnalogWrite.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: November 15th, 2015 +*/ + +#ifndef AnalogWrite_h +#define AnalogWrite_h + +#include + +#if defined AnalogOutputFirmata_h || defined ServoFirmata_h + +void analogWriteCallback(byte pin, int value) +{ + if (pin < TOTAL_PINS) { + switch (Firmata.getPinMode(pin)) { +#ifdef ServoFirmata_h + case PIN_MODE_SERVO: + if (IS_PIN_SERVO(pin)) { + servoAnalogWrite(pin, value); + Firmata.setPinState(pin, value); + } + break; +#endif +#ifdef AnalogOutputFirmata_h + case PIN_MODE_PWM: + if (IS_PIN_PWM(pin)) { + ledcAttachPin(PIN_TO_PWM(pin), 0); + ledcWrite(0, value); + //analogWrite(PIN_TO_PWM(pin), value); + Firmata.setPinState(pin, value); + } + break; +#endif + } + } +} + +#endif +#endif diff --git a/firmware/configurable_firmata/src/ConfigurableFirmata.cpp b/firmware/configurable_firmata/src/ConfigurableFirmata.cpp new file mode 100644 index 0000000..b2746bf --- /dev/null +++ b/firmware/configurable_firmata/src/ConfigurableFirmata.cpp @@ -0,0 +1,710 @@ +/* + ConfigurableFirmata.pp - ConfigurableFirmata library v2.10.1 - 2017-6-2 + Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (c) 2013 Norbert Truchsess. All rights reserved. + Copyright (c) 2013-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +//****************************************************************************** +//* Includes +//****************************************************************************** + +#include "ConfigurableFirmata.h" +#include "HardwareSerial.h" + +extern "C" { +#include +#include +} + +//****************************************************************************** +//* Support Functions +//****************************************************************************** + +/** + * Split a 16-bit byte into two 7-bit values and write each value. + * @param value The 16-bit value to be split and written separately. + */ +void FirmataClass::sendValueAsTwo7bitBytes(int value) +{ + FirmataStream->write(value & B01111111); // LSB + FirmataStream->write(value >> 7 & B01111111); // MSB +} + +/** + * A helper method to write the beginning of a Sysex message transmission. + */ +void FirmataClass::startSysex(void) +{ + FirmataStream->write(START_SYSEX); +} + +/** + * A helper method to write the end of a Sysex message transmission. + */ +void FirmataClass::endSysex(void) +{ + FirmataStream->write(END_SYSEX); +} + +//****************************************************************************** +//* Constructors +//****************************************************************************** + +/** + * The Firmata class. + * An instance named "Firmata" is created automatically for the user. + */ +FirmataClass::FirmataClass() +{ + firmwareVersionCount = 0; + firmwareVersionVector = 0; + blinkVersionDisabled = false; + systemReset(); +} + +//****************************************************************************** +//* Public Methods +//****************************************************************************** + +/** + * Initialize the default Serial transport at the default baud of 57600. + */ +void FirmataClass::begin(void) +{ + begin(57600); +} + +/** + * Initialize the default Serial transport and override the default baud. + * Sends the protocol version to the host application followed by the firmware version and name. + * blinkVersion is also called. To skip the call to blinkVersion, call Firmata.disableBlinkVersion() + * before calling Firmata.begin(baud). + * @param speed The baud to use. 57600 baud is the default value. + */ +void FirmataClass::begin(long speed) +{ + Serial.begin(speed); + FirmataStream = &Serial; + blinkVersion(); + printVersion(); // send the protocol version + printFirmwareVersion(); // send the firmware name and version +} + +/** + * Reassign the Firmata stream transport. + * @param s A reference to the Stream transport object. This can be any type of + * transport that implements the Stream interface. Some examples include Ethernet, WiFi + * and other UARTs on the board (Serial1, Serial2, etc). + */ +void FirmataClass::begin(Stream &s) +{ + FirmataStream = &s; + // do not call blinkVersion() here because some hardware such as the + // Ethernet shield use pin 13 + printVersion(); // send the protocol version + printFirmwareVersion(); // send the firmware name and version +} + +/** + * Send the Firmata protocol version to the Firmata host application. + */ +void FirmataClass::printVersion(void) +{ + FirmataStream->write(REPORT_VERSION); + FirmataStream->write(FIRMATA_PROTOCOL_MAJOR_VERSION); + FirmataStream->write(FIRMATA_PROTOCOL_MINOR_VERSION); +} + +/** + * Blink the Firmata protocol version to the onboard LEDs (if the board has an onboard LED). + * If VERSION_BLINK_PIN is not defined in Boards.h for a particular board, then this method + * does nothing. + * The first series of flashes indicates the firmware major version (2 flashes = 2). + * The second series of flashes indicates the firmware minor version (5 flashes = 5). + */ +void FirmataClass::blinkVersion(void) +{ +#if defined(VERSION_BLINK_PIN) + if (blinkVersionDisabled) return; + // flash the pin with the protocol version + pinMode(VERSION_BLINK_PIN, OUTPUT); + strobeBlinkPin(VERSION_BLINK_PIN, FIRMATA_FIRMWARE_MAJOR_VERSION, 40, 210); + delay(250); + strobeBlinkPin(VERSION_BLINK_PIN, FIRMATA_FIRMWARE_MINOR_VERSION, 40, 210); + delay(125); +#endif +} + +/** + * Provides a means to disable the version blink sequence on the onboard LED, trimming startup + * time by a couple of seconds. + * Call this before Firmata.begin(). It only applies when using the default Serial transport. + */ +void FirmataClass::disableBlinkVersion() +{ + blinkVersionDisabled = true; +} + +/** + * Sends the firmware name and version to the Firmata host application. The major and minor version + * numbers are the first 2 bytes in the message. The following bytes are the characters of the + * firmware name. + */ +void FirmataClass::printFirmwareVersion(void) +{ + byte i; + + if (firmwareVersionCount) { // make sure that the name has been set before reporting + startSysex(); + FirmataStream->write(REPORT_FIRMWARE); + FirmataStream->write(firmwareVersionVector[0]); // major version number + FirmataStream->write(firmwareVersionVector[1]); // minor version number + for (i = 2; i < firmwareVersionCount; ++i) { + sendValueAsTwo7bitBytes(firmwareVersionVector[i]); + } + endSysex(); + } +} + +/** + * Sets the name and version of the firmware. This is not the same version as the Firmata protocol + * (although at times the firmware version and protocol version may be the same number). + * @param name A pointer to the name char array + * @param major The major version number + * @param minor The minor version number + */ +void FirmataClass::setFirmwareNameAndVersion(const char *name, byte major, byte minor) +{ + const char *firmwareName; + const char *extension; + + // parse out ".cpp" and "applet/" that comes from using __FILE__ + extension = strstr(name, ".cpp"); + firmwareName = strrchr(name, '/'); + + if (!firmwareName) { + // windows + firmwareName = strrchr(name, '\\'); + } + if (!firmwareName) { + // user passed firmware name + firmwareName = name; + } else { + firmwareName ++; + } + + if (!extension) { + firmwareVersionCount = strlen(firmwareName) + 2; + } else { + firmwareVersionCount = extension - firmwareName + 2; + } + + // in case anyone calls setFirmwareNameAndVersion more than once + free(firmwareVersionVector); + + firmwareVersionVector = (byte *) malloc(firmwareVersionCount + 1); + firmwareVersionVector[firmwareVersionCount] = 0; + firmwareVersionVector[0] = major; + firmwareVersionVector[1] = minor; + strncpy((char *)firmwareVersionVector + 2, firmwareName, firmwareVersionCount - 2); +} + +//------------------------------------------------------------------------------ +// Input Stream Handling + +/** + * A wrapper for Stream::available() + * @return The number of bytes remaining in the input stream buffer. + */ +int FirmataClass::available(void) +{ + return FirmataStream->available(); +} + +/** + * Process incoming sysex messages. Handles REPORT_FIRMWARE and STRING_DATA internally. + * Calls callback function for STRING_DATA and all other sysex messages. + * @private + */ +void FirmataClass::processSysexMessage(void) +{ + switch (storedInputData[0]) { //first byte in buffer is command + case REPORT_FIRMWARE: + printFirmwareVersion(); + break; + case STRING_DATA: + if (currentStringCallback) { + byte bufferLength = (sysexBytesRead - 1) / 2; + byte i = 1; + byte j = 0; + while (j < bufferLength) { + // The string length will only be at most half the size of the + // stored input buffer so we can decode the string within the buffer. + storedInputData[j] = storedInputData[i]; + i++; + storedInputData[j] += (storedInputData[i] << 7); + i++; + j++; + } + // Make sure string is null terminated. This may be the case for data + // coming from client libraries in languages that don't null terminate + // strings. + if (storedInputData[j - 1] != '\0') { + storedInputData[j] = '\0'; + } + (*currentStringCallback)((char *)&storedInputData[0]); + } + break; + default: + if (currentSysexCallback) + (*currentSysexCallback)(storedInputData[0], sysexBytesRead - 1, storedInputData + 1); + } +} + + +/** + * Read a single int from the input stream. If the value is not = -1, pass it on to parse(byte) + */ +void FirmataClass::processInput(void) +{ + int inputData = FirmataStream->read(); // this is 'int' to handle -1 when no data + if (inputData != -1) { + parse(inputData); + } +} + +/** + * Parse data from the input stream. + * @param inputData A single byte to be added to the parser. + */ +void FirmataClass::parse(byte inputData) +{ + int command; + + // TODO make sure it handles -1 properly + + if (parsingSysex) { + if (inputData == END_SYSEX) { + //stop sysex byte + parsingSysex = false; + //fire off handler function + processSysexMessage(); + } else { + //normal data byte - add to buffer + storedInputData[sysexBytesRead] = inputData; + sysexBytesRead++; + } + } else if ( (waitForData > 0) && (inputData < 128) ) { + waitForData--; + storedInputData[waitForData] = inputData; + if ( (waitForData == 0) && executeMultiByteCommand ) { // got the whole message + switch (executeMultiByteCommand) { + case ANALOG_MESSAGE: + if (currentAnalogCallback) { + (*currentAnalogCallback)(multiByteChannel, + (storedInputData[0] << 7) + + storedInputData[1]); + } + break; + case DIGITAL_MESSAGE: + if (currentDigitalCallback) { + (*currentDigitalCallback)(multiByteChannel, + (storedInputData[0] << 7) + + storedInputData[1]); + } + break; + case SET_PIN_MODE: + setPinMode(storedInputData[1], storedInputData[0]); + break; + case SET_DIGITAL_PIN_VALUE: + if (currentPinValueCallback) + (*currentPinValueCallback)(storedInputData[1], storedInputData[0]); + break; + case REPORT_ANALOG: + if (currentReportAnalogCallback) + (*currentReportAnalogCallback)(multiByteChannel, storedInputData[0]); + break; + case REPORT_DIGITAL: + if (currentReportDigitalCallback) + (*currentReportDigitalCallback)(multiByteChannel, storedInputData[0]); + break; + } + executeMultiByteCommand = 0; + } + } else { + // remove channel info from command byte if less than 0xF0 + if (inputData < 0xF0) { + command = inputData & 0xF0; + multiByteChannel = inputData & 0x0F; + } else { + command = inputData; + // commands in the 0xF* range don't use channel data + } + switch (command) { + case ANALOG_MESSAGE: + case DIGITAL_MESSAGE: + case SET_PIN_MODE: + case SET_DIGITAL_PIN_VALUE: + waitForData = 2; // two data bytes needed + executeMultiByteCommand = command; + break; + case REPORT_ANALOG: + case REPORT_DIGITAL: + waitForData = 1; // one data byte needed + executeMultiByteCommand = command; + break; + case START_SYSEX: + parsingSysex = true; + sysexBytesRead = 0; + break; + case SYSTEM_RESET: + systemReset(); + break; + case REPORT_VERSION: + Firmata.printVersion(); + break; + } + } +} + +/** + * @return Returns true if the parser is actively parsing data. + */ +boolean FirmataClass::isParsingMessage(void) +{ + return (waitForData > 0 || parsingSysex); +} + +/** + * @return Returns true if the SYSTEM_RESET message is being executed + */ +boolean FirmataClass::isResetting(void) +{ + return resetting; +} + +//------------------------------------------------------------------------------ +// Output Stream Handling + +/** + * Send an analog message to the Firmata host application. The range of pins is limited to [0..15] + * when using the ANALOG_MESSAGE. The maximum value of the ANALOG_MESSAGE is limited to 14 bits + * (16384). To increase the pin range or value, see the documentation for the EXTENDED_ANALOG + * message. + * @param pin The analog pin to send the value of (limited to pins 0 - 15). + * @param value The value of the analog pin (0 - 1024 for 10-bit analog, 0 - 4096 for 12-bit, etc). + * The maximum value is 14-bits (16384). + */ +void FirmataClass::sendAnalog(byte pin, int value) +{ + // pin can only be 0-15, so chop higher bits + FirmataStream->write(ANALOG_MESSAGE | (pin & 0xF)); + sendValueAsTwo7bitBytes(value); +} + +/* (intentionally left out asterix here) + * STUB - NOT IMPLEMENTED + * Send a single digital pin value to the Firmata host application. + * @param pin The digital pin to send the value of. + * @param value The value of the pin. + */ +void FirmataClass::sendDigital(byte pin, int value) +{ + /* TODO add single pin digital messages to the protocol, this needs to + * track the last digital data sent so that it can be sure to change just + * one bit in the packet. This is complicated by the fact that the + * numbering of the pins will probably differ on Arduino, Wiring, and + * other boards. The DIGITAL_MESSAGE sends 14 bits at a time, but it is + * probably easier to send 8 bit ports for any board with more than 14 + * digital pins. + */ + + // TODO: the digital message should not be sent on the serial port every + // time sendDigital() is called. Instead, it should add it to an int + // which will be sent on a schedule. If a pin changes more than once + // before the digital message is sent on the serial port, it should send a + // digital message for each change. + + // if(value == 0) + // sendDigitalPortPair(); +} + + +/** + * Send an 8-bit port in a single digital message (protocol v2 and later). + * Send 14-bits in a single digital message (protocol v1). + * @param portNumber The port number to send. Note that this is not the same as a "port" on the + * physical microcontroller. Ports are defined in order per every 8 pins in ascending order + * of the Arduino digital pin numbering scheme. Port 0 = pins D0 - D7, port 1 = pins D8 - D15, etc. + * @param portData The value of the port. The value of each pin in the port is represented by a bit. + */ +void FirmataClass::sendDigitalPort(byte portNumber, int portData) +{ + FirmataStream->write(DIGITAL_MESSAGE | (portNumber & 0xF)); + FirmataStream->write((byte)portData % 128); // Tx bits 0-6 + FirmataStream->write(portData >> 7); // Tx bits 7-13 +} + +/** + * Send a sysex message where all values after the command byte are packet as 2 7-bit bytes + * (this is not always the case so this function is not always used to send sysex messages). + * @param command The sysex command byte. + * @param bytec The number of data bytes in the message (excludes start, command and end bytes). + * @param bytev A pointer to the array of data bytes to send in the message. + */ +void FirmataClass::sendSysex(byte command, byte bytec, byte *bytev) +{ + byte i; + startSysex(); + FirmataStream->write(command); + for (i = 0; i < bytec; i++) { + sendValueAsTwo7bitBytes(bytev[i]); + } + endSysex(); +} + +/** + * Send a string to the Firmata host application. + * @param command Must be STRING_DATA + * @param string A pointer to the char string + */ +void FirmataClass::sendString(byte command, const char *string) +{ + sendSysex(command, strlen(string), (byte *)string); +} + +/** + * Send a string to the Firmata host application. + * @param string A pointer to the char string + */ +void FirmataClass::sendString(const char *string) +{ + sendString(STRING_DATA, string); +} + +/** + * A wrapper for Stream::available(). + * Write a single byte to the output stream. + * @param c The byte to be written. + */ +void FirmataClass::write(byte c) +{ + FirmataStream->write(c); +} + + +/** + * Attach a generic sysex callback function to a command (options are: ANALOG_MESSAGE, + * DIGITAL_MESSAGE, REPORT_ANALOG, REPORT DIGITAL, SET_PIN_MODE and SET_DIGITAL_PIN_VALUE). + * @param command The ID of the command to attach a callback function to. + * @param newFunction A reference to the callback function to attach. + */ +void FirmataClass::attach(byte command, callbackFunction newFunction) +{ + switch (command) { + case ANALOG_MESSAGE: currentAnalogCallback = newFunction; break; + case DIGITAL_MESSAGE: currentDigitalCallback = newFunction; break; + case REPORT_ANALOG: currentReportAnalogCallback = newFunction; break; + case REPORT_DIGITAL: currentReportDigitalCallback = newFunction; break; + case SET_PIN_MODE: currentPinModeCallback = newFunction; break; + case SET_DIGITAL_PIN_VALUE: currentPinValueCallback = newFunction; break; + } +} + +/** + * Attach a callback function for the SYSTEM_RESET command. + * @param command Must be set to SYSTEM_RESET or it will be ignored. + * @param newFunction A reference to the system reset callback function to attach. + */ +void FirmataClass::attach(byte command, systemResetCallbackFunction newFunction) +{ + switch (command) { + case SYSTEM_RESET: currentSystemResetCallback = newFunction; break; + } +} + +/** + * Attach a callback function for the STRING_DATA command. + * @param command Must be set to STRING_DATA or it will be ignored. + * @param newFunction A reference to the string callback function to attach. + */ +void FirmataClass::attach(byte command, stringCallbackFunction newFunction) +{ + switch (command) { + case STRING_DATA: currentStringCallback = newFunction; break; + } +} + +/** + * Attach a generic sysex callback function to sysex command. + * @param command The ID of the command to attach a callback function to. + * @param newFunction A reference to the sysex callback function to attach. + */ +void FirmataClass::attach(byte command, sysexCallbackFunction newFunction) +{ + currentSysexCallback = newFunction; +} + +/** + * Detach a callback function for a specified command (such as SYSTEM_RESET, STRING_DATA, + * ANALOG_MESSAGE, DIGITAL_MESSAGE, etc). + * @param command The ID of the command to detatch the callback function from. + */ +void FirmataClass::detach(byte command) +{ + switch (command) { + case SYSTEM_RESET: currentSystemResetCallback = NULL; break; + case STRING_DATA: currentStringCallback = NULL; break; + case START_SYSEX: currentSysexCallback = NULL; break; + default: + attach(command, (callbackFunction)NULL); + } +} + +/** + * Detach a callback function for a delayed task when using FirmataScheduler + * @see FirmataScheduler + * @param newFunction A reference to the delay task callback function to attach. + */ +void FirmataClass::attachDelayTask(delayTaskCallbackFunction newFunction) +{ + delayTaskCallback = newFunction; +} + +/** + * Call the delayTask callback function when using FirmataScheduler. Must first attach a callback + * using attachDelayTask. + * @see FirmataScheduler + * @param delay The amount of time to delay in milliseconds. + */ +void FirmataClass::delayTask(long delay) +{ + if (delayTaskCallback) { + (*delayTaskCallback)(delay); + } +} + +/** + * @param pin The pin to get the configuration of. + * @return The configuration of the specified pin. + */ +byte FirmataClass::getPinMode(byte pin) +{ + return pinConfig[pin]; +} + +/** + * Set the pin mode/configuration. The pin configuration (or mode) in Firmata represents the + * current function of the pin. Examples are digital input or output, analog input, pwm, i2c, + * serial (uart), etc. + * @param pin The pin to configure. + * @param config The configuration value for the specified pin. + */ +void FirmataClass::setPinMode(byte pin, byte config) +{ + if (pinConfig[pin] == PIN_MODE_IGNORE) + return; + pinState[pin] = 0; + pinConfig[pin] = config; + if (currentPinModeCallback) + (*currentPinModeCallback)(pin, config); +} + +/** + * @param pin The pin to get the state of. + * @return The state of the specified pin. + */ +int FirmataClass::getPinState(byte pin) +{ + return pinState[pin]; +} + +/** + * Set the pin state. The pin state of an output pin is the pin value. The state of an + * input pin is 0, unless the pin has it's internal pull up resistor enabled, then the value is 1. + * @param pin The pin to set the state of + * @param state Set the state of the specified pin + */ +void FirmataClass::setPinState(byte pin, int state) +{ + pinState[pin] = state; +} + + +// sysex callbacks +/* + * this is too complicated for analogReceive, but maybe for Sysex? + void FirmataClass::attachSysex(sysexFunction newFunction) + { + byte i; + byte tmpCount = analogReceiveFunctionCount; + analogReceiveFunction* tmpArray = analogReceiveFunctionArray; + analogReceiveFunctionCount++; + analogReceiveFunctionArray = (analogReceiveFunction*) calloc(analogReceiveFunctionCount, sizeof(analogReceiveFunction)); + for(i = 0; i < tmpCount; i++) { + analogReceiveFunctionArray[i] = tmpArray[i]; + } + analogReceiveFunctionArray[tmpCount] = newFunction; + free(tmpArray); + } +*/ + +//****************************************************************************** +//* Private Methods +//****************************************************************************** + +/** + * Resets the system state upon a SYSTEM_RESET message from the host software. + * @private + */ +void FirmataClass::systemReset(void) +{ + resetting = true; + byte i; + + waitForData = 0; // this flag says the next serial input will be data + executeMultiByteCommand = 0; // execute this after getting multi-byte data + multiByteChannel = 0; // channel data for multiByteCommands + + for (i = 0; i < MAX_DATA_BYTES; i++) { + storedInputData[i] = 0; + } + + parsingSysex = false; + sysexBytesRead = 0; + + if (currentSystemResetCallback) + (*currentSystemResetCallback)(); + + resetting = false; +} + +/** + * Flashing the pin for the version number + * @private + * @param pin The pin the LED is attached to. + * @param count The number of times to flash the LED. + * @param onInterval The number of milliseconds for the LED to be ON during each interval. + * @param offInterval The number of milliseconds for the LED to be OFF during each interval. + */ +void FirmataClass::strobeBlinkPin(byte pin, int count, int onInterval, int offInterval) +{ + byte i; + for (i = 0; i < count; i++) { + delay(offInterval); + digitalWrite(pin, HIGH); + delay(onInterval); + digitalWrite(pin, LOW); + } +} + +// make one instance for the user to use +FirmataClass Firmata; diff --git a/firmware/configurable_firmata/src/ConfigurableFirmata.h b/firmware/configurable_firmata/src/ConfigurableFirmata.h new file mode 100644 index 0000000..c3940b8 --- /dev/null +++ b/firmware/configurable_firmata/src/ConfigurableFirmata.h @@ -0,0 +1,241 @@ +/* + ConfigurableFirmata.h - ConfigurableFirmata library v2.10.1 - 2018-6-2 + Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (c) 2013 Norbert Truchsess. All rights reserved. + Copyright (c) 2013-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef Configurable_Firmata_h +#define Configurable_Firmata_h + +#include "utility/Boards.h" /* Hardware Abstraction Layer + Wiring/Arduino */ + +/* Version numbers for the protocol. The protocol is still changing, so these + * version numbers are important. + * Query using the REPORT_VERSION message. + */ +#define FIRMATA_PROTOCOL_MAJOR_VERSION 2 // for non-compatible changes +#define FIRMATA_PROTOCOL_MINOR_VERSION 6 // for backwards compatible changes +#define FIRMATA_PROTOCOL_BUGFIX_VERSION 0 // for bugfix releases + +/* + * Version numbers for the Firmata library. + * ConfigurableFirmata 2.10.1 implements version 2.6.0 of the Firmata protocol. + * The firmware version will not always equal the protocol version going forward. + * Query using the REPORT_FIRMWARE message. + */ +#define FIRMATA_FIRMWARE_MAJOR_VERSION 2 // for non-compatible changes +#define FIRMATA_FIRMWARE_MINOR_VERSION 10 // for backwards compatible changes +#define FIRMATA_FIRMWARE_BUGFIX_VERSION 1 // for bugfix releases + +// DEPRECATED as of ConfigurableFirmata v2.8.1. +// Use FIRMATA_PROTOCOL_[MAJOR|MINOR|BUGFIX]_VERSION instead. +#define FIRMATA_MAJOR_VERSION 2 +#define FIRMATA_MINOR_VERSION 6 +#define FIRMATA_BUGFIX_VERSION 0 +// DEPRECATED as of ConfigurableFirmata v2.8.1. +//Use FIRMATA_FIRMWARE_[MAJOR|MINOR|BUGFIX]_VERSION instead. +#define FIRMWARE_MAJOR_VERSION 2 +#define FIRMWARE_MINOR_VERSION 10 +#define FIRMWARE_BUGFIX_VERSION 1 + +#define MAX_DATA_BYTES 64 // max number of data bytes in incoming messages + +// Arduino 101 also defines SET_PIN_MODE as a macro in scss_registers.h +#ifdef SET_PIN_MODE +#undef SET_PIN_MODE +#endif + +// message command bytes (128-255/0x80-0xFF) +#define DIGITAL_MESSAGE 0x90 // send data for a digital pin +#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 pair +// +#define SET_PIN_MODE 0xF4 // set a pin to INPUT/OUTPUT/PWM/etc +#define SET_DIGITAL_PIN_VALUE 0xF5 // set value of an individual digital pin +// +#define REPORT_VERSION 0xF9 // report protocol version +#define SYSTEM_RESET 0xFF // reset from MIDI +// +#define START_SYSEX 0xF0 // start a MIDI Sysex message +#define END_SYSEX 0xF7 // end a MIDI Sysex message + +// extended command set using sysex (0-127/0x00-0x7F) +/* 0x00-0x0F reserved for user-defined commands */ +#define SERIAL_MESSAGE 0x60 // communicate with serial devices, including other boards +#define ENCODER_DATA 0x61 // reply with encoders current positions +#define ACCELSTEPPER_DATA 0x62 // control a stepper motor +#define SERVO_CONFIG 0x70 // set max angle, minPulse, maxPulse, freq +#define STRING_DATA 0x71 // a string message with 14-bits per char +#define STEPPER_DATA 0x72 // control a stepper motor +#define ONEWIRE_DATA 0x73 // send an OneWire read/write/reset/select/skip/search request +#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 SCHEDULER_DATA 0x7B // send a createtask/deletetask/addtotask/schedule/querytasks/querytask request to the scheduler +#define SYSEX_NON_REALTIME 0x7E // MIDI Reserved for non-realtime messages +#define SYSEX_REALTIME 0x7F // MIDI Reserved for realtime messages + +// these are DEPRECATED to make the naming more consistent +#define FIRMATA_STRING 0x71 // same as STRING_DATA +#define SYSEX_I2C_REQUEST 0x76 // same as I2C_REQUEST +#define SYSEX_I2C_REPLY 0x77 // same as I2C_REPLY +#define SYSEX_SAMPLING_INTERVAL 0x7A // same as SAMPLING_INTERVAL + +// pin modes +//#define INPUT 0x00 // defined in Arduino.h +//#define OUTPUT 0x01 // defined in Arduino.h +#define PIN_MODE_ANALOG 0x02 // analog pin in analogInput mode +#define PIN_MODE_PWM 0x03 // digital pin in PWM output mode +#define PIN_MODE_SERVO 0x04 // digital pin in Servo output mode +#define PIN_MODE_SHIFT 0x05 // shiftIn/shiftOut mode +#define PIN_MODE_I2C 0x06 // pin included in I2C setup +#define PIN_MODE_ONEWIRE 0x07 // pin configured for 1-wire +#define PIN_MODE_STEPPER 0x08 // pin configured for stepper motor +#define PIN_MODE_ENCODER 0x09 // pin configured for rotary encoders +#define PIN_MODE_SERIAL 0x0A // pin configured for serial communication +#define PIN_MODE_PULLUP 0x0B // enable internal pull-up resistor for pin +#define PIN_MODE_IGNORE 0x7F // pin configured to be ignored by digitalWrite and capabilityResponse +#define TOTAL_PIN_MODES 13 +// DEPRECATED as of Firmata v2.5 +#define ANALOG 0x02 // same as PIN_MODE_ANALOG +#define PWM 0x03 // same as PIN_MODE_PWM +#define SERVO 0x04 // same as PIN_MODE_SERVO +#define SHIFT 0x05 // same as PIN_MODE_SHIFT +#define I2C 0x06 // same as PIN_MODE_I2C +#define ONEWIRE 0x07 // same as PIN_MODE_ONEWIRE +#define STEPPER 0x08 // same as PIN_MODE_STEPPER +#define ENCODER 0x09 // same as PIN_MODE_ENCODER +#define IGNORE 0x7F // same as PIN_MODE_IGNORE + +extern "C" { + // callback function types + typedef void (*callbackFunction)(byte, int); + typedef void (*systemResetCallbackFunction)(void); + typedef void (*stringCallbackFunction)(char *); + typedef void (*sysexCallbackFunction)(byte command, byte argc, byte *argv); + typedef void (*delayTaskCallbackFunction)(long delay); +} + + +// TODO make it a subclass of a generic Serial/Stream base class +class FirmataClass +{ + public: + FirmataClass(); + /* Arduino constructors */ + void begin(); + void begin(long); + void begin(Stream &s); + /* querying functions */ + void printVersion(void); + void blinkVersion(void); + void printFirmwareVersion(void); + //void setFirmwareVersion(byte major, byte minor); // see macro below + void setFirmwareNameAndVersion(const char *name, byte major, byte minor); + void disableBlinkVersion(); + /* serial receive handling */ + int available(void); + void processInput(void); + void parse(unsigned char value); + boolean isParsingMessage(void); + boolean isResetting(void); + /* serial send handling */ + void sendAnalog(byte pin, int value); + void sendDigital(byte pin, int value); // TODO implement this + void sendDigitalPort(byte portNumber, int portData); + void sendString(const char *string); + void sendString(byte command, const char *string); + void sendSysex(byte command, byte bytec, byte *bytev); + void write(byte c); + /* attach & detach callback functions to messages */ + void attach(byte command, callbackFunction newFunction); + void attach(byte command, systemResetCallbackFunction newFunction); + void attach(byte command, stringCallbackFunction newFunction); + void attach(byte command, sysexCallbackFunction newFunction); + void detach(byte command); + /* delegate to Scheduler (if any) */ + void attachDelayTask(delayTaskCallbackFunction newFunction); + void delayTask(long delay); + /* access pin config */ + byte getPinMode(byte pin); + void setPinMode(byte pin, byte config); + /* access pin state */ + int getPinState(byte pin); + void setPinState(byte pin, int state); + + /* utility methods */ + void sendValueAsTwo7bitBytes(int value); + void startSysex(void); + void endSysex(void); + + private: + Stream *FirmataStream; + /* firmware name and version */ + byte firmwareVersionCount; + byte *firmwareVersionVector; + /* input message handling */ + byte waitForData; // this flag says the next serial input will be data + byte executeMultiByteCommand; // execute this after getting multi-byte data + byte multiByteChannel; // channel data for multiByteCommands + byte storedInputData[MAX_DATA_BYTES]; // multi-byte data + /* sysex */ + boolean parsingSysex; + int sysexBytesRead; + /* pins configuration */ + byte pinConfig[TOTAL_PINS]; // configuration of every pin + int pinState[TOTAL_PINS]; // any value that has been written + + boolean resetting; + + /* callback functions */ + callbackFunction currentAnalogCallback; + callbackFunction currentDigitalCallback; + callbackFunction currentReportAnalogCallback; + callbackFunction currentReportDigitalCallback; + callbackFunction currentPinModeCallback; + callbackFunction currentPinValueCallback; + systemResetCallbackFunction currentSystemResetCallback; + stringCallbackFunction currentStringCallback; + sysexCallbackFunction currentSysexCallback; + delayTaskCallbackFunction delayTaskCallback; + + boolean blinkVersionDisabled; + + /* private methods ------------------------------ */ + void processSysexMessage(void); + void systemReset(void); + void strobeBlinkPin(byte pin, int count, int onInterval, int offInterval); +}; + +extern FirmataClass Firmata; + +/*============================================================================== + * MACROS + *============================================================================*/ + +/* shortcut for setFirmwareNameAndVersion() that uses __FILE__ to set the + * firmware name. It needs to be a macro so that __FILE__ is included in the + * firmware source file rather than the library source file. + */ +#define setFirmwareVersion(x, y) setFirmwareNameAndVersion(__FILE__, x, y) + +#endif /* Configurable_Firmata_h */ diff --git a/firmware/configurable_firmata/src/DigitalInputFirmata.cpp b/firmware/configurable_firmata/src/DigitalInputFirmata.cpp new file mode 100644 index 0000000..565066d --- /dev/null +++ b/firmware/configurable_firmata/src/DigitalInputFirmata.cpp @@ -0,0 +1,127 @@ +/* + DigitalInputFirmata.cpp - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: November 15th, 2015 +*/ + +#include +#include "DigitalInputFirmata.h" + +DigitalInputFirmata *DigitalInputFirmataInstance; + +void reportDigitalInputCallback(byte port, int value) +{ + DigitalInputFirmataInstance->reportDigital(port, value); +} + +DigitalInputFirmata::DigitalInputFirmata() +{ + DigitalInputFirmataInstance = this; + Firmata.attach(REPORT_DIGITAL, reportDigitalInputCallback); +} + +boolean DigitalInputFirmata::handleSysex(byte command, byte argc, byte* argv) +{ + return false; +} + +void DigitalInputFirmata::outputPort(byte portNumber, byte portValue, byte forceSend) +{ + // pins not configured as INPUT are cleared to zeros + portValue = portValue & portConfigInputs[portNumber]; + // only send if the value is different than previously sent + if (forceSend || previousPINs[portNumber] != portValue) { + Firmata.sendDigitalPort(portNumber, portValue); + previousPINs[portNumber] = portValue; + } +} + +/* ----------------------------------------------------------------------------- + * check all the active digital inputs for change of state, then add any events + * to the Serial output queue using Serial.print() */ +void DigitalInputFirmata::report(void) +{ + /* Using non-looping code allows constants to be given to readPort(). + * The compiler will apply substantial optimizations if the inputs + * to readPort() are compile-time constants. */ + if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false); + if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false); + if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false); + if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false); + if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false); + if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false); + if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false); + if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false); + if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false); + if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false); + if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false); + if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false); + if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false); + if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false); + if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false); + if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false); +} + +void DigitalInputFirmata::reportDigital(byte port, int value) +{ + if (port < TOTAL_PORTS) { + reportPINs[port] = (byte)value; + if (value) outputPort(port, readPort(port, portConfigInputs[port]), true); + } + // do not disable analog reporting on these 8 pins, to allow some + // pins used for digital, others analog. Instead, allow both types + // of reporting to be enabled, but check if the pin is configured + // as analog when sampling the analog inputs. Likewise, while + // scanning digital pins, portConfigInputs will mask off values from any + // pins configured as analog +} + +boolean DigitalInputFirmata::handlePinMode(byte pin, int mode) +{ + if (IS_PIN_DIGITAL(pin)) { + if (mode == INPUT || mode == PIN_MODE_PULLUP) { + portConfigInputs[pin / 8] |= (1 << (pin & 7)); + if (mode == INPUT) { + pinMode(PIN_TO_DIGITAL(pin), INPUT); + } else { + pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP); + Firmata.setPinState(pin, 1); + } + return true; + } else { + portConfigInputs[pin / 8] &= ~(1 << (pin & 7)); + } + } + return false; +} + +void DigitalInputFirmata::handleCapability(byte pin) +{ + if (IS_PIN_DIGITAL(pin)) { + Firmata.write((byte)INPUT); + Firmata.write(1); + Firmata.write((byte)PIN_MODE_PULLUP); + Firmata.write(1); + } +} + +void DigitalInputFirmata::reset() +{ + for (byte i = 0; i < TOTAL_PORTS; i++) { + reportPINs[i] = false; // by default, reporting off + portConfigInputs[i] = 0; // until activated + previousPINs[i] = 0; + } +} diff --git a/firmware/configurable_firmata/src/DigitalInputFirmata.h b/firmware/configurable_firmata/src/DigitalInputFirmata.h new file mode 100644 index 0000000..2c896b1 --- /dev/null +++ b/firmware/configurable_firmata/src/DigitalInputFirmata.h @@ -0,0 +1,46 @@ +/* + DigitalInputFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef DigitalInputFirmata_h +#define DigitalInputFirmata_h + +#include +#include "FirmataFeature.h" + +void reportDigitalInputCallback(byte port, int value); + +class DigitalInputFirmata: public FirmataFeature +{ + public: + DigitalInputFirmata(); + void reportDigital(byte port, int value); + void report(void); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte* argv); + boolean handlePinMode(byte pin, int mode); + void reset(); + + private: + /* digital input ports */ + byte reportPINs[TOTAL_PORTS]; // 1 = report this port, 0 = silence + byte previousPINs[TOTAL_PORTS]; // previous 8 bits sent + + /* pins configuration */ + byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else + void outputPort(byte portNumber, byte portValue, byte forceSend); +}; + +#endif diff --git a/firmware/configurable_firmata/src/DigitalOutputFirmata.cpp b/firmware/configurable_firmata/src/DigitalOutputFirmata.cpp new file mode 100644 index 0000000..a4448f0 --- /dev/null +++ b/firmware/configurable_firmata/src/DigitalOutputFirmata.cpp @@ -0,0 +1,106 @@ +/* + DigitalOutputFirmata.cpp - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: February 16th, 2016 +*/ + +#include +#include "DigitalOutputFirmata.h" + +DigitalOutputFirmata *DigitalOutputFirmataInstance; + +void digitalOutputWriteCallback(byte port, int value) +{ + DigitalOutputFirmataInstance->digitalWritePort(port, value); +} + +/* + * Sets the value of an individual pin. Useful if you want to set a pin value but + * are not tracking the digital port state. + * Can only be used on pins configured as OUTPUT. + * Cannot be used to enable pull-ups on Digital INPUT pins. + */ +void handleSetPinValueCallback(byte pin, int value) +{ + if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) { + if (Firmata.getPinMode(pin) == OUTPUT) { + digitalWrite(pin, value); + Firmata.setPinState(pin, value); + } + } +} + +DigitalOutputFirmata::DigitalOutputFirmata() +{ + DigitalOutputFirmataInstance = this; + Firmata.attach(DIGITAL_MESSAGE, digitalOutputWriteCallback); + Firmata.attach(SET_DIGITAL_PIN_VALUE, handleSetPinValueCallback); +} + +boolean DigitalOutputFirmata::handleSysex(byte command, byte argc, byte* argv) +{ + return false; +} + +void DigitalOutputFirmata::reset() +{ + +} + +void DigitalOutputFirmata::digitalWritePort(byte port, int value) +{ + byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0; + + if (port < TOTAL_PORTS) { + // create a mask of the pins on this port that are writable. + lastPin = port * 8 + 8; + if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS; + for (pin = port * 8; pin < lastPin; pin++) { + // do not disturb non-digital pins (eg, Rx & Tx) + if (IS_PIN_DIGITAL(pin)) { + // do not touch pins in PWM, ANALOG, SERVO or other modes + if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) { + pinValue = ((byte)value & mask) ? 1 : 0; + if (Firmata.getPinMode(pin) == OUTPUT) { + pinWriteMask |= mask; + } else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) { + pinMode(pin, INPUT_PULLUP); + } + Firmata.setPinState(pin, pinValue); + } + } + mask = mask << 1; + } + writePort(port, (byte)value, pinWriteMask); + } +} + +boolean DigitalOutputFirmata::handlePinMode(byte pin, int mode) +{ + if (IS_PIN_DIGITAL(pin) && mode == OUTPUT && Firmata.getPinMode(pin) != PIN_MODE_IGNORE) { + digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable PWM + pinMode(PIN_TO_DIGITAL(pin), OUTPUT); + return true; + } + return false; +} + +void DigitalOutputFirmata::handleCapability(byte pin) +{ + if (IS_PIN_DIGITAL(pin)) { + Firmata.write((byte)OUTPUT); + Firmata.write(1); + } +} diff --git a/firmware/configurable_firmata/src/DigitalOutputFirmata.h b/firmware/configurable_firmata/src/DigitalOutputFirmata.h new file mode 100644 index 0000000..dcd7979 --- /dev/null +++ b/firmware/configurable_firmata/src/DigitalOutputFirmata.h @@ -0,0 +1,40 @@ +/* + DigitalOutputFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: November 15th, 2015 +*/ + +#ifndef DigitalOutputFirmata_h +#define DigitalOutputFirmata_h + +#include +#include "FirmataFeature.h" + +void digitalOutputWriteCallback(byte port, int value); +void handleSetPinValueCallback(byte pin, int value); + +class DigitalOutputFirmata: public FirmataFeature +{ + public: + DigitalOutputFirmata(); + void digitalWritePort(byte port, int value); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte* argv); + boolean handlePinMode(byte pin, int mode); + void reset(); + private: +}; + +#endif diff --git a/firmware/configurable_firmata/src/Encoder7Bit.cpp b/firmware/configurable_firmata/src/Encoder7Bit.cpp new file mode 100644 index 0000000..c11c33c --- /dev/null +++ b/firmware/configurable_firmata/src/Encoder7Bit.cpp @@ -0,0 +1,64 @@ +/* + Encoder7Bit.cpp - Firmata library + Copyright (C) 2012-2013 Norbert Truchsess. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#include "Encoder7Bit.h" +#include + +Encoder7BitClass::Encoder7BitClass() +{ + previous = 0; + shift = 0; +} + +void Encoder7BitClass::startBinaryWrite() +{ + shift = 0; +} + +void Encoder7BitClass::endBinaryWrite() +{ + if (shift > 0) { + Firmata.write(previous); + } +} + +void Encoder7BitClass::writeBinary(byte data) +{ + if (shift == 0) { + Firmata.write(data & 0x7f); + shift++; + previous = data >> 7; + } + else { + Firmata.write(((data << shift) & 0x7f) | previous); + if (shift == 6) { + Firmata.write(data >> 1); + shift = 0; + } + else { + shift++; + previous = data >> (8 - shift); + } + } +} + +void Encoder7BitClass::readBinary(int outBytes, byte *inData, byte *outData) +{ + for (int i = 0; i < outBytes; i++) { + int j = i << 3; + int pos = j / 7; + byte shift = j % 7; + outData[i] = (inData[pos] >> shift) | ((inData[pos + 1] << (7 - shift)) & 0xFF); + } +} + +Encoder7BitClass Encoder7Bit; diff --git a/firmware/configurable_firmata/src/Encoder7Bit.h b/firmware/configurable_firmata/src/Encoder7Bit.h new file mode 100644 index 0000000..e3538b2 --- /dev/null +++ b/firmware/configurable_firmata/src/Encoder7Bit.h @@ -0,0 +1,35 @@ +/* + Encoder7Bit.h - Firmata library + Copyright (C) 2012-2013 Norbert Truchsess. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef Encoder7Bit_h +#define Encoder7Bit_h +#include + +#define num7BitOutbytes(a)(((a)*7)>>3) + +class Encoder7BitClass +{ + public: + Encoder7BitClass(); + void startBinaryWrite(); + void endBinaryWrite(); + void writeBinary(byte data); + void readBinary(int outBytes, byte *inData, byte *outData); + + private: + byte previous; + int shift; +}; + +extern Encoder7BitClass Encoder7Bit; + +#endif diff --git a/firmware/configurable_firmata/src/EthernetClientStream.cpp b/firmware/configurable_firmata/src/EthernetClientStream.cpp new file mode 100644 index 0000000..f4f8968 --- /dev/null +++ b/firmware/configurable_firmata/src/EthernetClientStream.cpp @@ -0,0 +1,3 @@ +/* + * Implementation is in EthernetClientStream.h to avoid linker issues. + */ diff --git a/firmware/configurable_firmata/src/EthernetClientStream.h b/firmware/configurable_firmata/src/EthernetClientStream.h new file mode 100644 index 0000000..f42c729 --- /dev/null +++ b/firmware/configurable_firmata/src/EthernetClientStream.h @@ -0,0 +1,141 @@ +/* + EthernetClientStream.h + An Arduino-Stream that wraps an instance of Client reconnecting to + the remote-ip in a transparent way. A disconnected client may be + recognized by the returnvalues -1 from calls to peek or read and + a 0 from calls to write. + + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated June 18th, 2016 + */ + +#ifndef ETHERNETCLIENTSTREAM_H +#define ETHERNETCLIENTSTREAM_H + +#include +#include + +//#define SERIAL_DEBUG +#include "utility/firmataDebug.h" + +#define MILLIS_RECONNECT 5000 + +class EthernetClientStream : public Stream +{ + public: + EthernetClientStream(Client &client, IPAddress localip, IPAddress ip, const char* host, uint16_t port); + int available(); + int read(); + int peek(); + void flush(); + size_t write(uint8_t); + void maintain(IPAddress localip); + + private: + Client &client; + IPAddress localip; + IPAddress ip; + const char* host; + uint16_t port; + bool connected; + uint32_t time_connect; + bool maintain(); + void stop(); +}; + + +/* + * EthernetClientStream.cpp + * Copied here as a hack to linker issues with 3rd party board packages that don't properly + * implement the Arduino network APIs. + */ +EthernetClientStream::EthernetClientStream(Client &client, IPAddress localip, IPAddress ip, const char* host, uint16_t port) + : client(client), + localip(localip), + ip(ip), + host(host), + port(port), + connected(false) +{ +} + +int +EthernetClientStream::available() +{ + return maintain() ? client.available() : 0; +} + +int +EthernetClientStream::read() +{ + return maintain() ? client.read() : -1; +} + +int +EthernetClientStream::peek() +{ + return maintain() ? client.peek() : -1; +} + +void EthernetClientStream::flush() +{ + if (maintain()) + client.flush(); +} + +size_t +EthernetClientStream::write(uint8_t c) +{ + return maintain() ? client.write(c) : 0; +} + +void +EthernetClientStream::maintain(IPAddress localip) +{ + // ensure the local IP is updated in the case that it is changed by the DHCP server + if (this->localip != localip) { + this->localip = localip; + if (connected) + stop(); + } +} + +void +EthernetClientStream::stop() +{ + client.stop(); + connected = false; + time_connect = millis(); +} + +bool +EthernetClientStream::maintain() +{ + if (client && client.connected()) + return true; + + if (connected) { + stop(); + } + // if the client is disconnected, attempt to reconnect every 5 seconds + else if (millis() - time_connect >= MILLIS_RECONNECT) { + connected = host ? client.connect(host, port) : client.connect(ip, port); + if (!connected) { + time_connect = millis(); + DEBUG_PRINTLN("connection failed. attempting to reconnect..."); + } else { + DEBUG_PRINTLN("connected"); + } + } + return connected; +} + +#endif /* ETHERNETCLIENTSTREAM_H */ diff --git a/firmware/configurable_firmata/src/FirmataExt.cpp b/firmware/configurable_firmata/src/FirmataExt.cpp new file mode 100644 index 0000000..d071ebc --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataExt.cpp @@ -0,0 +1,174 @@ +/* + FirmataExt.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + See file LICENSE.txt for further informations on licensing terms. + Last updated by Jeff Hoefs: November 15th, 2015 +*/ + +#include +#include "FirmataExt.h" + +FirmataExt *FirmataExtInstance; + +void handleSetPinModeCallback(byte pin, int mode) +{ + if (!FirmataExtInstance->handlePinMode(pin, mode) && mode != PIN_MODE_IGNORE) { + Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM + } +} + +void handleSysexCallback(byte command, byte argc, byte* argv) +{ + if (!FirmataExtInstance->handleSysex(command, argc, argv)) { + Firmata.sendString("Unhandled sysex command"); + } +} + +FirmataExt::FirmataExt() +{ + FirmataExtInstance = this; + Firmata.attach(SET_PIN_MODE, handleSetPinModeCallback); + Firmata.attach((byte)START_SYSEX, handleSysexCallback); + numFeatures = 0; +} + +void FirmataExt::handleCapability(byte pin) +{ + +} + +boolean FirmataExt::handlePinMode(byte pin, int mode) +{ + boolean known = false; + for (byte i = 0; i < numFeatures; i++) { + known |= features[i]->handlePinMode(pin, mode); + } + return known; +} +// Modified by Josenalde Oliveira, June 2020 +boolean FirmataExt::handleSysex(byte command, byte argc, byte* argv) +{ + switch (command) { + case 0x01: {// DIGITALWRITE = DIGITALOUTPUT + byte digitalPin; + byte pinState; + + digitalPin = argv[0]; //buffer[2] + pinState = argv[1]; //buffer[3] + + pinMode(digitalPin, OUTPUT); + digitalWrite(digitalPin, pinState); + + Firmata.sendSysex(command, argc, argv); // callback + + break; + } + case 0x02: { // Analog Read Command + byte adcPin = argv[0]; + + unsigned short rawV; + rawV = analogRead(adcPin); //0-1023 + // pin does not need to callback, therefore argv is modified here + byte i = 0; + byte nBytes = rawV / 127; + byte lastByte = rawV % 127; + //nBytes is the number of full 7 bit bytes and lastByte is the offset (difference) for the last 7-bit byte + for (; i= 1) { + argv[i] = lastByte; + } else argv[i] = rawV; + + argc = i+1; + + Firmata.sendSysex(command, argc, argv); + break; + } + case 0x03: { // Digital Read Command + byte digitalPin = argv[0]; + byte pinState[1]; + pinMode(digitalPin, INPUT); + pinState[0] = digitalRead(digitalPin); + Firmata.sendSysex(command, argc, pinState); + break; + } + case 0x04: { // PWM output + byte pwmPin = argv[0]; + byte pwmChannel = argv[1]; + byte pwmFreq = argv[2]; + byte pwmResolution = argv[3]; + unsigned short pwmValue = 0; + + for (byte i = 4; i < argc; i++) { + pwmValue += argv[i]; //from buffer[4]... + } + + ledcSetup(pwmChannel, pwmFreq*1000, pwmResolution); + ledcAttachPin(pwmPin, pwmChannel); + ledcWrite(pwmChannel, pwmValue); + Firmata.sendSysex(command, argc, argv); + } + case PIN_STATE_QUERY: + if (argc > 0) { + byte pin = argv[0]; + if (pin < TOTAL_PINS) { + Firmata.write(START_SYSEX); + Firmata.write(PIN_STATE_RESPONSE); + Firmata.write(pin); + Firmata.write(Firmata.getPinMode(pin)); + int pinState = Firmata.getPinState(pin); + Firmata.write((byte)pinState & 0x7F); + if (pinState & 0xFF80) Firmata.write((byte)(pinState >> 7) & 0x7F); + if (pinState & 0xC000) Firmata.write((byte)(pinState >> 14) & 0x7F); + Firmata.write(END_SYSEX); + return true; + } + } + break; + case CAPABILITY_QUERY: + Firmata.write(START_SYSEX); + Firmata.write(CAPABILITY_RESPONSE); + for (byte pin = 0; pin < TOTAL_PINS; pin++) { + if (Firmata.getPinMode(pin) != PIN_MODE_IGNORE) { + for (byte i = 0; i < numFeatures; i++) { + features[i]->handleCapability(pin); + } + } + Firmata.write(127); + } + Firmata.write(END_SYSEX); + return true; + default: + for (byte i = 0; i < numFeatures; i++) { + if (features[i]->handleSysex(command, argc, argv)) { + return true; + } + } + break; + } + return false; +} + +void FirmataExt::addFeature(FirmataFeature &capability) +{ + if (numFeatures < MAX_FEATURES) { + features[numFeatures++] = &capability; + } +} + +void FirmataExt::reset() +{ + for (byte i = 0; i < numFeatures; i++) { + features[i]->reset(); + } +} \ No newline at end of file diff --git a/firmware/configurable_firmata/src/FirmataExt.h b/firmware/configurable_firmata/src/FirmataExt.h new file mode 100644 index 0000000..04427e6 --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataExt.h @@ -0,0 +1,44 @@ +/* + FirmataExt.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef FirmataExt_h +#define FirmataExt_h + +#include +#include "FirmataFeature.h" + +#define MAX_FEATURES TOTAL_PIN_MODES + 1 + +void handleSetPinModeCallback(byte pin, int mode); + +void handleSysexCallback(byte command, byte argc, byte* argv); + +class FirmataExt: public FirmataFeature +{ + public: + FirmataExt(); + void handleCapability(byte pin); //empty method + boolean handlePinMode(byte pin, int mode); + boolean handleSysex(byte command, byte argc, byte* argv); + void addFeature(FirmataFeature &capability); + void reset(); + + private: + FirmataFeature *features[MAX_FEATURES]; + byte numFeatures; +}; + +#endif diff --git a/firmware/configurable_firmata/src/FirmataFeature.h b/firmware/configurable_firmata/src/FirmataFeature.h new file mode 100644 index 0000000..e83653b --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataFeature.h @@ -0,0 +1,31 @@ +/* + FirmataExt.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef FirmataFeature_h +#define FirmataFeature_h + +#include + +class FirmataFeature +{ + public: + virtual void handleCapability(byte pin) = 0; + virtual boolean handlePinMode(byte pin, int mode) = 0; + virtual boolean handleSysex(byte command, byte argc, byte* argv) = 0; + virtual void reset() = 0; +}; + +#endif diff --git a/firmware/configurable_firmata/src/FirmataReporting.cpp b/firmware/configurable_firmata/src/FirmataReporting.cpp new file mode 100644 index 0000000..b1705f7 --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataReporting.cpp @@ -0,0 +1,66 @@ +/* + FirmataReporting.cpp - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#include +#include "FirmataFeature.h" +#include "FirmataReporting.h" + +void FirmataReporting::setSamplingInterval(int interval) +{ + samplingInterval = interval; +} + +void FirmataReporting::handleCapability(byte pin) +{ + +} + +boolean FirmataReporting::handlePinMode(byte pin, int mode) +{ + return false; +} + +boolean FirmataReporting::handleSysex(byte command, byte argc, byte* argv) +{ + if (command == SAMPLING_INTERVAL) { + if (argc > 1) { + samplingInterval = argv[0] + (argv[1] << 7); + if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) { + samplingInterval = MINIMUM_SAMPLING_INTERVAL; + } + return true; + } + } + return false; +} + +boolean FirmataReporting::elapsed() +{ + currentMillis = millis(); + if (currentMillis - previousMillis > samplingInterval) { + previousMillis += samplingInterval; + if (currentMillis - previousMillis > samplingInterval) + previousMillis = currentMillis - samplingInterval; + return true; + } + return false; +} + +void FirmataReporting::reset() +{ + previousMillis = millis(); + samplingInterval = 19; +} diff --git a/firmware/configurable_firmata/src/FirmataReporting.h b/firmware/configurable_firmata/src/FirmataReporting.h new file mode 100644 index 0000000..88105e6 --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataReporting.h @@ -0,0 +1,43 @@ +/* + FirmataReporting.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: January 23rd, 2016 +*/ + +#ifndef FirmataReporting_h +#define FirmataReporting_h + +#include +#include "FirmataFeature.h" + +#define MINIMUM_SAMPLING_INTERVAL 1 + +class FirmataReporting: public FirmataFeature +{ + public: + void setSamplingInterval(int interval); + void handleCapability(byte pin); //empty method + boolean handlePinMode(byte pin, int mode); //empty method + boolean handleSysex(byte command, byte argc, byte* argv); + boolean elapsed(); + void reset(); + private: + /* timer variables */ + unsigned long currentMillis; // store the current value from millis() + unsigned long previousMillis; // for comparison with currentMillis + unsigned int samplingInterval; // how often to run the main loop (in ms) +}; + +#endif diff --git a/firmware/configurable_firmata/src/FirmataScheduler.cpp b/firmware/configurable_firmata/src/FirmataScheduler.cpp new file mode 100644 index 0000000..32a9bfb --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataScheduler.cpp @@ -0,0 +1,300 @@ +/* + FirmataScheduler.cpp - Firmata library + Copyright (C) 2012-2013 Norbert Truchsess. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#include +#include "FirmataFeature.h" +#include "Encoder7Bit.h" +#include "FirmataScheduler.h" +#include "FirmataExt.h" + +FirmataScheduler *FirmataSchedulerInstance; + +void delayTaskCallback(long delay) +{ + FirmataSchedulerInstance->delayTask(delay); +} + +FirmataScheduler::FirmataScheduler() +{ + FirmataSchedulerInstance = this; + tasks = NULL; + running = NULL; + Firmata.attachDelayTask(delayTaskCallback); +} + +void FirmataScheduler::handleCapability(byte pin) +{ + +} + +boolean FirmataScheduler::handlePinMode(byte pin, int mode) +{ + return false; +} + +boolean FirmataScheduler::handleSysex(byte command, byte argc, byte* argv) +{ + if (command == SCHEDULER_DATA) { + if (argc > 0) { + switch (argv[0]) { + case CREATE_FIRMATA_TASK: + { + if (argc == 4) { + createTask(argv[1], argv[2] | argv[3] << 7); + } + break; + } + case DELETE_FIRMATA_TASK: + { + if (argc == 2) { + deleteTask(argv[1]); + } + break; + } + case ADD_TO_FIRMATA_TASK: + { + if (argc > 2) { + int len = num7BitOutbytes(argc - 2); + Encoder7Bit.readBinary(len, argv + 2, argv + 2); //decode inplace + addToTask(argv[1], len, argv + 2); //addToTask copies data... + } + break; + } + case DELAY_FIRMATA_TASK: + { + if (argc == 6) { + argv++; + Encoder7Bit.readBinary(4, argv, argv); //decode inplace + delayTask(*(long*)((byte*)argv)); + } + break; + } + case SCHEDULE_FIRMATA_TASK: + { + if (argc == 7) { //one byte taskid, 5 bytes to encode 4 bytes of long + Encoder7Bit.readBinary(4, argv + 2, argv + 2); //decode inplace + schedule(argv[1], *(long*)((byte*)argv + 2)); //argv[1] | argv[2]<<8 | argv[3]<<16 | argv[4]<<24 + } + break; + } + case QUERY_ALL_FIRMATA_TASKS: + { + queryAllTasks(); + break; + } + case QUERY_FIRMATA_TASK: + { + if (argc == 2) { + queryTask(argv[1]); + } + break; + } + case RESET_FIRMATA_TASKS: + { + reset(); + } + } + } + return true; + } + return false; +}; + +void FirmataScheduler::createTask(byte id, int len) +{ + firmata_task *existing = findTask(id); + if (existing) { + reportTask(id, existing, true); + } + else { + firmata_task *newTask = (firmata_task*)malloc(sizeof(firmata_task) + len); + newTask->id = id; + newTask->time_ms = 0; + newTask->len = len; + newTask->nextTask = tasks; + newTask->pos = 0; + tasks = newTask; + } +}; + +void FirmataScheduler::deleteTask(byte id) +{ + firmata_task *current = tasks; + firmata_task *previous = NULL; + while (current) { + if (current->id == id) { + if (previous) { + previous->nextTask = current->nextTask; + } + else { + tasks = current->nextTask; + } + free (current); + return; + } + else { + previous = current; + current = current->nextTask; + } + } +}; + +void FirmataScheduler::addToTask(byte id, int additionalBytes, byte *message) +{ + firmata_task *existing = findTask(id); + if (existing) { //task exists and has not been fully loaded yet + if (existing->pos + additionalBytes <= existing->len) { + for (int i = 0; i < additionalBytes; i++) { + existing->messages[existing->pos++] = message[i]; + } + } + } + else { + reportTask(id, NULL, true); + } +}; + +void FirmataScheduler::schedule(byte id, long delay_ms) +{ + firmata_task *existing = findTask(id); + if (existing) { + existing->pos = 0; + existing->time_ms = millis() + delay_ms; + } + else { + reportTask(id, NULL, true); + } +}; + +void FirmataScheduler::delayTask(long delay_ms) +{ + if (running) { + long now = millis(); + running->time_ms += delay_ms; + if (running->time_ms < now) { //if delay time allready passed by schedule to 'now'. + running->time_ms = now; + } + } +} + +void FirmataScheduler::queryAllTasks() +{ + Firmata.write(START_SYSEX); + Firmata.write(SCHEDULER_DATA); + Firmata.write(QUERY_ALL_TASKS_REPLY); + firmata_task *task = tasks; + while (task) { + Firmata.write(task->id); + task = task->nextTask; + } + Firmata.write(END_SYSEX); +}; + +void FirmataScheduler::queryTask(byte id) +{ + firmata_task *task = findTask(id); + reportTask(id, task, false); +} + +void FirmataScheduler::reportTask(byte id, firmata_task *task, boolean error) +{ + Firmata.write(START_SYSEX); + Firmata.write(SCHEDULER_DATA); + if (error) { + Firmata.write(ERROR_TASK_REPLY); + } else { + Firmata.write(QUERY_TASK_REPLY); + } + Firmata.write(id); + if (task) { + Encoder7Bit.startBinaryWrite(); + for (int i = 3; i < firmata_task_len(task); i++) { + Encoder7Bit.writeBinary(((byte *)task)[i]); //don't write first 3 bytes (firmata_task*, byte); makes use of AVR byteorder (LSB first) + } + Encoder7Bit.endBinaryWrite(); + } + Firmata.write(END_SYSEX); +}; + +void FirmataScheduler::runTasks() +{ + if (tasks) { + long now = millis(); + firmata_task *current = tasks; + firmata_task *previous = NULL; + while (current) { + if (current->time_ms > 0 && current->time_ms < now) { // TODO handle overflow + if (execute(current)) { + previous = current; + current = current->nextTask; + } + else { + if (previous) { + previous->nextTask = current->nextTask; + free(current); + current = previous->nextTask; + } + else { + tasks = current->nextTask; + free(current); + current = tasks; + } + } + } + else { + current = current->nextTask; + } + } + } +}; + +void FirmataScheduler::reset() +{ + while (tasks) { + firmata_task *nextTask = tasks->nextTask; + free(tasks); + tasks = nextTask; + } +}; + +//private +boolean FirmataScheduler::execute(firmata_task *task) +{ + long start = task->time_ms; + int pos = task->pos; + int len = task->len; + byte *messages = task->messages; + running = task; + while (pos < len) { + Firmata.parse(messages[pos++]); + if (start != task->time_ms) { // return true if task got rescheduled during run. + task->pos = ( pos == len ? 0 : pos ); // last message executed? -> start over next time + running = NULL; + return true; + } + }; + running = NULL; + return false; +} + +firmata_task *FirmataScheduler::findTask(byte id) +{ + firmata_task *currentTask = tasks; + while (currentTask) { + if (id == currentTask->id) { + return currentTask; + } else { + currentTask = currentTask->nextTask; + } + }; + return NULL; +} diff --git a/firmware/configurable_firmata/src/FirmataScheduler.h b/firmware/configurable_firmata/src/FirmataScheduler.h new file mode 100644 index 0000000..bf6bd70 --- /dev/null +++ b/firmware/configurable_firmata/src/FirmataScheduler.h @@ -0,0 +1,73 @@ +/* + FirmataScheduler.h - Firmata library + Copyright (C) 2012-2013 Norbert Truchsess. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef FirmataScheduler_h +#define FirmataScheduler_h + +#include +#include "FirmataFeature.h" +#include "Encoder7Bit.h" + +//subcommands +#define CREATE_FIRMATA_TASK 0 +#define DELETE_FIRMATA_TASK 1 +#define ADD_TO_FIRMATA_TASK 2 +#define DELAY_FIRMATA_TASK 3 +#define SCHEDULE_FIRMATA_TASK 4 +#define QUERY_ALL_FIRMATA_TASKS 5 +#define QUERY_FIRMATA_TASK 6 +#define RESET_FIRMATA_TASKS 7 +#define ERROR_TASK_REPLY 8 +#define QUERY_ALL_TASKS_REPLY 9 +#define QUERY_TASK_REPLY 10 + +#define firmata_task_len(a)(sizeof(firmata_task)+(a)->len) + +void delayTaskCallback(long delay); + +struct firmata_task +{ + firmata_task *nextTask; + byte id; //only 7bits used -> supports 127 tasks + long time_ms; + int len; + int pos; + byte messages[]; +}; + +class FirmataScheduler: public FirmataFeature +{ + public: + FirmataScheduler(); + void handleCapability(byte pin); //empty method + boolean handlePinMode(byte pin, int mode); //empty method + boolean handleSysex(byte command, byte argc, byte* argv); + void runTasks(); + void reset(); + void createTask(byte id, int len); + void deleteTask(byte id); + void addToTask(byte id, int len, byte *message); + void schedule(byte id, long time_ms); + void delayTask(long time_ms); + void queryAllTasks(); + void queryTask(byte id); + + private: + firmata_task *tasks; + firmata_task *running; + + boolean execute(firmata_task *task); + firmata_task *findTask(byte id); + void reportTask(byte id, firmata_task *task, boolean error); +}; + +#endif diff --git a/firmware/configurable_firmata/src/I2CFirmata.cpp b/firmware/configurable_firmata/src/I2CFirmata.cpp new file mode 100644 index 0000000..c74ccc3 --- /dev/null +++ b/firmware/configurable_firmata/src/I2CFirmata.cpp @@ -0,0 +1,4 @@ +/* + * Implementation is in I2CFirmata.h to avoid having to include Wire.h in all + * sketch files that include ConfigurableFirmata.h + */ diff --git a/firmware/configurable_firmata/src/I2CFirmata.h b/firmware/configurable_firmata/src/I2CFirmata.h new file mode 100644 index 0000000..e837a7b --- /dev/null +++ b/firmware/configurable_firmata/src/I2CFirmata.h @@ -0,0 +1,333 @@ +/* + I2CFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + I2CFirmata.cpp has been merged into this header file as a hack to avoid having to + include Wire.h for every arduino sketch that includes ConfigurableFirmata. + + Last updated by Jeff Hoefs: January 23rd, 2015 +*/ + +#ifndef I2CFirmata_h +#define I2CFirmata_h + +#include +#include +#include "FirmataFeature.h" +#include "FirmataReporting.h" + +#define I2C_WRITE B00000000 +#define I2C_READ B00001000 +#define I2C_READ_CONTINUOUSLY B00010000 +#define I2C_STOP_READING B00011000 +#define I2C_READ_WRITE_MODE_MASK B00011000 +#define I2C_10BIT_ADDRESS_MODE_MASK B00100000 +#define I2C_END_TX_MASK B01000000 +#define I2C_STOP_TX 1 +#define I2C_RESTART_TX 0 +#define I2C_MAX_QUERIES 8 +#define I2C_REGISTER_NOT_SPECIFIED -1 + +/* i2c data */ +struct i2c_device_info { + byte addr; + int reg; + byte bytes; + byte stopTX; +}; + +class I2CFirmata: public FirmataFeature +{ + public: + I2CFirmata(); + boolean handlePinMode(byte pin, int mode); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte* argv); + void reset(); + void report(); + + private: + /* for i2c read continuous more */ + i2c_device_info query[I2C_MAX_QUERIES]; + + byte i2cRxData[32]; + boolean isI2CEnabled; + signed char queryIndex; + unsigned int i2cReadDelayTime; // default delay time between i2c read request and Wire.requestFrom() + + void readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX); + void handleI2CRequest(byte argc, byte *argv); + boolean handleI2CConfig(byte argc, byte *argv); + boolean enableI2CPins(); + void disableI2CPins(); +}; + + +/* + * I2CFirmata.cpp + * Copied here as a hack to avoid having to include Wire.h in all sketch files that + * include ConfigurableFirmata.h + */ + +I2CFirmata::I2CFirmata() +{ + isI2CEnabled = false; + queryIndex = -1; + i2cReadDelayTime = 0; // default delay time between i2c read request and Wire.requestFrom() +} + +void I2CFirmata::readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX) { + // allow I2C requests that don't require a register read + // for example, some devices using an interrupt pin to signify new data available + // do not always require the register read so upon interrupt you call Wire.requestFrom() + if (theRegister != I2C_REGISTER_NOT_SPECIFIED) { + Wire.beginTransmission(address); + Wire.write((byte)theRegister); + Wire.endTransmission(stopTX); // default = true + // do not set a value of 0 + if (i2cReadDelayTime > 0) { + // delay is necessary for some devices such as WiiNunchuck + delayMicroseconds(i2cReadDelayTime); + } + } else { + theRegister = 0; // fill the register with a dummy value + } + + Wire.requestFrom(address, numBytes); // all bytes are returned in requestFrom + + // check to be sure correct number of bytes were returned by slave + if (numBytes < Wire.available()) { + Firmata.sendString("I2C: Too many bytes received"); + } else if (numBytes > Wire.available()) { + Firmata.sendString("I2C: Too few bytes received"); + } + + i2cRxData[0] = address; + i2cRxData[1] = theRegister; + + for (int i = 0; i < numBytes && Wire.available(); i++) { + i2cRxData[2 + i] = Wire.read(); + } + + // send slave address, register and received bytes + Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData); +} + +boolean I2CFirmata::handlePinMode(byte pin, int mode) +{ + if (IS_PIN_I2C(pin)) { + if (mode == PIN_MODE_I2C) { + // the user must call I2C_CONFIG to enable I2C for a device + return true; + } else if (isI2CEnabled) { + // disable i2c so pins can be used for other functions + // the following if statements should reconfigure the pins properly + if (Firmata.getPinMode(pin) == PIN_MODE_I2C) { + disableI2CPins(); + } + } + } + return false; +} + +void I2CFirmata::handleCapability(byte pin) +{ + if (IS_PIN_I2C(pin)) { + Firmata.write(PIN_MODE_I2C); + Firmata.write(1); // TODO: could assign a number to map to SCL or SDA + } +} + +boolean I2CFirmata::handleSysex(byte command, byte argc, byte *argv) +{ + switch (command) { + case I2C_REQUEST: + if (isI2CEnabled) { + handleI2CRequest(argc, argv); + return true; + } + case I2C_CONFIG: + return handleI2CConfig(argc, argv); + } + return false; +} + +void I2CFirmata::handleI2CRequest(byte argc, byte *argv) +{ + byte mode; + byte stopTX; + byte slaveAddress; + byte data; + int slaveRegister; + mode = argv[1] & I2C_READ_WRITE_MODE_MASK; + if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) { + Firmata.sendString("10-bit addressing not supported"); + return; + } + else { + slaveAddress = argv[0]; + } + + // need to invert the logic here since 0 will be default for client + // libraries that have not updated to add support for restart tx + if (argv[1] & I2C_END_TX_MASK) { + stopTX = I2C_RESTART_TX; + } + else { + stopTX = I2C_STOP_TX; // default + } + + switch (mode) { + case I2C_WRITE: + Wire.beginTransmission(slaveAddress); + for (byte i = 2; i < argc; i += 2) { + data = argv[i] + (argv[i + 1] << 7); + Wire.write(data); + } + Wire.endTransmission(); + delayMicroseconds(70); + break; + case I2C_READ: + if (argc == 6) { + // a slave register is specified + slaveRegister = argv[2] + (argv[3] << 7); + data = argv[4] + (argv[5] << 7); // bytes to read + } + else { + // a slave register is NOT specified + slaveRegister = I2C_REGISTER_NOT_SPECIFIED; + data = argv[2] + (argv[3] << 7); // bytes to read + } + readAndReportData(slaveAddress, (int)slaveRegister, data, stopTX); + break; + case I2C_READ_CONTINUOUSLY: + if ((queryIndex + 1) >= I2C_MAX_QUERIES) { + // too many queries, just ignore + Firmata.sendString("too many queries"); + break; + } + if (argc == 6) { + // a slave register is specified + slaveRegister = argv[2] + (argv[3] << 7); + data = argv[4] + (argv[5] << 7); // bytes to read + } + else { + // a slave register is NOT specified + slaveRegister = (int)I2C_REGISTER_NOT_SPECIFIED; + data = argv[2] + (argv[3] << 7); // bytes to read + } + queryIndex++; + query[queryIndex].addr = slaveAddress; + query[queryIndex].reg = slaveRegister; + query[queryIndex].bytes = data; + query[queryIndex].stopTX = stopTX; + break; + case I2C_STOP_READING: + byte queryIndexToSkip; + // if read continuous mode is enabled for only 1 i2c device, disable + // read continuous reporting for that device + if (queryIndex <= 0) { + queryIndex = -1; + } else { + queryIndexToSkip = 0; + // if read continuous mode is enabled for multiple devices, + // determine which device to stop reading and remove it's data from + // the array, shifiting other array data to fill the space + for (byte i = 0; i < queryIndex + 1; i++) { + if (query[i].addr == slaveAddress) { + queryIndexToSkip = i; + break; + } + } + + for (byte i = queryIndexToSkip; i < queryIndex + 1; i++) { + if (i < I2C_MAX_QUERIES) { + query[i].addr = query[i + 1].addr; + query[i].reg = query[i + 1].reg; + query[i].bytes = query[i + 1].bytes; + query[i].stopTX = query[i + 1].stopTX; + } + } + queryIndex--; + } + break; + default: + break; + } +} + +boolean I2CFirmata::handleI2CConfig(byte argc, byte *argv) +{ + unsigned int delayTime = (argv[0] + (argv[1] << 7)); + + if (delayTime > 0) { + i2cReadDelayTime = delayTime; + } + + if (!isI2CEnabled) { + enableI2CPins(); + } + return isI2CEnabled; +} + +boolean I2CFirmata::enableI2CPins() +{ + byte i; + // is there a faster way to do this? would probaby require importing + // Arduino.h to get SCL and SDA pins + for (i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_I2C(i)) { + if (Firmata.getPinMode(i) == PIN_MODE_IGNORE) { + return false; + } + // mark pins as i2c so they are ignore in non i2c data requests + Firmata.setPinMode(i, PIN_MODE_I2C); + pinMode(i, PIN_MODE_I2C); + } + } + + isI2CEnabled = true; + + Wire.begin(); + return true; +} + +/* disable the i2c pins so they can be used for other functions */ +void I2CFirmata::disableI2CPins() +{ + isI2CEnabled = false; + // disable read continuous mode for all devices + queryIndex = -1; + // uncomment the following if or when the end() method is added to Wire library + // Wire.end(); +} + +void I2CFirmata::reset() +{ + if (isI2CEnabled) { + disableI2CPins(); + } +} + +void I2CFirmata::report() +{ + // report i2c data for all device with read continuous mode enabled + if (queryIndex > -1) { + for (byte i = 0; i < queryIndex + 1; i++) { + readAndReportData(query[i].addr, query[i].reg, query[i].bytes, query[i].stopTX); + } + } +} + +#endif /* I2CFirmata_h */ diff --git a/firmware/configurable_firmata/src/OneWireFirmata.cpp b/firmware/configurable_firmata/src/OneWireFirmata.cpp new file mode 100644 index 0000000..80cf5ae --- /dev/null +++ b/firmware/configurable_firmata/src/OneWireFirmata.cpp @@ -0,0 +1,178 @@ +/* + OneWireFirmata.cpp - Firmata library + Copyright (C) 2012-2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: January 23rd, 2016 +*/ + +#include +#include "OneWireFirmata.h" +#include "Encoder7Bit.h" + +boolean OneWireFirmata::handlePinMode(byte pin, int mode) +{ + if (IS_PIN_DIGITAL(pin) && mode == PIN_MODE_ONEWIRE) { + oneWireConfig(pin, ONEWIRE_POWER); + return true; + } + return false; +} + +void OneWireFirmata::handleCapability(byte pin) +{ + if (IS_PIN_DIGITAL(pin)) { + Firmata.write(PIN_MODE_ONEWIRE); + Firmata.write(1); + } +} + +void OneWireFirmata::oneWireConfig(byte pin, boolean power) +{ + ow_device_info *info = &pinOneWire[pin]; + if (info->device == NULL) { + info->device = new OneWire(pin); + } + for (int i = 0; i < 8; i++) { + info->addr[i] = 0x0; + } + info->power = power; +} + +boolean OneWireFirmata::handleSysex(byte command, byte argc, byte* argv) +{ + if (command == ONEWIRE_DATA) { + if (argc > 1) { + byte subcommand = argv[0]; + byte pin = argv[1]; + ow_device_info *info = &pinOneWire[pin]; + OneWire *device = info->device; + if (device || subcommand == ONEWIRE_CONFIG_REQUEST) { + switch (subcommand) { + case ONEWIRE_SEARCH_REQUEST: + case ONEWIRE_SEARCH_ALARMS_REQUEST: + { + device->reset_search(); + Firmata.write(START_SYSEX); + Firmata.write(ONEWIRE_DATA); + boolean isAlarmSearch = (subcommand == ONEWIRE_SEARCH_ALARMS_REQUEST); + Firmata.write(isAlarmSearch ? (byte)ONEWIRE_SEARCH_ALARMS_REPLY : (byte)ONEWIRE_SEARCH_REPLY); + Firmata.write(pin); + Encoder7Bit.startBinaryWrite(); + byte addrArray[8]; + while (isAlarmSearch ? device->search(addrArray, false) : device->search(addrArray)) { + for (int i = 0; i < 8; i++) { + Encoder7Bit.writeBinary(addrArray[i]); + } + } + Encoder7Bit.endBinaryWrite(); + Firmata.write(END_SYSEX); + break; + } + case ONEWIRE_CONFIG_REQUEST: + { + if (argc == 3 && Firmata.getPinMode(pin) != PIN_MODE_IGNORE) { + Firmata.setPinMode(pin, PIN_MODE_ONEWIRE); + oneWireConfig(pin, argv[2]); // this calls oneWireConfig again, this time setting the correct config (which doesn't cause harm though) + } else { + return false; + } + break; + } + default: + { + if (subcommand & ONEWIRE_RESET_REQUEST_BIT) { + device->reset(); + for (int i = 0; i < 8; i++) { + info->addr[i] = 0x0; + } + } + if (subcommand & ONEWIRE_SKIP_REQUEST_BIT) { + device->skip(); + for (byte i = 0; i < 8; i++) { + info->addr[i] = 0x0; + } + } + if (subcommand & ONEWIRE_WITHDATA_REQUEST_BITS) { + int numBytes = num7BitOutbytes(argc - 2); + int numReadBytes = 0; + int correlationId; + argv += 2; + Encoder7Bit.readBinary(numBytes, argv, argv); //decode inplace + + if (subcommand & ONEWIRE_SELECT_REQUEST_BIT) { + if (numBytes < 8) break; + device->select(argv); + for (int i = 0; i < 8; i++) { + info->addr[i] = argv[i]; + } + argv += 8; + numBytes -= 8; + } + + if (subcommand & ONEWIRE_READ_REQUEST_BIT) { + if (numBytes < 4) break; + numReadBytes = *((int*)argv); + argv += 2; + correlationId = *((int*)argv); + argv += 2; + numBytes -= 4; + } + + if (subcommand & ONEWIRE_DELAY_REQUEST_BIT) { + if (numBytes < 4) break; + Firmata.delayTask(*((long*)argv)); + argv += 4; + numBytes -= 4; + } + + if (subcommand & ONEWIRE_WRITE_REQUEST_BIT) { + for (int i = 0; i < numBytes; i++) { + info->device->write(argv[i], info->power); + } + } + + if (numReadBytes > 0) { + Firmata.write(START_SYSEX); + Firmata.write(ONEWIRE_DATA); + Firmata.write(ONEWIRE_READ_REPLY); + Firmata.write(pin); + Encoder7Bit.startBinaryWrite(); + Encoder7Bit.writeBinary(correlationId & 0xFF); + Encoder7Bit.writeBinary((correlationId >> 8) & 0xFF); + for (int i = 0; i < numReadBytes; i++) { + Encoder7Bit.writeBinary(device->read()); + } + Encoder7Bit.endBinaryWrite(); + Firmata.write(END_SYSEX); + } + } + } + } + } + return true; + } + } + return false; +} + +void OneWireFirmata::reset() +{ + for (int i = 0; i < TOTAL_PINS; i++) { + if (pinOneWire[i].device) { + free(pinOneWire[i].device); + pinOneWire[i].device = NULL; + } + for (int j = 0; j < 8; j++) { + pinOneWire[i].addr[j] = 0; + } + pinOneWire[i].power = false; + } +} diff --git a/firmware/configurable_firmata/src/OneWireFirmata.h b/firmware/configurable_firmata/src/OneWireFirmata.h new file mode 100644 index 0000000..9c68aef --- /dev/null +++ b/firmware/configurable_firmata/src/OneWireFirmata.h @@ -0,0 +1,63 @@ +/* + OneWireFirmata.h - Firmata library + Copyright (C) 2012-2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef OneWireFirmata_h +#define OneWireFirmata_h + +#include +#include "utility/OneWire.h" +#include "FirmataFeature.h" + +//subcommands: +#define ONEWIRE_SEARCH_REQUEST 0x40 +#define ONEWIRE_CONFIG_REQUEST 0x41 +#define ONEWIRE_SEARCH_REPLY 0x42 +#define ONEWIRE_READ_REPLY 0x43 +#define ONEWIRE_SEARCH_ALARMS_REQUEST 0x44 +#define ONEWIRE_SEARCH_ALARMS_REPLY 0x45 + +#define ONEWIRE_RESET_REQUEST_BIT 0x01 +#define ONEWIRE_SKIP_REQUEST_BIT 0x02 +#define ONEWIRE_SELECT_REQUEST_BIT 0x04 +#define ONEWIRE_READ_REQUEST_BIT 0x08 +#define ONEWIRE_DELAY_REQUEST_BIT 0x10 +#define ONEWIRE_WRITE_REQUEST_BIT 0x20 + +#define ONEWIRE_WITHDATA_REQUEST_BITS 0x3C + +#define ONEWIRE_CRC 0 //for OneWire.h: crc-functions are not used by Firmata + +//default value for power: +#define ONEWIRE_POWER 1 + +struct ow_device_info +{ + OneWire* device; + byte addr[8]; + boolean power; +}; + +class OneWireFirmata: public FirmataFeature +{ + public: + boolean handlePinMode(byte pin, int mode); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte* argv); + void reset(); + + private: + ow_device_info pinOneWire[TOTAL_PINS]; + void oneWireConfig(byte pin, boolean power); +}; + +#endif diff --git a/firmware/configurable_firmata/src/SerialFirmata.cpp b/firmware/configurable_firmata/src/SerialFirmata.cpp new file mode 100644 index 0000000..ae35e85 --- /dev/null +++ b/firmware/configurable_firmata/src/SerialFirmata.cpp @@ -0,0 +1,360 @@ +/* + SerialFirmata.cpp - Firmata library + Copyright (C) 2015-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated December 23rd, 2016 +*/ + +#include "SerialFirmata.h" + +SerialFirmata::SerialFirmata() +{ +#if defined(SoftwareSerial_h) + swSerial0 = NULL; + swSerial1 = NULL; + swSerial2 = NULL; + swSerial3 = NULL; +#endif + + serialIndex = -1; +} + +boolean SerialFirmata::handlePinMode(byte pin, int mode) +{ + if (mode == PIN_MODE_SERIAL) { + // nothing else to do here since the mode is set in SERIAL_CONFIG + return true; + } + return false; +} + +void SerialFirmata::handleCapability(byte pin) +{ + if (IS_PIN_SERIAL(pin)) { + Firmata.write(PIN_MODE_SERIAL); + Firmata.write(getSerialPinType(pin)); + } +} + +boolean SerialFirmata::handleSysex(byte command, byte argc, byte *argv) +{ + if (command == SERIAL_MESSAGE) { + + Stream *serialPort; + byte mode = argv[0] & SERIAL_MODE_MASK; + byte portId = argv[0] & SERIAL_PORT_ID_MASK; + if (portId >= SERIAL_READ_ARR_LEN) return false; + + switch (mode) { + case SERIAL_CONFIG: + { + long baud = (long)argv[1] | ((long)argv[2] << 7) | ((long)argv[3] << 14); + serial_pins pins; + lastAvailableBytes[portId] = 0; + lastReceive[portId] = 0; +// this ifdef will be removed once a command to enable RX buffering has been added to the protocol +#if defined(FIRMATA_SERIAL_PORT_RX_BUFFERING) + // 8N1 = 10 bits per char, max. 50 bits -> 50000 = 50bits * 1000ms/s + // char delay value (ms) to detect the end of a message, defaults to 50 bits * 1000 / baud rate + // a value of 0 will disable RX buffering, resulting in single byte transfers to the host with + // baud rates below approximately 56k (varies with CPU speed) + maxCharDelay[portId] = 50000 / baud; +#else + maxCharDelay[portId] = 0; +#endif + if (portId < 8) { + serialPort = getPortFromId(portId); + if (serialPort != NULL) { + pins = getSerialPinNumbers(portId); + if (pins.rx != 0 && pins.tx != 0) { + Firmata.setPinMode(pins.rx, PIN_MODE_SERIAL); + Firmata.setPinMode(pins.tx, PIN_MODE_SERIAL); + // Fixes an issue where some serial devices would not work properly with Arduino Due + // because all Arduino pins are set to OUTPUT by default in StandardFirmata. + pinMode(pins.rx, INPUT); + } + ((HardwareSerial*)serialPort)->begin(baud); + } + } else { +#if defined(SoftwareSerial_h) + byte swTxPin, swRxPin; + if (argc > 4) { + swRxPin = argv[4]; + swTxPin = argv[5]; + } else { + // RX and TX pins must be specified when using software serial + Firmata.sendString("Specify serial RX and TX pins"); + return false; + } + switch (portId) { + case SW_SERIAL0: + if (swSerial0 == NULL) { + swSerial0 = new SoftwareSerial(swRxPin, swTxPin); + } + break; + case SW_SERIAL1: + if (swSerial1 == NULL) { + swSerial1 = new SoftwareSerial(swRxPin, swTxPin); + } + break; + case SW_SERIAL2: + if (swSerial2 == NULL) { + swSerial2 = new SoftwareSerial(swRxPin, swTxPin); + } + break; + case SW_SERIAL3: + if (swSerial3 == NULL) { + swSerial3 = new SoftwareSerial(swRxPin, swTxPin); + } + break; + } + serialPort = getPortFromId(portId); + if (serialPort != NULL) { + Firmata.setPinMode(swRxPin, PIN_MODE_SERIAL); + Firmata.setPinMode(swTxPin, PIN_MODE_SERIAL); + ((SoftwareSerial*)serialPort)->begin(baud); + } +#endif + } + break; // SERIAL_CONFIG + } + case SERIAL_WRITE: + { + byte data; + serialPort = getPortFromId(portId); + if (serialPort == NULL) { + break; + } + for (byte i = 1; i < argc; i += 2) { + data = argv[i] + (argv[i + 1] << 7); + serialPort->write(data); + } + break; // SERIAL_WRITE + } + case SERIAL_READ: + if (argv[1] == SERIAL_READ_CONTINUOUSLY) { + if (serialIndex + 1 >= MAX_SERIAL_PORTS) { + break; + } + + if (argc > 2) { + // maximum number of bytes to read from buffer per iteration of loop() + serialBytesToRead[portId] = (int)argv[2] | ((int)argv[3] << 7); + } else { + // read all available bytes per iteration of loop() + serialBytesToRead[portId] = 0; + } + serialIndex++; + reportSerial[serialIndex] = portId; + } else if (argv[1] == SERIAL_STOP_READING) { + byte serialIndexToSkip = 0; + if (serialIndex <= 0) { + serialIndex = -1; + } else { + for (byte i = 0; i < serialIndex + 1; i++) { + if (reportSerial[i] == portId) { + serialIndexToSkip = i; + break; + } + } + // shift elements over to fill space left by removed element + for (byte i = serialIndexToSkip; i < serialIndex + 1; i++) { + if (i < MAX_SERIAL_PORTS) { + reportSerial[i] = reportSerial[i + 1]; + } + } + serialIndex--; + } + } + break; // SERIAL_READ + case SERIAL_CLOSE: + serialPort = getPortFromId(portId); + if (serialPort != NULL) { + if (portId < 8) { + ((HardwareSerial*)serialPort)->end(); + } else { +#if defined(SoftwareSerial_h) + ((SoftwareSerial*)serialPort)->end(); + if (serialPort != NULL) { + free(serialPort); + serialPort = NULL; + } +#endif + } + } + break; // SERIAL_CLOSE + case SERIAL_FLUSH: + serialPort = getPortFromId(portId); + if (serialPort != NULL) { + getPortFromId(portId)->flush(); + } + break; // SERIAL_FLUSH +#if defined(SoftwareSerial_h) + case SERIAL_LISTEN: + // can only call listen() on software serial ports + if (portId > 7) { + serialPort = getPortFromId(portId); + if (serialPort != NULL) { + ((SoftwareSerial*)serialPort)->listen(); + } + } + break; // SERIAL_LISTEN +#endif + } + return true; + } + return false; +} + +void SerialFirmata::update() +{ + checkSerial(); +} + +void SerialFirmata::reset() +{ +#if defined(SoftwareSerial_h) + Stream *serialPort; + // free memory allocated for SoftwareSerial ports + for (byte i = SW_SERIAL0; i < SW_SERIAL3 + 1; i++) { + serialPort = getPortFromId(i); + if (serialPort != NULL) { + free(serialPort); + serialPort = NULL; + } + } +#endif + + serialIndex = -1; + for (byte i = 0; i < SERIAL_READ_ARR_LEN; i++) { + serialBytesToRead[i] = 0; + } +} + +// get a pointer to the serial port associated with the specified port id +Stream* SerialFirmata::getPortFromId(byte portId) +{ + switch (portId) { + case HW_SERIAL0: + // block use of Serial (typically pins 0 and 1) until ability to reclaim Serial is implemented + //return &Serial; + return NULL; +#if defined(PIN_SERIAL1_RX) + case HW_SERIAL1: + return &Serial1; +#endif +#if defined(PIN_SERIAL2_RX) + case HW_SERIAL2: + return &Serial2; +#endif +#if defined(PIN_SERIAL3_RX) + case HW_SERIAL3: + return &Serial3; +#endif +#if defined(SoftwareSerial_h) + case SW_SERIAL0: + if (swSerial0 != NULL) { + // instances of SoftwareSerial are already pointers so simply return the instance + return swSerial0; + } + break; + case SW_SERIAL1: + if (swSerial1 != NULL) { + return swSerial1; + } + break; + case SW_SERIAL2: + if (swSerial2 != NULL) { + return swSerial2; + } + break; + case SW_SERIAL3: + if (swSerial3 != NULL) { + return swSerial3; + } + break; +#endif + } + return NULL; +} + +// Check serial ports that have READ_CONTINUOUS mode set and relay any data +// for each port to the device attached to that port. +void SerialFirmata::checkSerial() +{ + byte portId, serialData; + int bytesToRead = 0; + int numBytesToRead = 0; + Stream* serialPort; + + if (serialIndex > -1) { + + unsigned long currentMillis = millis(); + + // loop through all reporting (READ_CONTINUOUS) serial ports + for (byte i = 0; i < serialIndex + 1; i++) { + portId = reportSerial[i]; + bytesToRead = serialBytesToRead[portId]; + serialPort = getPortFromId(portId); + if (serialPort == NULL) { + continue; + } +#if defined(SoftwareSerial_h) + // only the SoftwareSerial port that is "listening" can read data + if (portId > 7 && !((SoftwareSerial*)serialPort)->isListening()) { + continue; + } +#endif + int availableBytes = serialPort->available(); + if (availableBytes > 0) { + bool read = true; + + // check if reading should be delayed to collect some bytes before + // forwarding (for baud rates significantly below 57600 baud) + if (maxCharDelay[portId]) { + // inter character delay exceeded or more than 48 bytes available or more bytes available than required + read = (lastAvailableBytes[portId] > 0 && (currentMillis - lastReceive[portId]) >= maxCharDelay[portId]) + || (bytesToRead == 0 && availableBytes >= 48) || (bytesToRead > 0 && availableBytes >= bytesToRead); + if (availableBytes > lastAvailableBytes[portId]) { + lastReceive[portId] = currentMillis; + lastAvailableBytes[portId] = availableBytes; + } + } + + if (read) { + Firmata.write(START_SYSEX); + Firmata.write(SERIAL_MESSAGE); + Firmata.write(SERIAL_REPLY | portId); + + if (bytesToRead == 0 || (serialPort->available() <= bytesToRead)) { + numBytesToRead = serialPort->available(); + } else { + numBytesToRead = bytesToRead; + } + + if (lastAvailableBytes[portId] - numBytesToRead >= 0) { + lastAvailableBytes[portId] -= numBytesToRead; + } else { + lastAvailableBytes[portId] = 0; + } + + // relay serial data to the serial device + while (numBytesToRead > 0) { + serialData = serialPort->read(); + Firmata.write(serialData & 0x7F); + Firmata.write((serialData >> 7) & 0x7F); + numBytesToRead--; + } + Firmata.write(END_SYSEX); + } + } + } + } +} diff --git a/firmware/configurable_firmata/src/SerialFirmata.h b/firmware/configurable_firmata/src/SerialFirmata.h new file mode 100644 index 0000000..87f58d9 --- /dev/null +++ b/firmware/configurable_firmata/src/SerialFirmata.h @@ -0,0 +1,166 @@ +/* + SerialFirmata.h - Firmata library + Copyright (C) 2015-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated December 23rd, 2016 +*/ + +#ifndef SerialFirmata_h +#define SerialFirmata_h + +#include +#include "FirmataFeature.h" +// SoftwareSerial is currently only supported for AVR-based boards and the Arduino 101. +// Limited to Arduino 1.6.6 or higher because Arduino builder cannot find SoftwareSerial +// prior to this release. +#if (ARDUINO > 10605) && (defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ARC32)) +#include +#endif + +// uncomment FIRMATA_SERIAL_PORT_RX_BUFFERING to collect bytes received by serial port until the +// receive buffer gets filled or a data gap is detected to avoid forwarding single bytes at baud +// rates below 50000 +//#define FIRMATA_SERIAL_PORT_RX_BUFFERING + +// Serial port Ids +#define HW_SERIAL0 0x00 +#define HW_SERIAL1 0x01 +#define HW_SERIAL2 0x02 +#define HW_SERIAL3 0x03 +// extensible up to 0x07 + +#define SW_SERIAL0 0x08 +#define SW_SERIAL1 0x09 +#define SW_SERIAL2 0x0A +#define SW_SERIAL3 0x0B +// extensible up to 0x0F + +#define SERIAL_PORT_ID_MASK 0x0F +#define MAX_SERIAL_PORTS 8 +#define SERIAL_READ_ARR_LEN 12 + +// map configuration query response resolution value to serial pin type +#define RES_RX1 0x02 +#define RES_TX1 0x03 +#define RES_RX2 0x04 +#define RES_TX2 0x05 +#define RES_RX3 0x06 +#define RES_TX3 0x07 + +// Serial command bytes +#define SERIAL_CONFIG 0x10 +#define SERIAL_WRITE 0x20 +#define SERIAL_READ 0x30 +#define SERIAL_REPLY 0x40 +#define SERIAL_CLOSE 0x50 +#define SERIAL_FLUSH 0x60 +#define SERIAL_LISTEN 0x70 + +// Serial read modes +#define SERIAL_READ_CONTINUOUSLY 0x00 +#define SERIAL_STOP_READING 0x01 +#define SERIAL_MODE_MASK 0xF0 + +struct serial_pins { + uint8_t rx; + uint8_t tx; +}; + +/* + * Get the serial serial pin type (RX1, TX1, RX2, TX2, etc) for the specified pin. + */ +inline uint8_t getSerialPinType(uint8_t pin) { +#if defined(PIN_SERIAL_RX) + // TODO when use of HW_SERIAL0 is enabled +#endif +#if defined(PIN_SERIAL1_RX) + if (pin == PIN_SERIAL1_RX) return RES_RX1; + if (pin == PIN_SERIAL1_TX) return RES_TX1; +#endif +#if defined(PIN_SERIAL2_RX) + if (pin == PIN_SERIAL2_RX) return RES_RX2; + if (pin == PIN_SERIAL2_TX) return RES_TX2; +#endif +#if defined(PIN_SERIAL3_RX) + if (pin == PIN_SERIAL3_RX) return RES_RX3; + if (pin == PIN_SERIAL3_TX) return RES_TX3; +#endif + return 0; +} + +/* + * Get the RX and TX pins numbers for the specified HW serial port. + */ +inline serial_pins getSerialPinNumbers(uint8_t portId) { + serial_pins pins; + switch (portId) { +#if defined(PIN_SERIAL_RX) + // case HW_SERIAL0: + // // TODO when use of HW_SERIAL0 is enabled + // break; +#endif +#if defined(PIN_SERIAL1_RX) + case HW_SERIAL1: + pins.rx = PIN_SERIAL1_RX; + pins.tx = PIN_SERIAL1_TX; + break; +#endif +#if defined(PIN_SERIAL2_RX) + case HW_SERIAL2: + pins.rx = PIN_SERIAL2_RX; + pins.tx = PIN_SERIAL2_TX; + break; +#endif +#if defined(PIN_SERIAL3_RX) + case HW_SERIAL3: + pins.rx = PIN_SERIAL3_RX; + pins.tx = PIN_SERIAL3_TX; + break; +#endif + default: + pins.rx = 0; + pins.tx = 0; + } + return pins; +} + + +class SerialFirmata: public FirmataFeature +{ + public: + SerialFirmata(); + boolean handlePinMode(byte pin, int mode); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte* argv); + void update(); + void reset(); + void checkSerial(); + + private: + byte reportSerial[MAX_SERIAL_PORTS]; + int serialBytesToRead[SERIAL_READ_ARR_LEN]; + signed char serialIndex; + + unsigned long lastReceive[SERIAL_READ_ARR_LEN]; + unsigned char maxCharDelay[SERIAL_READ_ARR_LEN]; + int lastAvailableBytes[SERIAL_READ_ARR_LEN]; + +#if defined(SoftwareSerial_h) + Stream *swSerial0; + Stream *swSerial1; + Stream *swSerial2; + Stream *swSerial3; +#endif + + Stream* getPortFromId(byte portId); + +}; + +#endif /* SerialFirmata_h */ diff --git a/firmware/configurable_firmata/src/ServoFirmata.cpp b/firmware/configurable_firmata/src/ServoFirmata.cpp new file mode 100644 index 0000000..307453f --- /dev/null +++ b/firmware/configurable_firmata/src/ServoFirmata.cpp @@ -0,0 +1,4 @@ +/* + * Implementation is in ServoFirmata.h to avoid having to include Servo.h in all + * sketch files that include ConfigurableFirmata.h + */ diff --git a/firmware/configurable_firmata/src/ServoFirmata.h b/firmware/configurable_firmata/src/ServoFirmata.h new file mode 100644 index 0000000..bac4ed9 --- /dev/null +++ b/firmware/configurable_firmata/src/ServoFirmata.h @@ -0,0 +1,151 @@ +/* + ServoFirmata.h - Firmata library + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2015 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + ServoFirmata.cpp has been merged into this header file as a hack to avoid having to + include Servo.h for every arduino sketch that includes ConfigurableFirmata. + + Last updated by Jeff Hoefs: November 15th, 2015 +*/ + +#ifndef ServoFirmata_h +#define ServoFirmata_h + +#include +#include +#include "FirmataFeature.h" + +void servoAnalogWrite(byte pin, int value); + +class ServoFirmata: public FirmataFeature +{ + public: + ServoFirmata(); + boolean analogWrite(byte pin, int value); + boolean handlePinMode(byte pin, int mode); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte* argv); + void reset(); + private: + Servo *servos[MAX_SERVOS]; + void attach(byte pin, int minPulse, int maxPulse); + void detach(byte pin); +}; + + +/* + * ServoFirmata.cpp + * Copied here as a hack to avoid having to include Servo.h in all sketch files that + * include ConfigurableFirmata.h + */ + +ServoFirmata *ServoInstance; + +void servoAnalogWrite(byte pin, int value) +{ + ServoInstance->analogWrite(pin, value); +} + +ServoFirmata::ServoFirmata() +{ + ServoInstance = this; +} + +boolean ServoFirmata::analogWrite(byte pin, int value) +{ + if (IS_PIN_SERVO(pin)) { + Servo *servo = servos[PIN_TO_SERVO(pin)]; + if (servo) { + servo->write(value); + return true; + } + } + return false; +} + +boolean ServoFirmata::handlePinMode(byte pin, int mode) +{ + if (IS_PIN_SERVO(pin)) { + if (mode == PIN_MODE_SERVO) { + attach(pin, -1, -1); + return true; + } else { + detach(pin); + } + } + return false; +} + +void ServoFirmata::handleCapability(byte pin) +{ + if (IS_PIN_SERVO(pin)) { + Firmata.write(PIN_MODE_SERVO); + Firmata.write(14); //14 bit resolution (Servo takes int as argument) + } +} + +boolean ServoFirmata::handleSysex(byte command, byte argc, byte* argv) +{ + if (command == SERVO_CONFIG) { + if (argc > 4) { + // these vars are here for clarity, they'll optimized away by the compiler + byte pin = argv[0]; + if (IS_PIN_SERVO(pin) && Firmata.getPinMode(pin) != PIN_MODE_IGNORE) { + int minPulse = argv[1] + (argv[2] << 7); + int maxPulse = argv[3] + (argv[4] << 7); + Firmata.setPinMode(pin, PIN_MODE_SERVO); + attach(pin, minPulse, maxPulse); + return true; + } + } + } + return false; +} + +void ServoFirmata::attach(byte pin, int minPulse, int maxPulse) +{ + Servo *servo = servos[PIN_TO_SERVO(pin)]; + if (!servo) { + servo = new Servo(); + servos[PIN_TO_SERVO(pin)] = servo; + } + if (servo->attached()) + servo->detach(); + if (minPulse >= 0 || maxPulse >= 0) + servo->attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse); + else + servo->attach(PIN_TO_DIGITAL(pin)); +} + +void ServoFirmata::detach(byte pin) +{ + Servo *servo = servos[PIN_TO_SERVO(pin)]; + if (servo) { + if (servo->attached()) + servo->detach(); + free(servo); + servos[PIN_TO_SERVO(pin)] = NULL; + } +} + +void ServoFirmata::reset() +{ + for (byte pin = 0; pin < TOTAL_PINS; pin++) { + if (IS_PIN_SERVO(pin)) { + detach(pin); + } + } +} + +#endif /* ServoFirmata_h */ diff --git a/firmware/configurable_firmata/src/StepperFirmata.cpp b/firmware/configurable_firmata/src/StepperFirmata.cpp new file mode 100644 index 0000000..a5618ff --- /dev/null +++ b/firmware/configurable_firmata/src/StepperFirmata.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated by Jeff Hoefs: January 23rd, 2016 +*/ + +#include +#include "StepperFirmata.h" +#include "utility/FirmataStepper.h" + +boolean StepperFirmata::handlePinMode(byte pin, int mode) +{ + if (mode == PIN_MODE_STEPPER) { + if (IS_PIN_DIGITAL(pin)) { + digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable PWM + pinMode(PIN_TO_DIGITAL(pin), OUTPUT); + return true; + } + } + return false; +} + +void StepperFirmata::handleCapability(byte pin) +{ + if (IS_PIN_DIGITAL(pin)) { + Firmata.write(PIN_MODE_STEPPER); + Firmata.write(21); //21 bits used for number of steps + } +} + +/*============================================================================== + * SYSEX-BASED commands + *============================================================================*/ + +boolean StepperFirmata::handleSysex(byte command, byte argc, byte *argv) +{ + if (command == STEPPER_DATA) { + byte stepCommand, deviceNum, directionPin, stepPin, stepDirection; + byte interface, interfaceType; + byte motorPin3, motorPin4; + unsigned int stepsPerRev; + long numSteps; + int stepSpeed; + int accel; + int decel; + + stepCommand = argv[0]; + deviceNum = argv[1]; + + if (deviceNum < MAX_STEPPERS) { + if (stepCommand == STEPPER_CONFIG) { + + interface = argv[2]; // upper 4 bits are the stepDelay, lower 4 bits are the interface type + interfaceType = interface & 0x0F; // the interface type is specified by the lower 4 bits + stepsPerRev = (argv[3] + (argv[4] << 7)); + + directionPin = argv[5]; // or motorPin1 for TWO_WIRE or FOUR_WIRE interface + stepPin = argv[6]; // // or motorPin2 for TWO_WIRE or FOUR_WIRE interface + if (Firmata.getPinMode(directionPin) == PIN_MODE_IGNORE || Firmata.getPinMode(stepPin) == PIN_MODE_IGNORE) + return false; + Firmata.setPinMode(directionPin, PIN_MODE_STEPPER); + Firmata.setPinMode(stepPin, PIN_MODE_STEPPER); + + if (!stepper[deviceNum]) { + numSteppers++; + } + if (interfaceType == FirmataStepper::DRIVER || interfaceType == FirmataStepper::TWO_WIRE) { + stepper[deviceNum] = new FirmataStepper(interface, stepsPerRev, directionPin, stepPin); + } else if (interfaceType == FirmataStepper::FOUR_WIRE) { + motorPin3 = argv[7]; + motorPin4 = argv[8]; + if (Firmata.getPinMode(motorPin3) == PIN_MODE_IGNORE || Firmata.getPinMode(motorPin4) == PIN_MODE_IGNORE) + return false; + Firmata.setPinMode(motorPin3, PIN_MODE_STEPPER); + Firmata.setPinMode(motorPin4, PIN_MODE_STEPPER); + stepper[deviceNum] = new FirmataStepper(interface, stepsPerRev, directionPin, stepPin, motorPin3, motorPin4); + } + } + else if (stepCommand == STEPPER_STEP) { + stepDirection = argv[2]; + numSteps = (long)argv[3] | ((long)argv[4] << 7) | ((long)argv[5] << 14); + stepSpeed = (argv[6] + (argv[7] << 7)); + + if (stepDirection == 0) { + numSteps *= -1; + } + if (stepper[deviceNum]) { + if (argc >= 8 && argc < 12) { + // num steps, speed (0.01*rad/sec) + stepper[deviceNum]->setStepsToMove(numSteps, stepSpeed); + } else if (argc == 12) { + accel = (argv[8] + (argv[9] << 7)); + decel = (argv[10] + (argv[11] << 7)); + // num steps, speed (0.01*rad/sec), accel (0.01*rad/sec^2), decel (0.01*rad/sec^2) + stepper[deviceNum]->setStepsToMove(numSteps, stepSpeed, accel, decel); + } + } + } + return true; + } + } + return false; +} + +/*============================================================================== + * SETUP() + *============================================================================*/ + +void StepperFirmata::reset() +{ + for (byte i = 0; i < MAX_STEPPERS; i++) { + if (stepper[i]) { + free(stepper[i]); + stepper[i] = 0; + } + } + numSteppers = 0; +} + +/*============================================================================== + * LOOP() + *============================================================================*/ +void StepperFirmata::update() +{ + if (numSteppers > 0) { + // if one or more stepper motors are used, update their position + for (byte i = 0; i < MAX_STEPPERS; i++) { + if (stepper[i]) { + bool done = stepper[i]->update(); + // send command to client application when stepping is complete + if (done) { + Firmata.write(START_SYSEX); + Firmata.write(STEPPER_DATA); + Firmata.write(i); + Firmata.write(END_SYSEX); + } + } + } + } +} diff --git a/firmware/configurable_firmata/src/StepperFirmata.h b/firmware/configurable_firmata/src/StepperFirmata.h new file mode 100644 index 0000000..7fc9459 --- /dev/null +++ b/firmware/configurable_firmata/src/StepperFirmata.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2013 Norbert Truchsess. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. +*/ + +#ifndef StepperFirmata_h +#define StepperFirmata_h + +#include +#include "utility/FirmataStepper.h" +#include "FirmataFeature.h" + +#define MAX_STEPPERS 6 // arbitrary value... may need to adjust +#define STEPPER_CONFIG 0 +#define STEPPER_STEP 1 + +class StepperFirmata: public FirmataFeature +{ + public: + boolean handlePinMode(byte pin, int mode); + void handleCapability(byte pin); + boolean handleSysex(byte command, byte argc, byte *argv); + void update(); + void reset(); + private: + FirmataStepper *stepper[MAX_STEPPERS]; + byte numSteppers; +}; + +#endif diff --git a/firmware/configurable_firmata/src/utility/AccelStepper.cpp b/firmware/configurable_firmata/src/utility/AccelStepper.cpp new file mode 100644 index 0000000..2bc17bd --- /dev/null +++ b/firmware/configurable_firmata/src/utility/AccelStepper.cpp @@ -0,0 +1,651 @@ +// AccelStepper.cpp +// +// Copyright (C) 2009-2013 Mike McCauley +// $Id: AccelStepper.cpp,v 1.23 2016/08/09 00:39:10 mikem Exp $ + +#include "AccelStepper.h" + +#if 0 +// Some debugging assistance +void dump(uint8_t* p, int l) +{ + int i; + + for (i = 0; i < l; i++) + { + Serial.print(p[i], HEX); + Serial.print(" "); + } + Serial.println(""); +} +#endif + +void AccelStepper::moveTo(long absolute) +{ + if (_targetPos != absolute) + { + _targetPos = absolute; + computeNewSpeed(); + // compute new n? + } +} + +void AccelStepper::move(long relative) +{ + moveTo(_currentPos + relative); +} + +// Implements steps according to the current step interval +// You must call this at least once per step +// returns true if a step occurred +boolean AccelStepper::runSpeed() +{ + // Dont do anything unless we actually have a step interval + if (!_stepInterval) + return false; + + unsigned long time = micros(); + if (time - _lastStepTime >= _stepInterval) + { + if (_direction == DIRECTION_CW) + { + // Clockwise + _currentPos += 1; + } + else + { + // Anticlockwise + _currentPos -= 1; + } + step(_currentPos); + + _lastStepTime = time; // Caution: does not account for costs in step() + + return true; + } + else + { + return false; + } +} + +long AccelStepper::distanceToGo() +{ + return _targetPos - _currentPos; +} + +long AccelStepper::targetPosition() +{ + return _targetPos; +} + +long AccelStepper::currentPosition() +{ + return _currentPos; +} + +// Useful during initialisations or after initial positioning +// Sets speed to 0 +void AccelStepper::setCurrentPosition(long position) +{ + _targetPos = _currentPos = position; + _n = 0; + _stepInterval = 0; + _speed = 0.0; +} + +void AccelStepper::computeNewSpeed() +{ + long distanceTo = distanceToGo(); // +ve is clockwise from curent location + + long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16 + + if (distanceTo == 0 && stepsToStop <= 1) + { + // We are at the target and its time to stop + _stepInterval = 0; + _speed = 0.0; + _n = 0; + return; + } + + if (distanceTo > 0) + { + // We are anticlockwise from the target + // Need to go clockwise from here, maybe decelerate now + if (_n > 0) + { + // Currently accelerating, need to decel now? Or maybe going the wrong way? + if ((stepsToStop >= distanceTo) || _direction == DIRECTION_CCW) + _n = -stepsToStop; // Start deceleration + } + else if (_n < 0) + { + // Currently decelerating, need to accel again? + if ((stepsToStop < distanceTo) && _direction == DIRECTION_CW) + _n = -_n; // Start accceleration + } + } + else if (distanceTo < 0) + { + // We are clockwise from the target + // Need to go anticlockwise from here, maybe decelerate + if (_n > 0) + { + // Currently accelerating, need to decel now? Or maybe going the wrong way? + if ((stepsToStop >= -distanceTo) || _direction == DIRECTION_CW) + _n = -stepsToStop; // Start deceleration + } + else if (_n < 0) + { + // Currently decelerating, need to accel again? + if ((stepsToStop < -distanceTo) && _direction == DIRECTION_CCW) + _n = -_n; // Start accceleration + } + } + + // Need to accelerate or decelerate + if (_n == 0) + { + // First step from stopped + _cn = _c0; + _direction = (distanceTo > 0) ? DIRECTION_CW : DIRECTION_CCW; + } + else + { + // Subsequent step. Works for accel (n is +_ve) and decel (n is -ve). + _cn = _cn - ((2.0 * _cn) / ((4.0 * _n) + 1)); // Equation 13 + _cn = max(_cn, _cmin); + } + _n++; + _stepInterval = _cn; + _speed = 1000000.0 / _cn; + if (_direction == DIRECTION_CCW) + _speed = -_speed; + +#if 0 + Serial.println(_speed); + Serial.println(_acceleration); + Serial.println(_cn); + Serial.println(_c0); + Serial.println(_n); + Serial.println(_stepInterval); + Serial.println(distanceTo); + Serial.println(stepsToStop); + Serial.println("-----"); +#endif +} + +// Run the motor to implement speed and acceleration in order to proceed to the target position +// You must call this at least once per step, preferably in your main loop +// If the motor is in the desired position, the cost is very small +// returns true if the motor is still running to the target position. +boolean AccelStepper::run() +{ + if (runSpeed()) + computeNewSpeed(); + return _speed != 0.0 || distanceToGo() != 0; +} + +AccelStepper::AccelStepper(uint8_t interface, uint8_t pin1, uint8_t pin2, uint8_t pin3, uint8_t pin4, bool enable) +{ + _interface = interface; + _currentPos = 0; + _targetPos = 0; + _speed = 0.0; + _maxSpeed = 1.0; + _acceleration = 0.0; + _sqrt_twoa = 1.0; + _stepInterval = 0; + _minPulseWidth = 1; + _enablePin = 0xff; + _lastStepTime = 0; + _pin[0] = pin1; + _pin[1] = pin2; + _pin[2] = pin3; + _pin[3] = pin4; + + // NEW + _n = 0; + _c0 = 0.0; + _cn = 0.0; + _cmin = 1.0; + _direction = DIRECTION_CCW; + + int i; + for (i = 0; i < 4; i++) + _pinInverted[i] = 0; + if (enable) + enableOutputs(); + // Some reasonable default + setAcceleration(1); +} + +AccelStepper::AccelStepper(void (*forward)(), void (*backward)()) +{ + _interface = 0; + _currentPos = 0; + _targetPos = 0; + _speed = 0.0; + _maxSpeed = 1.0; + _acceleration = 0.0; + _sqrt_twoa = 1.0; + _stepInterval = 0; + _minPulseWidth = 1; + _enablePin = 0xff; + _lastStepTime = 0; + _pin[0] = 0; + _pin[1] = 0; + _pin[2] = 0; + _pin[3] = 0; + _forward = forward; + _backward = backward; + + // NEW + _n = 0; + _c0 = 0.0; + _cn = 0.0; + _cmin = 1.0; + _direction = DIRECTION_CCW; + + int i; + for (i = 0; i < 4; i++) + _pinInverted[i] = 0; + // Some reasonable default + setAcceleration(1); +} + +void AccelStepper::setMaxSpeed(float speed) +{ + if (speed < 0.0) + speed = -speed; + if (_maxSpeed != speed) + { + _maxSpeed = speed; + _cmin = 1000000.0 / speed; + // Recompute _n from current speed and adjust speed if accelerating or cruising + if (_n > 0) + { + _n = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16 + computeNewSpeed(); + } + } +} + +float AccelStepper::maxSpeed() +{ + return _maxSpeed; +} + +void AccelStepper::setAcceleration(float acceleration) +{ + if (acceleration == 0.0) + return; + if (acceleration < 0.0) + acceleration = -acceleration; + if (_acceleration != acceleration) + { + // Recompute _n per Equation 17 + _n = _n * (_acceleration / acceleration); + // New c0 per Equation 7, with correction per Equation 15 + _c0 = 0.676 * sqrt(2.0 / acceleration) * 1000000.0; // Equation 15 + _acceleration = acceleration; + computeNewSpeed(); + } +} + +void AccelStepper::setSpeed(float speed) +{ + if (speed == _speed) + return; + speed = constrain(speed, -_maxSpeed, _maxSpeed); + if (speed == 0.0) + _stepInterval = 0; + else + { + _stepInterval = fabs(1000000.0 / speed); + _direction = (speed > 0.0) ? DIRECTION_CW : DIRECTION_CCW; + } + _speed = speed; +} + +float AccelStepper::speed() +{ + return _speed; +} + +// Subclasses can override +void AccelStepper::step(long step) +{ + switch (_interface) + { + case FUNCTION: + step0(step); + break; + + case DRIVER: + step1(step); + break; + + case FULL2WIRE: + step2(step); + break; + + case FULL3WIRE: + step3(step); + break; + + case FULL4WIRE: + step4(step); + break; + + case HALF3WIRE: + step6(step); + break; + + case HALF4WIRE: + step8(step); + break; + } +} + +// You might want to override this to implement eg serial output +// bit 0 of the mask corresponds to _pin[0] +// bit 1 of the mask corresponds to _pin[1] +// .... +void AccelStepper::setOutputPins(uint8_t mask) +{ + uint8_t numpins = 2; + if (_interface == FULL4WIRE || _interface == HALF4WIRE) + numpins = 4; + else if (_interface == FULL3WIRE || _interface == HALF3WIRE) + numpins = 3; + uint8_t i; + for (i = 0; i < numpins; i++) + digitalWrite(_pin[i], (mask & (1 << i)) ? (HIGH ^ _pinInverted[i]) : (LOW ^ _pinInverted[i])); +} + +// 0 pin step function (ie for functional usage) +void AccelStepper::step0(long step) +{ + (void)(step); // Unused + if (_speed > 0) + _forward(); + else + _backward(); +} + +// 1 pin step function (ie for stepper drivers) +// This is passed the current step number (0 to 7) +// Subclasses can override +void AccelStepper::step1(long step) +{ + (void)(step); // Unused + + // _pin[0] is step, _pin[1] is direction + setOutputPins(_direction ? 0b10 : 0b00); // Set direction first else get rogue pulses + setOutputPins(_direction ? 0b11 : 0b01); // step HIGH + // Caution 200ns setup time + // Delay the minimum allowed pulse width + delayMicroseconds(_minPulseWidth); + setOutputPins(_direction ? 0b10 : 0b00); // step LOW +} + + +// 2 pin step function +// This is passed the current step number (0 to 7) +// Subclasses can override +void AccelStepper::step2(long step) +{ + switch (step & 0x3) + { + case 0: /* 01 */ + setOutputPins(0b10); + break; + + case 1: /* 11 */ + setOutputPins(0b11); + break; + + case 2: /* 10 */ + setOutputPins(0b01); + break; + + case 3: /* 00 */ + setOutputPins(0b00); + break; + } +} +// 3 pin step function +// This is passed the current step number (0 to 7) +// Subclasses can override +void AccelStepper::step3(long step) +{ + switch (step % 3) + { + case 0: // 100 + setOutputPins(0b100); + break; + + case 1: // 001 + setOutputPins(0b001); + break; + + case 2: //010 + setOutputPins(0b010); + break; + + } +} + +// 4 pin step function for half stepper +// This is passed the current step number (0 to 7) +// Subclasses can override +void AccelStepper::step4(long step) +{ + switch (step & 0x3) + { + case 0: // 1010 + setOutputPins(0b0101); + break; + + case 1: // 0110 + setOutputPins(0b0110); + break; + + case 2: //0101 + setOutputPins(0b1010); + break; + + case 3: //1001 + setOutputPins(0b1001); + break; + } +} + +// 3 pin half step function +// This is passed the current step number (0 to 7) +// Subclasses can override +void AccelStepper::step6(long step) +{ + switch (step % 6) + { + case 0: // 100 + setOutputPins(0b100); + break; + + case 1: // 101 + setOutputPins(0b101); + break; + + case 2: // 001 + setOutputPins(0b001); + break; + + case 3: // 011 + setOutputPins(0b011); + break; + + case 4: // 010 + setOutputPins(0b010); + break; + + case 5: // 011 + setOutputPins(0b110); + break; + + } +} + +// 4 pin half step function +// This is passed the current step number (0 to 7) +// Subclasses can override +void AccelStepper::step8(long step) +{ + switch (step & 0x7) + { + case 0: // 1000 + setOutputPins(0b0001); + break; + + case 1: // 1010 + setOutputPins(0b0101); + break; + + case 2: // 0010 + setOutputPins(0b0100); + break; + + case 3: // 0110 + setOutputPins(0b0110); + break; + + case 4: // 0100 + setOutputPins(0b0010); + break; + + case 5: //0101 + setOutputPins(0b1010); + break; + + case 6: // 0001 + setOutputPins(0b1000); + break; + + case 7: //1001 + setOutputPins(0b1001); + break; + } +} + +// Prevents power consumption on the outputs +void AccelStepper::disableOutputs() +{ + if (! _interface) return; + + setOutputPins(0); // Handles inversion automatically + if (_enablePin != 0xff) + { + pinMode(_enablePin, OUTPUT); + digitalWrite(_enablePin, LOW ^ _enableInverted); + } +} + +void AccelStepper::enableOutputs() +{ + if (! _interface) + return; + + pinMode(_pin[0], OUTPUT); + pinMode(_pin[1], OUTPUT); + if (_interface == FULL4WIRE || _interface == HALF4WIRE) + { + pinMode(_pin[2], OUTPUT); + pinMode(_pin[3], OUTPUT); + } + else if (_interface == FULL3WIRE || _interface == HALF3WIRE) + { + pinMode(_pin[2], OUTPUT); + } + + if (_enablePin != 0xff) + { + pinMode(_enablePin, OUTPUT); + digitalWrite(_enablePin, HIGH ^ _enableInverted); + } +} + +void AccelStepper::setMinPulseWidth(unsigned int minWidth) +{ + _minPulseWidth = minWidth; +} + +void AccelStepper::setEnablePin(uint8_t enablePin) +{ + _enablePin = enablePin; + + // This happens after construction, so init pin now. + if (_enablePin != 0xff) + { + pinMode(_enablePin, OUTPUT); + digitalWrite(_enablePin, HIGH ^ _enableInverted); + } +} + +void AccelStepper::setPinsInverted(bool directionInvert, bool stepInvert, bool enableInvert) +{ + _pinInverted[0] = stepInvert; + _pinInverted[1] = directionInvert; + _enableInverted = enableInvert; +} + +void AccelStepper::setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert) +{ + _pinInverted[0] = pin1Invert; + _pinInverted[1] = pin2Invert; + _pinInverted[2] = pin3Invert; + _pinInverted[3] = pin4Invert; + _enableInverted = enableInvert; +} + +// Blocks until the target position is reached and stopped +void AccelStepper::runToPosition() +{ + while (run()) + ; +} + +boolean AccelStepper::runSpeedToPosition() +{ + if (_targetPos == _currentPos) + return false; + if (_targetPos >_currentPos) + _direction = DIRECTION_CW; + else + _direction = DIRECTION_CCW; + return runSpeed(); +} + +// Blocks until the new target position is reached +void AccelStepper::runToNewPosition(long position) +{ + moveTo(position); + runToPosition(); +} + +void AccelStepper::stop() +{ + if (_speed != 0.0) + { + long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)) + 1; // Equation 16 (+integer rounding) + if (_speed > 0) + move(stepsToStop); + else + move(-stepsToStop); + } +} + +bool AccelStepper::isRunning() +{ + return !(_speed == 0.0 && _targetPos == _currentPos); +} diff --git a/firmware/configurable_firmata/src/utility/AccelStepper.h b/firmware/configurable_firmata/src/utility/AccelStepper.h new file mode 100644 index 0000000..c4fd852 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/AccelStepper.h @@ -0,0 +1,728 @@ +// AccelStepper.h +// +/// \mainpage AccelStepper library for Arduino +/// +/// This is the Arduino AccelStepper library. +/// It provides an object-oriented interface for 2, 3 or 4 pin stepper motors and motor drivers. +/// +/// The standard Arduino IDE includes the Stepper library +/// (http://arduino.cc/en/Reference/Stepper) for stepper motors. It is +/// perfectly adequate for simple, single motor applications. +/// +/// AccelStepper significantly improves on the standard Arduino Stepper library in several ways: +/// \li Supports acceleration and deceleration +/// \li Supports multiple simultaneous steppers, with independent concurrent stepping on each stepper +/// \li API functions never delay() or block +/// \li Supports 2, 3 and 4 wire steppers, plus 3 and 4 wire half steppers. +/// \li Supports alternate stepping functions to enable support of AFMotor (https://github.com/adafruit/Adafruit-Motor-Shield-library) +/// \li Supports stepper drivers such as the Sparkfun EasyDriver (based on 3967 driver chip) +/// \li Very slow speeds are supported +/// \li Extensive API +/// \li Subclass support +/// +/// The latest version of this documentation can be downloaded from +/// http://www.airspayce.com/mikem/arduino/AccelStepper +/// The version of the package that this documentation refers to can be downloaded +/// from http://www.airspayce.com/mikem/arduino/AccelStepper/AccelStepper-1.57.zip +/// +/// Example Arduino programs are included to show the main modes of use. +/// +/// You can also find online help and discussion at http://groups.google.com/group/accelstepper +/// Please use that group for all questions and discussions on this topic. +/// Do not contact the author directly, unless it is to discuss commercial licensing. +/// Before asking a question or reporting a bug, please read +/// - http://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question +/// - http://www.catb.org/esr/faqs/smart-questions.html +/// - http://www.chiark.greenend.org.uk/~shgtatham/bugs.html +/// +/// Tested on Arduino Diecimila and Mega with arduino-0018 & arduino-0021 +/// on OpenSuSE 11.1 and avr-libc-1.6.1-1.15, +/// cross-avr-binutils-2.19-9.1, cross-avr-gcc-4.1.3_20080612-26.5. +/// Tested on Teensy http://www.pjrc.com/teensy including Teensy 3.1 built using Arduino IDE 1.0.5 with +/// teensyduino addon 1.18 and later. +/// +/// \par Installation +/// +/// Install in the usual way: unzip the distribution zip file to the libraries +/// sub-folder of your sketchbook. +/// +/// \par Theory +/// +/// This code uses speed calculations as described in +/// "Generate stepper-motor speed profiles in real time" by David Austin +/// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf or +/// http://www.embedded.com/design/mcus-processors-and-socs/4006438/Generate-stepper-motor-speed-profiles-in-real-time or +/// http://web.archive.org/web/20140705143928/http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf +/// with the exception that AccelStepper uses steps per second rather than radians per second +/// (because we dont know the step angle of the motor) +/// An initial step interval is calculated for the first step, based on the desired acceleration +/// On subsequent steps, shorter step intervals are calculated based +/// on the previous step until max speed is achieved. +/// +/// \par Adafruit Motor Shield V2 +/// +/// The included examples AFMotor_* are for Adafruit Motor Shield V1 and do not work with Adafruit Motor Shield V2. +/// See https://github.com/adafruit/Adafruit_Motor_Shield_V2_Library for examples that work with Adafruit Motor Shield V2. +/// +/// \par Donations +/// +/// This library is offered under a free GPL license for those who want to use it that way. +/// We try hard to keep it up to date, fix bugs +/// and to provide free support. If this library has helped you save time or money, please consider donating at +/// http://www.airspayce.com or here: +/// +/// \htmlonly
\endhtmlonly +/// +/// \par Trademarks +/// +/// AccelStepper is a trademark of AirSpayce Pty Ltd. The AccelStepper mark was first used on April 26 2010 for +/// international trade, and is used only in relation to motor control hardware and software. +/// It is not to be confused with any other similar marks covering other goods and services. +/// +/// \par Copyright +/// +/// This software is Copyright (C) 2010 Mike McCauley. Use is subject to license +/// conditions. The main licensing options available are GPL V2 or Commercial: +/// +/// \par Open Source Licensing GPL V2 +/// This is the appropriate option if you want to share the source code of your +/// application with everyone you distribute it to, and you also want to give them +/// the right to share who uses it. If you wish to use this software under Open +/// Source Licensing, you must contribute all your source code to the open source +/// community in accordance with the GPL Version 2 when your application is +/// distributed. See https://www.gnu.org/licenses/gpl-2.0.html +/// +/// \par Commercial Licensing +/// This is the appropriate option if you are creating proprietary applications +/// and you are not prepared to distribute and share the source code of your +/// application. Purchase commercial licenses at http://airspayce.binpress.com/ +/// +/// \par Revision History +/// \version 1.0 Initial release +/// +/// \version 1.1 Added speed() function to get the current speed. +/// \version 1.2 Added runSpeedToPosition() submitted by Gunnar Arndt. +/// \version 1.3 Added support for stepper drivers (ie with Step and Direction inputs) with _pins == 1 +/// \version 1.4 Added functional contructor to support AFMotor, contributed by Limor, with example sketches. +/// \version 1.5 Improvements contributed by Peter Mousley: Use of microsecond steps and other speed improvements +/// to increase max stepping speed to about 4kHz. New option for user to set the min allowed pulse width. +/// Added checks for already running at max speed and skip further calcs if so. +/// \version 1.6 Fixed a problem with wrapping of microsecond stepping that could cause stepping to hang. +/// Reported by Sandy Noble. +/// Removed redundant _lastRunTime member. +/// \version 1.7 Fixed a bug where setCurrentPosition() did not always work as expected. +/// Reported by Peter Linhart. +/// \version 1.8 Added support for 4 pin half-steppers, requested by Harvey Moon +/// \version 1.9 setCurrentPosition() now also sets motor speed to 0. +/// \version 1.10 Builds on Arduino 1.0 +/// \version 1.11 Improvments from Michael Ellison: +/// Added optional enable line support for stepper drivers +/// Added inversion for step/direction/enable lines for stepper drivers +/// \version 1.12 Announce Google Group +/// \version 1.13 Improvements to speed calculation. Cost of calculation is now less in the worst case, +/// and more or less constant in all cases. This should result in slightly beter high speed performance, and +/// reduce anomalous speed glitches when other steppers are accelerating. +/// However, its hard to see how to replace the sqrt() required at the very first step from 0 speed. +/// \version 1.14 Fixed a problem with compiling under arduino 0021 reported by EmbeddedMan +/// \version 1.15 Fixed a problem with runSpeedToPosition which did not correctly handle +/// running backwards to a smaller target position. Added examples +/// \version 1.16 Fixed some cases in the code where abs() was used instead of fabs(). +/// \version 1.17 Added example ProportionalControl +/// \version 1.18 Fixed a problem: If one calls the funcion runSpeed() when Speed is zero, it makes steps +/// without counting. reported by Friedrich, Klappenbach. +/// \version 1.19 Added MotorInterfaceType and symbolic names for the number of pins to use +/// for the motor interface. Updated examples to suit. +/// Replaced individual pin assignment variables _pin1, _pin2 etc with array _pin[4]. +/// _pins member changed to _interface. +/// Added _pinInverted array to simplify pin inversion operations. +/// Added new function setOutputPins() which sets the motor output pins. +/// It can be overridden in order to provide, say, serial output instead of parallel output +/// Some refactoring and code size reduction. +/// \version 1.20 Improved documentation and examples to show need for correctly +/// specifying AccelStepper::FULL4WIRE and friends. +/// \version 1.21 Fixed a problem where desiredSpeed could compute the wrong step acceleration +/// when _speed was small but non-zero. Reported by Brian Schmalz. +/// Precompute sqrt_twoa to improve performance and max possible stepping speed +/// \version 1.22 Added Bounce.pde example +/// Fixed a problem where calling moveTo(), setMaxSpeed(), setAcceleration() more +/// frequently than the step time, even +/// with the same values, would interfere with speed calcs. Now a new speed is computed +/// only if there was a change in the set value. Reported by Brian Schmalz. +/// \version 1.23 Rewrite of the speed algorithms in line with +/// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf +/// Now expect smoother and more linear accelerations and decelerations. The desiredSpeed() +/// function was removed. +/// \version 1.24 Fixed a problem introduced in 1.23: with runToPosition, which did never returned +/// \version 1.25 Now ignore attempts to set acceleration to 0.0 +/// \version 1.26 Fixed a problem where certina combinations of speed and accelration could cause +/// oscillation about the target position. +/// \version 1.27 Added stop() function to stop as fast as possible with current acceleration parameters. +/// Also added new Quickstop example showing its use. +/// \version 1.28 Fixed another problem where certain combinations of speed and accelration could cause +/// oscillation about the target position. +/// Added support for 3 wire full and half steppers such as Hard Disk Drive spindle. +/// Contributed by Yuri Ivatchkovitch. +/// \version 1.29 Fixed a problem that could cause a DRIVER stepper to continually step +/// with some sketches. Reported by Vadim. +/// \version 1.30 Fixed a problem that could cause stepper to back up a few steps at the end of +/// accelerated travel with certain speeds. Reported and patched by jolo. +/// \version 1.31 Updated author and distribution location details to airspayce.com +/// \version 1.32 Fixed a problem with enableOutputs() and setEnablePin on Arduino Due that +/// prevented the enable pin changing stae correctly. Reported by Duane Bishop. +/// \version 1.33 Fixed an error in example AFMotor_ConstantSpeed.pde did not setMaxSpeed(); +/// Fixed a problem that caused incorrect pin sequencing of FULL3WIRE and HALF3WIRE. +/// Unfortunately this meant changing the signature for all step*() functions. +/// Added example MotorShield, showing how to use AdaFruit Motor Shield to control +/// a 3 phase motor such as a HDD spindle motor (and without using the AFMotor library. +/// \version 1.34 Added setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert) +/// to allow inversion of 2, 3 and 4 wire stepper pins. Requested by Oleg. +/// \version 1.35 Removed default args from setPinsInverted(bool, bool, bool, bool, bool) to prevent ambiguity with +/// setPinsInverted(bool, bool, bool). Reported by Mac Mac. +/// \version 1.36 Changed enableOutputs() and disableOutputs() to be virtual so can be overridden. +/// Added new optional argument 'enable' to constructor, which allows you toi disable the +/// automatic enabling of outputs at construction time. Suggested by Guido. +/// \version 1.37 Fixed a problem with step1 that could cause a rogue step in the +/// wrong direction (or not, +/// depending on the setup-time requirements of the connected hardware). +/// Reported by Mark Tillotson. +/// \version 1.38 run() function incorrectly always returned true. Updated function and doc so it returns true +/// if the motor is still running to the target position. +/// \version 1.39 Updated typos in keywords.txt, courtesey Jon Magill. +/// \version 1.40 Updated documentation, including testing on Teensy 3.1 +/// \version 1.41 Fixed an error in the acceleration calculations, resulting in acceleration of haldf the intended value +/// \version 1.42 Improved support for FULL3WIRE and HALF3WIRE output pins. These changes were in Yuri's original +/// contribution but did not make it into production.
+/// \version 1.43 Added DualMotorShield example. Shows how to use AccelStepper to control 2 x 2 phase steppers using the +/// Itead Studio Arduino Dual Stepper Motor Driver Shield model IM120417015.
+/// \version 1.44 examples/DualMotorShield/DualMotorShield.ino examples/DualMotorShield/DualMotorShield.pde +/// was missing from the distribution.
+/// \version 1.45 Fixed a problem where if setAcceleration was not called, there was no default +/// acceleration. Reported by Michael Newman.
+/// \version 1.45 Fixed inaccuracy in acceleration rate by using Equation 15, suggested by Sebastian Gracki.
+/// Performance improvements in runSpeed suggested by Jaakko Fagerlund.
+/// \version 1.46 Fixed error in documentation for runToPosition(). +/// Reinstated time calculations in runSpeed() since new version is reported +/// not to work correctly under some circumstances. Reported by Oleg V Gavva.
+/// \version 1.48 2015-08-25 +/// Added new class MultiStepper that can manage multiple AccelSteppers, +/// and cause them all to move +/// to selected positions at such a (constant) speed that they all arrive at their +/// target position at the same time. Suitable for X-Y flatbeds etc.
+/// Added new method maxSpeed() to AccelStepper to return the currently configured maxSpeed.
+/// \version 1.49 2016-01-02 +/// Testing with VID28 series instrument stepper motors and EasyDriver. +/// OK, although with light pointers +/// and slow speeds like 180 full steps per second the motor movement can be erratic, +/// probably due to some mechanical resonance. Best to accelerate through this speed.
+/// Added isRunning().
+/// \version 1.50 2016-02-25 +/// AccelStepper::disableOutputs now sets the enable pion to OUTPUT mode if the enable pin is defined. +/// Patch from Piet De Jong.
+/// Added notes about the fact that AFMotor_* examples do not work with Adafruit Motor Shield V2.
+/// \version 1.51 2016-03-24 +/// Fixed a problem reported by gregor: when resetting the stepper motor position using setCurrentPosition() the +/// stepper speed is reset by setting _stepInterval to 0, but _speed is not +/// reset. this results in the stepper motor not starting again when calling +/// setSpeed() with the same speed the stepper was set to before. +/// \version 1.52 2016-08-09 +/// Added MultiStepper to keywords.txt. +/// Improvements to efficiency of AccelStepper::runSpeed() as suggested by David Grayson. +/// Improvements to speed accuracy as suggested by David Grayson. +/// \version 1.53 2016-08-14 +/// Backed out Improvements to speed accuracy from 1.52 as it did not work correctly. +/// \version 1.54 2017-01-24 +/// Fixed some warnings about unused arguments. +/// \version 1.55 2017-01-25 +/// Fixed another warning in MultiStepper.cpp +/// \version 1.56 2017-02-03 +/// Fixed minor documentation error with DIRECTION_CCW and DIRECTION_CW. Reported by David Mutterer. +/// Added link to Binpress commercial license purchasing. +/// \version 1.57 2017-03-28 +/// _direction moved to protected at the request of Rudy Ercek. +/// setMaxSpeed() and setAcceleration() now correct negative values to be positive. +/// +/// \author Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY: USE THE LISTS +// Copyright (C) 2009-2013 Mike McCauley +// $Id: AccelStepper.h,v 1.27 2016/08/14 10:26:54 mikem Exp mikem $ + +#ifndef AccelStepper_h +#define AccelStepper_h + +#include +#if ARDUINO >= 100 +#include +#else +#include +#include +#endif + +// These defs cause trouble on some versions of Arduino +#undef round + +///////////////////////////////////////////////////////////////////// +/// \class AccelStepper AccelStepper.h +/// \brief Support for stepper motors with acceleration etc. +/// +/// This defines a single 2 or 4 pin stepper motor, or stepper moter with fdriver chip, with optional +/// acceleration, deceleration, absolute positioning commands etc. Multiple +/// simultaneous steppers are supported, all moving +/// at different speeds and accelerations. +/// +/// \par Operation +/// This module operates by computing a step time in microseconds. The step +/// time is recomputed after each step and after speed and acceleration +/// parameters are changed by the caller. The time of each step is recorded in +/// microseconds. The run() function steps the motor once if a new step is due. +/// The run() function must be called frequently until the motor is in the +/// desired position, after which time run() will do nothing. +/// +/// \par Positioning +/// Positions are specified by a signed long integer. At +/// construction time, the current position of the motor is consider to be 0. Positive +/// positions are clockwise from the initial position; negative positions are +/// anticlockwise. The current position can be altered for instance after +/// initialization positioning. +/// +/// \par Caveats +/// This is an open loop controller: If the motor stalls or is oversped, +/// AccelStepper will not have a correct +/// idea of where the motor really is (since there is no feedback of the motor's +/// real position. We only know where we _think_ it is, relative to the +/// initial starting point). +/// +/// \par Performance +/// The fastest motor speed that can be reliably supported is about 4000 steps per +/// second at a clock frequency of 16 MHz on Arduino such as Uno etc. +/// Faster processors can support faster stepping speeds. +/// However, any speed less than that +/// down to very slow speeds (much less than one per second) are also supported, +/// provided the run() function is called frequently enough to step the motor +/// whenever required for the speed set. +/// Calling setAcceleration() is expensive, +/// since it requires a square root to be calculated. +/// +/// Gregor Christandl reports that with an Arduino Due and a simple test program, +/// he measured 43163 steps per second using runSpeed(), +/// and 16214 steps per second using run(); +class AccelStepper +{ +public: + /// \brief Symbolic names for number of pins. + /// Use this in the pins argument the AccelStepper constructor to + /// provide a symbolic name for the number of pins + /// to use. + typedef enum + { + FUNCTION = 0, ///< Use the functional interface, implementing your own driver functions (internal use only) + DRIVER = 1, ///< Stepper Driver, 2 driver pins required + FULL2WIRE = 2, ///< 2 wire stepper, 2 motor pins required + FULL3WIRE = 3, ///< 3 wire stepper, such as HDD spindle, 3 motor pins required + FULL4WIRE = 4, ///< 4 wire full stepper, 4 motor pins required + HALF3WIRE = 6, ///< 3 wire half stepper, such as HDD spindle, 3 motor pins required + HALF4WIRE = 8 ///< 4 wire half stepper, 4 motor pins required + } MotorInterfaceType; + + /// Constructor. You can have multiple simultaneous steppers, all moving + /// at different speeds and accelerations, provided you call their run() + /// functions at frequent enough intervals. Current Position is set to 0, target + /// position is set to 0. MaxSpeed and Acceleration default to 1.0. + /// The motor pins will be initialised to OUTPUT mode during the + /// constructor by a call to enableOutputs(). + /// \param[in] interface Number of pins to interface to. Integer values are + /// supported, but it is preferred to use the \ref MotorInterfaceType symbolic names. + /// AccelStepper::DRIVER (1) means a stepper driver (with Step and Direction pins). + /// If an enable line is also needed, call setEnablePin() after construction. + /// You may also invert the pins using setPinsInverted(). + /// AccelStepper::FULL2WIRE (2) means a 2 wire stepper (2 pins required). + /// AccelStepper::FULL3WIRE (3) means a 3 wire stepper, such as HDD spindle (3 pins required). + /// AccelStepper::FULL4WIRE (4) means a 4 wire stepper (4 pins required). + /// AccelStepper::HALF3WIRE (6) means a 3 wire half stepper, such as HDD spindle (3 pins required) + /// AccelStepper::HALF4WIRE (8) means a 4 wire half stepper (4 pins required) + /// Defaults to AccelStepper::FULL4WIRE (4) pins. + /// \param[in] pin1 Arduino digital pin number for motor pin 1. Defaults + /// to pin 2. For a AccelStepper::DRIVER (interface==1), + /// this is the Step input to the driver. Low to high transition means to step) + /// \param[in] pin2 Arduino digital pin number for motor pin 2. Defaults + /// to pin 3. For a AccelStepper::DRIVER (interface==1), + /// this is the Direction input the driver. High means forward. + /// \param[in] pin3 Arduino digital pin number for motor pin 3. Defaults + /// to pin 4. + /// \param[in] pin4 Arduino digital pin number for motor pin 4. Defaults + /// to pin 5. + /// \param[in] enable If this is true (the default), enableOutputs() will be called to enable + /// the output pins at construction time. + AccelStepper(uint8_t interface = AccelStepper::FULL4WIRE, uint8_t pin1 = 2, uint8_t pin2 = 3, uint8_t pin3 = 4, uint8_t pin4 = 5, bool enable = true); + + /// Alternate Constructor which will call your own functions for forward and backward steps. + /// You can have multiple simultaneous steppers, all moving + /// at different speeds and accelerations, provided you call their run() + /// functions at frequent enough intervals. Current Position is set to 0, target + /// position is set to 0. MaxSpeed and Acceleration default to 1.0. + /// Any motor initialization should happen before hand, no pins are used or initialized. + /// \param[in] forward void-returning procedure that will make a forward step + /// \param[in] backward void-returning procedure that will make a backward step + AccelStepper(void (*forward)(), void (*backward)()); + + /// Set the target position. The run() function will try to move the motor (at most one step per call) + /// from the current position to the target position set by the most + /// recent call to this function. Caution: moveTo() also recalculates the speed for the next step. + /// If you are trying to use constant speed movements, you should call setSpeed() after calling moveTo(). + /// \param[in] absolute The desired absolute position. Negative is + /// anticlockwise from the 0 position. + void moveTo(long absolute); + + /// Set the target position relative to the current position + /// \param[in] relative The desired position relative to the current position. Negative is + /// anticlockwise from the current position. + void move(long relative); + + /// Poll the motor and step it if a step is due, implementing + /// accelerations and decelerations to acheive the target position. You must call this as + /// frequently as possible, but at least once per minimum step time interval, + /// preferably in your main loop. Note that each call to run() will make at most one step, and then only when a step is due, + /// based on the current speed and the time since the last step. + /// \return true if the motor is still running to the target position. + boolean run(); + + /// Poll the motor and step it if a step is due, implementing a constant + /// speed as set by the most recent call to setSpeed(). You must call this as + /// frequently as possible, but at least once per step interval, + /// \return true if the motor was stepped. + boolean runSpeed(); + + /// Sets the maximum permitted speed. The run() function will accelerate + /// up to the speed set by this function. + /// Caution: the maximum speed achievable depends on your processor and clock speed. + /// \param[in] speed The desired maximum speed in steps per second. Must + /// be > 0. Caution: Speeds that exceed the maximum speed supported by the processor may + /// Result in non-linear accelerations and decelerations. + void setMaxSpeed(float speed); + + /// returns the maximum speed configured for this stepper + /// that was previously set by setMaxSpeed(); + /// \return The currently configured maximum speed + float maxSpeed(); + + /// Sets the acceleration/deceleration rate. + /// \param[in] acceleration The desired acceleration in steps per second + /// per second. Must be > 0.0. This is an expensive call since it requires a square + /// root to be calculated. Dont call more ofthen than needed + void setAcceleration(float acceleration); + + /// Sets the desired constant speed for use with runSpeed(). + /// \param[in] speed The desired constant speed in steps per + /// second. Positive is clockwise. Speeds of more than 1000 steps per + /// second are unreliable. Very slow speeds may be set (eg 0.00027777 for + /// once per hour, approximately. Speed accuracy depends on the Arduino + /// crystal. Jitter depends on how frequently you call the runSpeed() function. + void setSpeed(float speed); + + /// The most recently set speed + /// \return the most recent speed in steps per second + float speed(); + + /// The distance from the current position to the target position. + /// \return the distance from the current position to the target position + /// in steps. Positive is clockwise from the current position. + long distanceToGo(); + + /// The most recently set target position. + /// \return the target position + /// in steps. Positive is clockwise from the 0 position. + long targetPosition(); + + /// The currently motor position. + /// \return the current motor position + /// in steps. Positive is clockwise from the 0 position. + long currentPosition(); + + /// Resets the current position of the motor, so that wherever the motor + /// happens to be right now is considered to be the new 0 position. Useful + /// for setting a zero position on a stepper after an initial hardware + /// positioning move. + /// Has the side effect of setting the current motor speed to 0. + /// \param[in] position The position in steps of wherever the motor + /// happens to be right now. + void setCurrentPosition(long position); + + /// Moves the motor (with acceleration/deceleration) + /// to the target position and blocks until it is at + /// position. Dont use this in event loops, since it blocks. + void runToPosition(); + + /// Runs at the currently selected speed until the target position is reached + /// Does not implement accelerations. + /// \return true if it stepped + boolean runSpeedToPosition(); + + /// Moves the motor (with acceleration/deceleration) + /// to the new target position and blocks until it is at + /// position. Dont use this in event loops, since it blocks. + /// \param[in] position The new target position. + void runToNewPosition(long position); + + /// Sets a new target position that causes the stepper + /// to stop as quickly as possible, using the current speed and acceleration parameters. + void stop(); + + /// Disable motor pin outputs by setting them all LOW + /// Depending on the design of your electronics this may turn off + /// the power to the motor coils, saving power. + /// This is useful to support Arduino low power modes: disable the outputs + /// during sleep and then reenable with enableOutputs() before stepping + /// again. + /// If the enable Pin is defined, sets it to OUTPUT mode and clears the pin to disabled. + virtual void disableOutputs(); + + /// Enable motor pin outputs by setting the motor pins to OUTPUT + /// mode. Called automatically by the constructor. + /// If the enable Pin is defined, sets it to OUTPUT mode and sets the pin to enabled. + virtual void enableOutputs(); + + /// Sets the minimum pulse width allowed by the stepper driver. The minimum practical pulse width is + /// approximately 20 microseconds. Times less than 20 microseconds + /// will usually result in 20 microseconds or so. + /// \param[in] minWidth The minimum pulse width in microseconds. + void setMinPulseWidth(unsigned int minWidth); + + /// Sets the enable pin number for stepper drivers. + /// 0xFF indicates unused (default). + /// Otherwise, if a pin is set, the pin will be turned on when + /// enableOutputs() is called and switched off when disableOutputs() + /// is called. + /// \param[in] enablePin Arduino digital pin number for motor enable + /// \sa setPinsInverted + void setEnablePin(uint8_t enablePin = 0xff); + + /// Sets the inversion for stepper driver pins + /// \param[in] directionInvert True for inverted direction pin, false for non-inverted + /// \param[in] stepInvert True for inverted step pin, false for non-inverted + /// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted + void setPinsInverted(bool directionInvert = false, bool stepInvert = false, bool enableInvert = false); + + /// Sets the inversion for 2, 3 and 4 wire stepper pins + /// \param[in] pin1Invert True for inverted pin1, false for non-inverted + /// \param[in] pin2Invert True for inverted pin2, false for non-inverted + /// \param[in] pin3Invert True for inverted pin3, false for non-inverted + /// \param[in] pin4Invert True for inverted pin4, false for non-inverted + /// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted + void setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert); + + /// Checks to see if the motor is currently running to a target + /// \return true if the speed is not zero or not at the target position + bool isRunning(); + +protected: + + /// \brief Direction indicator + /// Symbolic names for the direction the motor is turning + typedef enum + { + DIRECTION_CCW = 0, ///< Counter-Clockwise + DIRECTION_CW = 1 ///< Clockwise + } Direction; + + /// Forces the library to compute a new instantaneous speed and set that as + /// the current speed. It is called by + /// the library: + /// \li after each step + /// \li after change to maxSpeed through setMaxSpeed() + /// \li after change to acceleration through setAcceleration() + /// \li after change to target position (relative or absolute) through + /// move() or moveTo() + void computeNewSpeed(); + + /// Low level function to set the motor output pins + /// bit 0 of the mask corresponds to _pin[0] + /// bit 1 of the mask corresponds to _pin[1] + /// You can override this to impment, for example serial chip output insted of using the + /// output pins directly + virtual void setOutputPins(uint8_t mask); + + /// Called to execute a step. Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default calls step1(), step2(), step4() or step8() depending on the + /// number of pins defined for the stepper. + /// \param[in] step The current step phase number (0 to 7) + virtual void step(long step); + + /// Called to execute a step using stepper functions (pins = 0) Only called when a new step is + /// required. Calls _forward() or _backward() to perform the step + /// \param[in] step The current step phase number (0 to 7) + virtual void step0(long step); + + /// Called to execute a step on a stepper driver (ie where pins == 1). Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default sets or clears the outputs of Step pin1 to step, + /// and sets the output of _pin2 to the desired direction. The Step pin (_pin1) is pulsed for 1 microsecond + /// which is the minimum STEP pulse width for the 3967 driver. + /// \param[in] step The current step phase number (0 to 7) + virtual void step1(long step); + + /// Called to execute a step on a 2 pin motor. Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default sets or clears the outputs of pin1 and pin2 + /// \param[in] step The current step phase number (0 to 7) + virtual void step2(long step); + + /// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default sets or clears the outputs of pin1, pin2, + /// pin3 + /// \param[in] step The current step phase number (0 to 7) + virtual void step3(long step); + + /// Called to execute a step on a 4 pin motor. Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default sets or clears the outputs of pin1, pin2, + /// pin3, pin4. + /// \param[in] step The current step phase number (0 to 7) + virtual void step4(long step); + + /// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default sets or clears the outputs of pin1, pin2, + /// pin3 + /// \param[in] step The current step phase number (0 to 7) + virtual void step6(long step); + + /// Called to execute a step on a 4 pin half-steper motor. Only called when a new step is + /// required. Subclasses may override to implement new stepping + /// interfaces. The default sets or clears the outputs of pin1, pin2, + /// pin3, pin4. + /// \param[in] step The current step phase number (0 to 7) + virtual void step8(long step); + + /// Current direction motor is spinning in + /// Protected because some peoples subclasses need it to be so + boolean _direction; // 1 == CW + +private: + /// Number of pins on the stepper motor. Permits 2 or 4. 2 pins is a + /// bipolar, and 4 pins is a unipolar. + uint8_t _interface; // 0, 1, 2, 4, 8, See MotorInterfaceType + + /// Arduino pin number assignments for the 2 or 4 pins required to interface to the + /// stepper motor or driver + uint8_t _pin[4]; + + /// Whether the _pins is inverted or not + uint8_t _pinInverted[4]; + + /// The current absolution position in steps. + long _currentPos; // Steps + + /// The target position in steps. The AccelStepper library will move the + /// motor from the _currentPos to the _targetPos, taking into account the + /// max speed, acceleration and deceleration + long _targetPos; // Steps + + /// The current motos speed in steps per second + /// Positive is clockwise + float _speed; // Steps per second + + /// The maximum permitted speed in steps per second. Must be > 0. + float _maxSpeed; + + /// The acceleration to use to accelerate or decelerate the motor in steps + /// per second per second. Must be > 0 + float _acceleration; + float _sqrt_twoa; // Precomputed sqrt(2*_acceleration) + + /// The current interval between steps in microseconds. + /// 0 means the motor is currently stopped with _speed == 0 + unsigned long _stepInterval; + + /// The last step time in microseconds + unsigned long _lastStepTime; + + /// The minimum allowed pulse width in microseconds + unsigned int _minPulseWidth; + + /// Is the direction pin inverted? + ///bool _dirInverted; /// Moved to _pinInverted[1] + + /// Is the step pin inverted? + ///bool _stepInverted; /// Moved to _pinInverted[0] + + /// Is the enable pin inverted? + bool _enableInverted; + + /// Enable pin for stepper driver, or 0xFF if unused. + uint8_t _enablePin; + + /// The pointer to a forward-step procedure + void (*_forward)(); + + /// The pointer to a backward-step procedure + void (*_backward)(); + + /// The step counter for speed calculations + long _n; + + /// Initial step size in microseconds + float _c0; + + /// Last step size in microseconds + float _cn; + + /// Min step size in microseconds based on maxSpeed + float _cmin; // at max speed + +}; + +/// @example Random.pde +/// Make a single stepper perform random changes in speed, position and acceleration + +/// @example Overshoot.pde +/// Check overshoot handling +/// which sets a new target position and then waits until the stepper has +/// achieved it. This is used for testing the handling of overshoots + +/// @example MultipleSteppers.pde +/// Shows how to multiple simultaneous steppers +/// Runs one stepper forwards and backwards, accelerating and decelerating +/// at the limits. Runs other steppers at the same time + +/// @example ConstantSpeed.pde +/// Shows how to run AccelStepper in the simplest, +/// fixed speed mode with no accelerations + +/// @example Blocking.pde +/// Shows how to use the blocking call runToNewPosition +/// Which sets a new target position and then waits until the stepper has +/// achieved it. + +/// @example AFMotor_MultiStepper.pde +/// Control both Stepper motors at the same time with different speeds +/// and accelerations. + +/// @example AFMotor_ConstantSpeed.pde +/// Shows how to run AccelStepper in the simplest, +/// fixed speed mode with no accelerations + +/// @example ProportionalControl.pde +/// Make a single stepper follow the analog value read from a pot or whatever +/// The stepper will move at a constant speed to each newly set posiiton, +/// depending on the value of the pot. + +/// @example Bounce.pde +/// Make a single stepper bounce from one limit to another, observing +/// accelrations at each end of travel + +/// @example Quickstop.pde +/// Check stop handling. +/// Calls stop() while the stepper is travelling at full speed, causing +/// the stepper to stop as quickly as possible, within the constraints of the +/// current acceleration. + +/// @example MotorShield.pde +/// Shows how to use AccelStepper to control a 3-phase motor, such as a HDD spindle motor +/// using the Adafruit Motor Shield http://www.ladyada.net/make/mshield/index.html. + +/// @example DualMotorShield.pde +/// Shows how to use AccelStepper to control 2 x 2 phase steppers using the +/// Itead Studio Arduino Dual Stepper Motor Driver Shield +/// model IM120417015 + +#endif diff --git a/firmware/configurable_firmata/src/utility/BLEStream.cpp b/firmware/configurable_firmata/src/utility/BLEStream.cpp new file mode 100644 index 0000000..ea50840 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/BLEStream.cpp @@ -0,0 +1,3 @@ +/* + * Implementation is in BLEStream.h to avoid linker issues. + */ diff --git a/firmware/configurable_firmata/src/utility/BLEStream.h b/firmware/configurable_firmata/src/utility/BLEStream.h new file mode 100644 index 0000000..56b3648 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/BLEStream.h @@ -0,0 +1,243 @@ +/* + BLEStream.h + + Based on BLESerial.cpp by Voita Molda + https://github.com/sandeepmistry/arduino-BLEPeripheral/blob/master/examples/serial/BLESerial.h + + Last updated April 4th, 2016 + */ + +#ifndef _BLE_STREAM_H_ +#define _BLE_STREAM_H_ + +#include +#if defined(_VARIANT_ARDUINO_101_X_) +#include +#define _MAX_ATTR_DATA_LEN_ BLE_MAX_ATTR_DATA_LEN +#else +#include +#define _MAX_ATTR_DATA_LEN_ BLE_ATTRIBUTE_MAX_VALUE_LENGTH +#endif + +#define BLESTREAM_TXBUFFER_FLUSH_INTERVAL 80 +#define BLESTREAM_MIN_FLUSH_INTERVAL 8 // minimum interval for flushing the TX buffer + +// #define BLE_SERIAL_DEBUG + +class BLEStream : public BLEPeripheral, public Stream +{ + public: + BLEStream(unsigned char req = 0, unsigned char rdy = 0, unsigned char rst = 0); + + void begin(...); + bool poll(); + void end(); + void setFlushInterval(int); + + virtual int available(void); + virtual int peek(void); + virtual int read(void); + virtual void flush(void); + virtual size_t write(uint8_t byte); + using Print::write; + virtual operator bool(); + + private: + bool _connected; + unsigned long _flushed; + int _flushInterval; + static BLEStream* _instance; + + size_t _rxHead; + size_t _rxTail; + size_t _rxCount() const; + unsigned char _rxBuffer[256]; + size_t _txCount; + unsigned char _txBuffer[_MAX_ATTR_DATA_LEN_]; + + BLEService _uartService = BLEService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); + BLEDescriptor _uartNameDescriptor = BLEDescriptor("2901", "UART"); + BLECharacteristic _rxCharacteristic = BLECharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E", BLEWriteWithoutResponse, _MAX_ATTR_DATA_LEN_); + BLEDescriptor _rxNameDescriptor = BLEDescriptor("2901", "RX - Receive Data (Write)"); + BLECharacteristic _txCharacteristic = BLECharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BLENotify, _MAX_ATTR_DATA_LEN_); + BLEDescriptor _txNameDescriptor = BLEDescriptor("2901", "TX - Transfer Data (Notify)"); + + void _received(const unsigned char* data, size_t size); + static void _received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic); +}; + + +/* + * BLEStream.cpp + * Copied here as a hack to avoid having to install the BLEPeripheral libarary even if it's + * not needed. + */ + +BLEStream* BLEStream::_instance = NULL; + +BLEStream::BLEStream(unsigned char req, unsigned char rdy, unsigned char rst) : +#if defined(_VARIANT_ARDUINO_101_X_) + BLEPeripheral() +#else + BLEPeripheral(req, rdy, rst) +#endif +{ + this->_txCount = 0; + this->_rxHead = this->_rxTail = 0; + this->_flushed = 0; + this->_flushInterval = BLESTREAM_TXBUFFER_FLUSH_INTERVAL; + BLEStream::_instance = this; + + addAttribute(this->_uartService); + addAttribute(this->_uartNameDescriptor); + setAdvertisedServiceUuid(this->_uartService.uuid()); + addAttribute(this->_rxCharacteristic); + addAttribute(this->_rxNameDescriptor); + this->_rxCharacteristic.setEventHandler(BLEWritten, BLEStream::_received); + addAttribute(this->_txCharacteristic); + addAttribute(this->_txNameDescriptor); +} + +void BLEStream::begin(...) +{ + BLEPeripheral::begin(); +#ifdef BLE_SERIAL_DEBUG + Serial.println(F("BLEStream::begin()")); +#endif +} + +bool BLEStream::poll() +{ + // BLEPeripheral::poll is called each time connected() is called + this->_connected = BLEPeripheral::connected(); + if (millis() > this->_flushed + this->_flushInterval) { + flush(); + } + return this->_connected; +} + +void BLEStream::end() +{ + this->_rxCharacteristic.setEventHandler(BLEWritten, (void(*)(BLECentral&, BLECharacteristic&))NULL); + this->_rxHead = this->_rxTail = 0; + flush(); + BLEPeripheral::disconnect(); +} + +int BLEStream::available(void) +{ +// BLEPeripheral::poll only calls delay(1) in CurieBLE so skipping it here to avoid the delay +#ifndef _VARIANT_ARDUINO_101_X_ + // TODO Need to do more testing to determine if all of these calls to BLEPeripheral::poll are + // actually necessary. Seems to run fine without them, but only minimal testing so far. + BLEPeripheral::poll(); +#endif + int retval = (this->_rxHead - this->_rxTail + sizeof(this->_rxBuffer)) % sizeof(this->_rxBuffer); +#ifdef BLE_SERIAL_DEBUG + if (retval > 0) { + Serial.print(F("BLEStream::available() = ")); + Serial.println(retval); + } +#endif + return retval; +} + +int BLEStream::peek(void) +{ +#ifndef _VARIANT_ARDUINO_101_X_ + BLEPeripheral::poll(); +#endif + if (this->_rxTail == this->_rxHead) return -1; + uint8_t byte = this->_rxBuffer[this->_rxTail]; +#ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLEStream::peek() = 0x")); + Serial.println(byte, HEX); +#endif + return byte; +} + +int BLEStream::read(void) +{ +#ifndef _VARIANT_ARDUINO_101_X_ + BLEPeripheral::poll(); +#endif + if (this->_rxTail == this->_rxHead) return -1; + this->_rxTail = (this->_rxTail + 1) % sizeof(this->_rxBuffer); + uint8_t byte = this->_rxBuffer[this->_rxTail]; +#ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLEStream::read() = 0x")); + Serial.println(byte, HEX); +#endif + return byte; +} + +void BLEStream::flush(void) +{ + if (this->_txCount == 0) return; +#ifndef _VARIANT_ARDUINO_101_X_ + // ensure there are available packets before sending + while(!this->_txCharacteristic.canNotify()) { + BLEPeripheral::poll(); + } +#endif + this->_txCharacteristic.setValue(this->_txBuffer, this->_txCount); + this->_flushed = millis(); + this->_txCount = 0; +#ifdef BLE_SERIAL_DEBUG + Serial.println(F("BLEStream::flush()")); +#endif +} + +size_t BLEStream::write(uint8_t byte) +{ +#ifndef _VARIANT_ARDUINO_101_X_ + BLEPeripheral::poll(); +#endif + if (this->_txCharacteristic.subscribed() == false) return 0; + this->_txBuffer[this->_txCount++] = byte; + if (this->_txCount == sizeof(this->_txBuffer)) flush(); +#ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLEStream::write( 0x")); + Serial.print(byte, HEX); + Serial.println(F(") = 1")); +#endif + return 1; +} + +BLEStream::operator bool() +{ + bool retval = this->_connected = BLEPeripheral::connected(); +#ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLEStream::operator bool() = ")); + Serial.println(retval); +#endif + return retval; +} + +void BLEStream::setFlushInterval(int interval) +{ + if (interval > BLESTREAM_MIN_FLUSH_INTERVAL) { + this->_flushInterval = interval; + } +} + +void BLEStream::_received(const unsigned char* data, size_t size) +{ + for (size_t i = 0; i < size; i++) { + this->_rxHead = (this->_rxHead + 1) % sizeof(this->_rxBuffer); + this->_rxBuffer[this->_rxHead] = data[i]; + } +#ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLEStream::received(")); + for (int i = 0; i < size; i++) Serial.print(data[i], HEX); + Serial.println(F(")")); +#endif +} + +void BLEStream::_received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic) +{ + BLEStream::_instance->_received(rxCharacteristic.value(), rxCharacteristic.valueLength()); +} + + +#endif // _BLE_STREAM_H_ diff --git a/firmware/configurable_firmata/src/utility/Boards.h b/firmware/configurable_firmata/src/utility/Boards.h new file mode 100644 index 0000000..00f5d91 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/Boards.h @@ -0,0 +1,987 @@ +/* + Boards.h - Hardware Abstraction Layer for Firmata library + Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated April 15th, 2018 +*/ + +#ifndef Firmata_Boards_h +#define Firmata_Boards_h + +#include + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" // for digitalRead, digitalWrite, etc +#else +#include "WProgram.h" +#endif + +// Normally Servo.h must be included before Firmata.h (which then includes +// this file). If Servo.h wasn't included, this allows the code to still +// compile, but without support for any Servos. Hopefully that's what the +// user intended by not including Servo.h +#ifndef MAX_SERVOS +#define MAX_SERVOS 0 +#endif + +/* + Firmata Hardware Abstraction Layer + +Firmata is built on top of the hardware abstraction functions of Arduino, +specifically digitalWrite, digitalRead, analogWrite, analogRead, and +pinMode. While these functions offer simple integer pin numbers, Firmata +needs more information than is provided by Arduino. This file provides +all other hardware specific details. To make Firmata support a new board, +only this file should require editing. + +The key concept is every "pin" implemented by Firmata may be mapped to +any pin as implemented by Arduino. Usually a simple 1-to-1 mapping is +best, but such mapping should not be assumed. This hardware abstraction +layer allows Firmata to implement any number of pins which map onto the +Arduino implemented pins in almost any arbitrary way. + + +General Constants: + +These constants provide basic information Firmata requires. + +TOTAL_PINS: The total number of pins Firmata implemented by Firmata. + Usually this will match the number of pins the Arduino functions + implement, including any pins pins capable of analog or digital. + However, Firmata may implement any number of pins. For example, + on Arduino Mini with 8 analog inputs, 6 of these may be used + for digital functions, and 2 are analog only. On such boards, + Firmata can implement more pins than Arduino's pinMode() + function, in order to accommodate those special pins. The + Firmata protocol supports a maximum of 128 pins, so this + constant must not exceed 128. + +TOTAL_ANALOG_PINS: The total number of analog input pins implemented. + The Firmata protocol allows up to 16 analog inputs, accessed + using offsets 0 to 15. Because Firmata presents the analog + inputs using different offsets than the actual pin numbers + (a legacy of Arduino's analogRead function, and the way the + analog input capable pins are physically labeled on all + Arduino boards), the total number of analog input signals + must be specified. 16 is the maximum. + +VERSION_BLINK_PIN: When Firmata starts up, it will blink the version + number. This constant is the Arduino pin number where a + LED is connected. + + +Pin Mapping Macros: + +These macros provide the mapping between pins as implemented by +Firmata protocol and the actual pin numbers used by the Arduino +functions. Even though such mappings are often simple, pin +numbers received by Firmata protocol should always be used as +input to these macros, and the result of the macro should be +used with with any Arduino function. + +When Firmata is extended to support a new pin mode or feature, +a pair of macros should be added and used for all hardware +access. For simple 1:1 mapping, these macros add no actual +overhead, yet their consistent use allows source code which +uses them consistently to be easily adapted to all other boards +with different requirements. + +IS_PIN_XXXX(pin): The IS_PIN macros resolve to true or non-zero + if a pin as implemented by Firmata corresponds to a pin + that actually implements the named feature. + +PIN_TO_XXXX(pin): The PIN_TO macros translate pin numbers as + implemented by Firmata to the pin numbers needed as inputs + to the Arduino functions. The corresponding IS_PIN macro + should always be tested before using a PIN_TO macro, so + these macros only need to handle valid Firmata pin + numbers for the named feature. + + +Port Access Inline Funtions: + +For efficiency, Firmata protocol provides access to digital +input and output pins grouped by 8 bit ports. When these +groups of 8 correspond to actual 8 bit ports as implemented +by the hardware, these inline functions can provide high +speed direct port access. Otherwise, a default implementation +using 8 calls to digitalWrite or digitalRead is used. + +When porting Firmata to a new board, it is recommended to +use the default functions first and focus only on the constants +and macros above. When those are working, if optimized port +access is desired, these inline functions may be extended. +The recommended approach defines a symbol indicating which +optimization to use, and then conditional complication is +used within these functions. + +readPort(port, bitmask): Read an 8 bit port, returning the value. + port: The port number, Firmata pins port*8 to port*8+7 + bitmask: The actual pins to read, indicated by 1 bits. + +writePort(port, value, bitmask): Write an 8 bit port. + port: The port number, Firmata pins port*8 to port*8+7 + value: The 8 bit value to write + bitmask: The actual pins to write, indicated by 1 bits. +*/ + +/*============================================================================== + * Board Specific Configuration + *============================================================================*/ + +#ifndef digitalPinHasPWM +#define digitalPinHasPWM(p) IS_PIN_DIGITAL(p) +#endif + +// Arduino Duemilanove, Diecimila, and NG +#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) +#if defined(NUM_ANALOG_INPUTS) && NUM_ANALOG_INPUTS == 6 +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 20 // 14 digital + 6 analog +#else +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS 22 // 14 digital + 8 analog +#endif +#define VERSION_BLINK_PIN 13 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) +#define ARDUINO_PINOUT_OPTIMIZE 1 + + +// Wiring (and board) +#elif defined(WIRING) +#define VERSION_BLINK_PIN WLED +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= FIRST_ANALOG_PIN && (p) < (FIRST_ANALOG_PIN+TOTAL_ANALOG_PINS)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - FIRST_ANALOG_PIN) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// old Arduinos +#elif defined(__AVR_ATmega8__) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 20 // 14 digital + 6 analog +#define VERSION_BLINK_PIN 13 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 19) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) +#define ARDUINO_PINOUT_OPTIMIZE 1 + + +// Arduino Mega +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +#define TOTAL_ANALOG_PINS 16 +#define TOTAL_PINS 70 // 54 digital + 16 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 19 +#define PIN_SERIAL1_TX 18 +#define PIN_SERIAL2_RX 17 +#define PIN_SERIAL2_TX 16 +#define PIN_SERIAL3_RX 15 +#define PIN_SERIAL3_TX 14 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 54 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 2 && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) > 13 && (p) < 20) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 54) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Arduino DUE +#elif defined(__SAM3X8E__) +#define TOTAL_ANALOG_PINS 12 +#define TOTAL_PINS 66 // 54 digital + 12 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 19 +#define PIN_SERIAL1_TX 18 +#define PIN_SERIAL2_RX 17 +#define PIN_SERIAL2_TX 16 +#define PIN_SERIAL3_RX 15 +#define PIN_SERIAL3_TX 14 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 54 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 2 && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) // 70 71 +#define IS_PIN_SERIAL(p) ((p) > 13 && (p) < 20) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 54) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Arduino/Genuino MKR1000 +#elif defined(ARDUINO_SAMD_MKR1000) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 22 // 8 digital + 3 spi + 2 i2c + 2 uart + 7 analog +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 21) +#define IS_PIN_ANALOG(p) ((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Arduino MKRZero +#elif defined(ARDUINO_SAMD_MKRZERO) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 34 // 8 digital + 3 spi + 2 i2c + 2 uart + 7 analog + 3 usb + 1 aref + 5 sd + 1 bottom pad + 1 led + 1 battery adc +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21) || (p) == 32) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 33) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + +// Arduino MKRFox1200 +#elif defined(ARDUINO_SAMD_MKRFox1200) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 33 // 8 digital + 3 spi + 2 i2c + 2 uart + 7 analog + 3 usb + 1 aref + 5 sd + 1 bottom pad + 1 battery adc +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 32) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + +// Arduino MKR WAN 1300 +#elif defined(ARDUINO_SAMD_MKRWAN1300) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 33 +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 32) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + +// Arduino MKR GSM 1400 +#elif defined(ARDUINO_SAMD_MKRGSM1400) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 33 +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 32) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + +// Arduino Zero +// Note this will work with an Arduino Zero Pro, but not with an Arduino M0 Pro +// Arduino M0 Pro does not properly map pins to the board labeled pin numbers +#elif defined(_VARIANT_ARDUINO_ZERO_) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 25 // 14 digital + 6 analog + 2 i2c + 3 spi +#define TOTAL_PORTS 3 // set when TOTAL_PINS > num digitial I/O pins +#define VERSION_BLINK_PIN LED_BUILTIN +//#define PIN_SERIAL1_RX 0 // already defined in zero core variant.h +//#define PIN_SERIAL1_TX 1 // already defined in zero core variant.h +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) // SDA = 20, SCL = 21 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) // SS = A2 +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + +// Arduino Primo +#elif defined(ARDUINO_PRIMO) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 22 //14 digital + 6 analog + 2 i2c +#define VERSION_BLINK_PIN LED_BUILTIN +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < 20) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 20) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS+2) +#define IS_PIN_I2C(p) ((p) == PIN_WIRE_SDA || (p) == PIN_WIRE_SCL) // SDA = 20, SCL = 21 +#define IS_PIN_SPI(p) ((p) == SS || (p)== MOSI || (p) == MISO || (p == SCK)) // 10, 11, 12, 13 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + +// Arduino 101 +#elif defined(_VARIANT_ARDUINO_101_X_) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS NUM_DIGITAL_PINS // 15 digital (including ATN pin) + 6 analog +#define VERSION_BLINK_PIN LED_BUILTIN +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 20) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) // 3, 5, 6, 9 +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) // SDA = 18, SCL = 19 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Teensy 1.0 +#elif defined(__AVR_AT90USB162__) +#define TOTAL_ANALOG_PINS 0 +#define TOTAL_PINS 21 // 21 digital + no analog +#define VERSION_BLINK_PIN 6 +#define PIN_SERIAL1_RX 2 +#define PIN_SERIAL1_TX 3 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) (0) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) (0) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 2 || (p) == 3) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (0) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy 2.0 +#elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) +#define TOTAL_ANALOG_PINS 12 +#define TOTAL_PINS 25 // 11 digital + 12 analog +#define VERSION_BLINK_PIN 11 +#define PIN_SERIAL1_RX 7 +#define PIN_SERIAL1_TX 8 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 11 && (p) <= 22) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 5 || (p) == 6) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 7 || (p) == 8) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (((p) < 22) ? 21 - (p) : 11) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy 3.5 and 3.6 +// reference: https://github.com/PaulStoffregen/cores/blob/master/teensy3/pins_arduino.h +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) +#define TOTAL_ANALOG_PINS 27 // 3.5 has 27 and 3.6 has 25 +#define TOTAL_PINS 70 // 43 digital + 21 analog-digital + 6 analog (64-69) +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define PIN_SERIAL2_RX 9 +#define PIN_SERIAL2_TX 10 +#define PIN_SERIAL3_RX 7 +#define PIN_SERIAL3_TX 8 +#define PIN_SERIAL4_RX 31 +#define PIN_SERIAL4_TX 32 +#define PIN_SERIAL5_RX 34 +#define PIN_SERIAL5_TX 33 +#define PIN_SERIAL6_RX 47 +#define PIN_SERIAL6_TX 48 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 63) +#define IS_PIN_ANALOG(p) (((p) >= 14 && (p) <= 23) || ((p) >= 31 && (p) <= 39) || ((p) >= 49 && (p) <= 50) || ((p) >= 64 && (p) <= 69)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SERIAL(p) (((p) > 6 && (p) < 11) || ((p) == 0 || (p) == 1) || ((p) > 30 && (p) < 35) || ((p) == 47 || (p) == 48)) +#define PIN_TO_DIGITAL(p) (p) +// A0-A9 = D14-D23; A12-A20 = D31-D39; A23-A24 = D49-D50; A10-A11 = D64-D65; A21-A22 = D66-D67; A25-A26 = D68-D69 +#define PIN_TO_ANALOG(p) (((p) <= 23) ? (p) - 14 : (((p) <= 39) ? (p) - 19 : (((p) <= 50) ? (p) - 26 : (((p) <= 65) ? (p) - 55 : (((p) <= 67) ? (p) - 45 : (p) - 43))))) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy 3.0, 3.1 and 3.2 +#elif defined(__MK20DX128__) || defined(__MK20DX256__) +#define TOTAL_ANALOG_PINS 14 +#define TOTAL_PINS 38 // 24 digital + 10 analog-digital + 4 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define PIN_SERIAL2_RX 9 +#define PIN_SERIAL2_TX 10 +#define PIN_SERIAL3_RX 7 +#define PIN_SERIAL3_TX 8 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 33) +#define IS_PIN_ANALOG(p) (((p) >= 14 && (p) <= 23) || ((p) >= 34 && (p) <= 38)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SERIAL(p) (((p) > 6 && (p) < 11) || ((p) == 0 || (p) == 1)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (((p) <= 23) ? (p) - 14 : (p) - 24) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy-LC +#elif defined(__MKL26Z64__) +#define TOTAL_ANALOG_PINS 13 +#define TOTAL_PINS 27 // 27 digital + 13 analog-digital +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define PIN_SERIAL2_RX 9 +#define PIN_SERIAL2_TX 10 +#define PIN_SERIAL3_RX 7 +#define PIN_SERIAL3_TX 8 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 26) +#define IS_PIN_ANALOG(p) ((p) >= 14) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SERIAL(p) (((p) > 6 && (p) < 11) || ((p) == 0 || (p) == 1)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy++ 1.0 and 2.0 +#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS 46 // 38 digital + 8 analog +#define VERSION_BLINK_PIN 6 +#define PIN_SERIAL1_RX 2 +#define PIN_SERIAL1_TX 3 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 38 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 0 || (p) == 1) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 2 || (p) == 3) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 38) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Leonardo +#elif defined(__AVR_ATmega32U4__) +#define TOTAL_ANALOG_PINS 12 +#define TOTAL_PINS 30 // 14 digital + 12 analog + 4 SPI (D14-D17 on ISP header) +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 18 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) ((p) == 3 || (p) == 5 || (p) == 6 || (p) == 9 || (p) == 10 || (p) == 11 || (p) == 13) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 2 || (p) == 3) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p) - 18 +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Intel Galileo Board (gen 1 and 2) and Intel Edison +#elif defined(ARDUINO_LINUX) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 20 // 14 digital + 6 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 19) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// RedBearLab BLE Nano with factory switch settings (S1 - S10) +#elif defined(BLE_NANO) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 15 // 9 digital + 3 analog +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 14) +#define IS_PIN_ANALOG(p) ((p) == 8 || (p) == 9 || (p) == 10 || (p) == 11 || (p) == 12 || (p) == 14) //A0~A5 +#define IS_PIN_PWM(p) ((p) == 3 || (p) == 5 || (p) == 6) +#define IS_PIN_SERVO(p) ((p) >= 2 && (p) <= 7) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == CS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 8) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Sanguino +#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS 32 // 24 digital + 8 analog +#define VERSION_BLINK_PIN 0 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 24 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 16 || (p) == 17) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 24) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Illuminato +#elif defined(__AVR_ATmega645__) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 42 // 36 digital + 6 analog +#define VERSION_BLINK_PIN 13 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 36 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 4 || (p) == 5) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 36) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Pic32 chipKIT FubarinoSD +#elif defined(_BOARD_FUBARINO_SD_) +#define TOTAL_ANALOG_PINS NUM_ANALOG_PINS // 15 +#define TOTAL_PINS NUM_DIGITAL_PINS // 45, All pins can be digital +#define MAX_SERVOS NUM_DIGITAL_PINS +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) 1 +#define IS_PIN_ANALOG(p) ((p) >= 30 && (p) <= 44) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 1 || (p) == 2) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (14 - (p - 30)) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT FubarinoMini +// Note, FubarinoMini analog pin 20 will not function in Firmata as analog input due to limitation in analog mapping +#elif defined(_BOARD_FUBARINO_MINI_) +#define TOTAL_ANALOG_PINS 14 // We have to fake this because of the poor analog pin mapping planning in FubarinoMini +#define TOTAL_PINS NUM_DIGITAL_PINS // 33 +#define MAX_SERVOS NUM_DIGITAL_PINS +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) ((p) != 14 && (p) != 15 && (p) != 31 && (p) != 32) +#define IS_PIN_ANALOG(p) ((p) == 0 || ((p) >= 3 && (p) <= 13)) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 25 || (p) == 26) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT UNO32 +#elif defined(_BOARD_UNO_) && defined(__PIC32) // NOTE: no _BOARD_UNO32_ to use +#define TOTAL_ANALOG_PINS NUM_ANALOG_PINS // 12 +#define TOTAL_PINS NUM_DIGITAL_PINS // 47 All pins can be digital +#define MAX_SERVOS NUM_DIGITAL_PINS // All pins can be servo with SoftPWMservo +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) ((p) >= 2) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 25) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 45 || (p) == 46) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT DP32 +#elif defined(_BOARD_DP32_) +#define TOTAL_ANALOG_PINS 15 // Really only has 9, but have to override because of mistake in variant file +#define TOTAL_PINS NUM_DIGITAL_PINS // 19 +#define MAX_SERVOS NUM_DIGITAL_PINS // All pins can be servo with SoftPWMservo +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) (((p) != 1) && ((p) != 4) && ((p) != 5) && ((p) != 15) && ((p) != 16)) +#define IS_PIN_ANALOG(p) ((p) >= 6 && (p) <= 14) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 2 || (p) == 3) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT uC32 +#elif defined(_BOARD_UC32_) +#define TOTAL_ANALOG_PINS NUM_ANALOG_PINS // 12 +#define TOTAL_PINS NUM_DIGITAL_PINS // 47 All pins can be digital +#define MAX_SERVOS NUM_DIGITAL_PINS // All pins can be servo with SoftPWMservo +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) ((p) >= 2) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 25) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 45 || (p) == 46) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT WF32 +#elif defined(_BOARD_WF32_) +#define TOTAL_ANALOG_PINS NUM_ANALOG_PINS +#define TOTAL_PINS NUM_DIGITAL_PINS +#define MAX_SERVOS NUM_DIGITAL_PINS +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 49) // Accounts for SD and WiFi dedicated pins +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 25) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 34 || (p) == 35) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT WiFire +#elif defined(_BOARD_WIFIRE_) +#define TOTAL_ANALOG_PINS NUM_ANALOG_PINS // 14 +#define TOTAL_PINS NUM_DIGITAL_PINS // 71 +#define MAX_SERVOS NUM_DIGITAL_PINS +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 47) // Accounts for SD and WiFi dedicated pins +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 25) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 34 || (p) == 35) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) <= 25 ? ((p) - 14) : (p) - 36) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT MAX32 +#elif defined(_BOARD_MEGA_) && defined(__PIC32) // NOTE: no _BOARD_MAX32_ to use +#define TOTAL_ANALOG_PINS NUM_ANALOG_PINS // 16 +#define TOTAL_PINS NUM_DIGITAL_PINS // 87 +#define MAX_SERVOS NUM_DIGITAL_PINS +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) ((p) >= 2) +#define IS_PIN_ANALOG(p) ((p) >= 54 && (p) <= 69) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 34 || (p) == 35) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 54) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// Pic32 chipKIT Pi +#elif defined(_BOARD_CHIPKIT_PI_) +#define TOTAL_ANALOG_PINS 16 +#define TOTAL_PINS NUM_DIGITAL_PINS // 19 +#define MAX_SERVOS NUM_DIGITAL_PINS +#define VERSION_BLINK_PIN PIN_LED1 +#define IS_PIN_DIGITAL(p) (((p) >= 2) && ((p) <= 3) || (((p) >= 8) && ((p) <= 13)) || (((p) >= 14) && ((p) <= 17))) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 17) +#define IS_PIN_PWM(p) IS_PIN_DIGITAL(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == 16 || (p) == 17) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) <= 15 ? (p) - 14 : (p) - 12) +//#define PIN_TO_ANALOG(p) (((p) <= 16) ? ((p) - 14) : ((p) - 16)) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + +// Pinoccio Scout +// Note: digital pins 9-16 are usable but not labeled on the board numerically. +// SS=9, MOSI=10, MISO=11, SCK=12, RX1=13, TX1=14, SCL=15, SDA=16 +#elif defined(ARDUINO_PINOCCIO) +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS NUM_DIGITAL_PINS // 32 +#define VERSION_BLINK_PIN 23 +#define PIN_SERIAL1_RX 13 +#define PIN_SERIAL1_TX 14 +#define IS_PIN_DIGITAL(p) (((p) >= 2) && ((p) <= 16)) || (((p) >= 24) && ((p) <= 31)) +#define IS_PIN_ANALOG(p) ((p) >= 24 && (p) <= 31) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == SCL || (p) == SDA) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 13 || (p) == 14) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 24) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + +// ESP8266 +// note: boot mode GPIOs 0, 2 and 15 can be used as outputs, GPIOs 6-11 are in use for flash IO +#elif defined(ESP8266) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS A0 + NUM_ANALOG_INPUTS +#define PIN_SERIAL_RX 3 +#define PIN_SERIAL_TX 1 +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 5) || ((p) >= 12 && (p) < A0)) +#define IS_PIN_ANALOG(p) ((p) >= A0 && (p) < A0 + NUM_ANALOG_INPUTS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_INTERRUPT(p) (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL_RX || (p) == PIN_SERIAL_TX) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - A0) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) +#define DEFAULT_PWM_RESOLUTION 10 + +// ESP32: by Josenalde Oliveira 26.11.2019 + +// note: at boot mode: can be used as outputs/inputs (except 0,1,3) + +// GPIOs 0,5,14,15 (pwm output), 1 (debug output), 3 (high), 12 (low) + +// GPIOs 6-11 are connected to the integrated SPI flash + +// ADC: 08 ADC1_CH0...CH7 and 10 ADC2_CH0..CH9 (do not use ADC2 and Wifi) + +// I2C: SDA (21), SCL (22) + +// SPI: 1) mosi=23, miso=19, clk=18, cs=5; 2) 13, 12, 14, 15 +#elif defined(ARDUINO_ARCH_ESP32) + +#define TOTAL_ANALOG_PINS 6 //firmata allows a maximum of 16 (esp32 has 18) +#define TOTAL_PINS 40 // all gpios +#define TOTAL_PORTS 40 +#define PIN_SERIAL_RX 3 //uart0 RX +#define PIN_SERIAL_TX 1 //uart0 TX +#define IS_PIN_DIGITAL(p) (((p) >= 2 && (p) <= 5) || ((p) >= 13 && (p) <= 27)) +#define IS_PIN_ANALOG(p) (((p) >= 32 && (p) <= 32 + TOTAL_ANALOG_PINS)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) // all gpios in digital +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) // or 21 and 22? +#define IS_PIN_SPI(p) ((p) == CS || (p) == MOSI || (p) == MISO || (p) == CLK) // or numbers? +#define IS_PIN_INTERRUPT(p) (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL_RX || (p) == PIN_SERIAL_TX) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p) - 32 +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) +#define DEFAULT_PWM_RESOLUTION 8 // 1 to 16 bits + +// STM32 based boards +#elif defined(ARDUINO_ARCH_STM32) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS NUM_DIGITAL_PINS +#define TOTAL_PORTS MAX_NB_PORT +#define VERSION_BLINK_PIN LED_BUILTIN +// PIN_SERIALY_RX/TX defined in the variant.h +#define IS_PIN_DIGITAL(p) (digitalPinIsValid(p) && !pinIsSerial(p)) +#define IS_PIN_ANALOG(p) ((p >= A0) && (p < (A0 + TOTAL_ANALOG_PINS)) && !pinIsSerial(p)) +#define IS_PIN_PWM(p) (IS_PIN_DIGITAL(p) && digitalPinHasPWM(p)) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) (IS_PIN_DIGITAL(p) && digitalPinHasI2C(p)) +#define IS_PIN_SPI(p) (IS_PIN_DIGITAL(p) && digitalPinHasSPI(p)) +#define IS_PIN_INTERRUPT(p) (IS_PIN_DIGITAL(p) && (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT))) +#define IS_PIN_SERIAL(p) (digitalPinHasSerial(p) && !pinIsSerial(p)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p-A0) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) +#define DEFAULT_PWM_RESOLUTION PWM_RESOLUTION + +// Adafruit Bluefruit nRF52 boards +#elif defined(ARDUINO_NRF52_ADAFRUIT) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS 32 +#define VERSION_BLINK_PIN LED_BUILTIN +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) == PIN_A0 || (p) == PIN_A1 || (p) == PIN_A2 || (p) == PIN_A3 || \ + (p) == PIN_A4 || (p) == PIN_A5 || (p) == PIN_A6 || (p) == PIN_A7) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == PIN_WIRE_SDA || (p) == PIN_WIRE_SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p)== MOSI || (p) == MISO || (p == SCK)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ( ((p) == PIN_A0) ? 0 : ((p) == PIN_A1) ? 1 : ((p) == PIN_A2) ? 2 : ((p) == PIN_A3) ? 3 : \ + ((p) == PIN_A4) ? 4 : ((p) == PIN_A5) ? 5 : ((p) == PIN_A6) ? 6 : ((p) == PIN_A7) ? 7 : (127)) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + +// anything else +#else +#error "Please edit Boards.h with a hardware abstraction for this board" +#endif + +// as long this is not defined for all boards: +#ifndef IS_PIN_SPI +#define IS_PIN_SPI(p) 0 +#endif + +#ifndef IS_PIN_SERIAL +#define IS_PIN_SERIAL(p) 0 +#endif + +#ifndef DEFAULT_PWM_RESOLUTION +#define DEFAULT_PWM_RESOLUTION 8 +#endif + +/*============================================================================== + * readPort() - Read an 8 bit port + *============================================================================*/ + +static inline unsigned char readPort(byte, byte) __attribute__((always_inline, unused)); +static inline unsigned char readPort(byte port, byte bitmask) +{ +#if defined(ARDUINO_PINOUT_OPTIMIZE) + if (port == 0) return (PIND & 0xFC) & bitmask; // ignore Rx/Tx 0/1 + if (port == 1) return ((PINB & 0x3F) | ((PINC & 0x03) << 6)) & bitmask; + if (port == 2) return ((PINC & 0x3C) >> 2) & bitmask; + return 0; +#else + unsigned char out = 0, pin = port * 8; + if (IS_PIN_DIGITAL(pin + 0) && (bitmask & 0x01) && digitalRead(PIN_TO_DIGITAL(pin + 0))) out |= 0x01; + if (IS_PIN_DIGITAL(pin + 1) && (bitmask & 0x02) && digitalRead(PIN_TO_DIGITAL(pin + 1))) out |= 0x02; + if (IS_PIN_DIGITAL(pin + 2) && (bitmask & 0x04) && digitalRead(PIN_TO_DIGITAL(pin + 2))) out |= 0x04; + if (IS_PIN_DIGITAL(pin + 3) && (bitmask & 0x08) && digitalRead(PIN_TO_DIGITAL(pin + 3))) out |= 0x08; + if (IS_PIN_DIGITAL(pin + 4) && (bitmask & 0x10) && digitalRead(PIN_TO_DIGITAL(pin + 4))) out |= 0x10; + if (IS_PIN_DIGITAL(pin + 5) && (bitmask & 0x20) && digitalRead(PIN_TO_DIGITAL(pin + 5))) out |= 0x20; + if (IS_PIN_DIGITAL(pin + 6) && (bitmask & 0x40) && digitalRead(PIN_TO_DIGITAL(pin + 6))) out |= 0x40; + if (IS_PIN_DIGITAL(pin + 7) && (bitmask & 0x80) && digitalRead(PIN_TO_DIGITAL(pin + 7))) out |= 0x80; + return out; +#endif +} + +/*============================================================================== + * writePort() - Write an 8 bit port, only touch pins specified by a bitmask + *============================================================================*/ + +static inline unsigned char writePort(byte, byte, byte) __attribute__((always_inline, unused)); +static inline unsigned char writePort(byte port, byte value, byte bitmask) +{ +#if defined(ARDUINO_PINOUT_OPTIMIZE) + if (port == 0) { + bitmask = bitmask & 0xFC; // do not touch Tx & Rx pins + byte valD = value & bitmask; + byte maskD = ~bitmask; + cli(); + PORTD = (PORTD & maskD) | valD; + sei(); + } else if (port == 1) { + byte valB = (value & bitmask) & 0x3F; + byte valC = (value & bitmask) >> 6; + byte maskB = ~(bitmask & 0x3F); + byte maskC = ~((bitmask & 0xC0) >> 6); + cli(); + PORTB = (PORTB & maskB) | valB; + PORTC = (PORTC & maskC) | valC; + sei(); + } else if (port == 2) { + bitmask = bitmask & 0x0F; + byte valC = (value & bitmask) << 2; + byte maskC = ~(bitmask << 2); + cli(); + PORTC = (PORTC & maskC) | valC; + sei(); + } + return 1; +#else + byte pin = port * 8; + if ((bitmask & 0x01)) digitalWrite(PIN_TO_DIGITAL(pin + 0), (value & 0x01)); + if ((bitmask & 0x02)) digitalWrite(PIN_TO_DIGITAL(pin + 1), (value & 0x02)); + if ((bitmask & 0x04)) digitalWrite(PIN_TO_DIGITAL(pin + 2), (value & 0x04)); + if ((bitmask & 0x08)) digitalWrite(PIN_TO_DIGITAL(pin + 3), (value & 0x08)); + if ((bitmask & 0x10)) digitalWrite(PIN_TO_DIGITAL(pin + 4), (value & 0x10)); + if ((bitmask & 0x20)) digitalWrite(PIN_TO_DIGITAL(pin + 5), (value & 0x20)); + if ((bitmask & 0x40)) digitalWrite(PIN_TO_DIGITAL(pin + 6), (value & 0x40)); + if ((bitmask & 0x80)) digitalWrite(PIN_TO_DIGITAL(pin + 7), (value & 0x80)/*HIGH*/); + return 1; +#endif +} + + + + +#ifndef TOTAL_PORTS +#define TOTAL_PORTS ((TOTAL_PINS + 7) / 8) +#endif + + +#endif /* Firmata_Boards_h */ diff --git a/firmware/configurable_firmata/src/utility/Boards_orig.h b/firmware/configurable_firmata/src/utility/Boards_orig.h new file mode 100644 index 0000000..f578727 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/Boards_orig.h @@ -0,0 +1,806 @@ +/* + Boards.h - Hardware Abstraction Layer for Firmata library + Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (c) 2013 Norbert Truchsess. All rights reserved. + Copyright (c) 2013-2016 Jeff Hoefs. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated June 2nd, 2018 +*/ + +#ifndef Firmata_Boards_h +#define Firmata_Boards_h + +#include + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" // for digitalRead, digitalWrite, etc +#else +#include "WProgram.h" +#endif + +// Normally Servo.h must be included before Firmata.h (which then includes +// this file). If Servo.h wasn't included, this allows the code to still +// compile, but without support for any Servos. Hopefully that's what the +// user intended by not including Servo.h +#ifndef MAX_SERVOS +#define MAX_SERVOS 0 +#endif + +/* + Firmata Hardware Abstraction Layer + +Firmata is built on top of the hardware abstraction functions of Arduino, +specifically digitalWrite, digitalRead, analogWrite, analogRead, and +pinMode. While these functions offer simple integer pin numbers, Firmata +needs more information than is provided by Arduino. This file provides +all other hardware specific details. To make Firmata support a new board, +only this file should require editing. + +The key concept is every "pin" implemented by Firmata may be mapped to +any pin as implemented by Arduino. Usually a simple 1-to-1 mapping is +best, but such mapping should not be assumed. This hardware abstraction +layer allows Firmata to implement any number of pins which map onto the +Arduino implemented pins in almost any arbitrary way. + + +General Constants: + +These constants provide basic information Firmata requires. + +TOTAL_PINS: The total number of pins Firmata implemented by Firmata. + Usually this will match the number of pins the Arduino functions + implement, including any pins pins capable of analog or digital. + However, Firmata may implement any number of pins. For example, + on Arduino Mini with 8 analog inputs, 6 of these may be used + for digital functions, and 2 are analog only. On such boards, + Firmata can implement more pins than Arduino's pinMode() + function, in order to accommodate those special pins. The + Firmata protocol supports a maximum of 128 pins, so this + constant must not exceed 128. + +TOTAL_ANALOG_PINS: The total number of analog input pins implemented. + The Firmata protocol allows up to 16 analog inputs, accessed + using offsets 0 to 15. Because Firmata presents the analog + inputs using different offsets than the actual pin numbers + (a legacy of Arduino's analogRead function, and the way the + analog input capable pins are physically labeled on all + Arduino boards), the total number of analog input signals + must be specified. 16 is the maximum. + +VERSION_BLINK_PIN: When Firmata starts up, it will blink the version + number. This constant is the Arduino pin number where a + LED is connected. + + +Pin Mapping Macros: + +These macros provide the mapping between pins as implemented by +Firmata protocol and the actual pin numbers used by the Arduino +functions. Even though such mappings are often simple, pin +numbers received by Firmata protocol should always be used as +input to these macros, and the result of the macro should be +used with any Arduino function. + +When Firmata is extended to support a new pin mode or feature, +a pair of macros should be added and used for all hardware +access. For simple 1:1 mapping, these macros add no actual +overhead, yet their consistent use allows source code which +uses them consistently to be easily adapted to all other boards +with different requirements. + +IS_PIN_XXXX(pin): The IS_PIN macros resolve to true or non-zero + if a pin as implemented by Firmata corresponds to a pin + that actually implements the named feature. + +PIN_TO_XXXX(pin): The PIN_TO macros translate pin numbers as + implemented by Firmata to the pin numbers needed as inputs + to the Arduino functions. The corresponding IS_PIN macro + should always be tested before using a PIN_TO macro, so + these macros only need to handle valid Firmata pin + numbers for the named feature. + + +Port Access Inline Funtions: + +For efficiency, Firmata protocol provides access to digital +input and output pins grouped by 8 bit ports. When these +groups of 8 correspond to actual 8 bit ports as implemented +by the hardware, these inline functions can provide high +speed direct port access. Otherwise, a default implementation +using 8 calls to digitalWrite or digitalRead is used. + +When porting Firmata to a new board, it is recommended to +use the default functions first and focus only on the constants +and macros above. When those are working, if optimized port +access is desired, these inline functions may be extended. +The recommended approach defines a symbol indicating which +optimization to use, and then conditional complication is +used within these functions. + +readPort(port, bitmask): Read an 8 bit port, returning the value. + port: The port number, Firmata pins port*8 to port*8+7 + bitmask: The actual pins to read, indicated by 1 bits. + +writePort(port, value, bitmask): Write an 8 bit port. + port: The port number, Firmata pins port*8 to port*8+7 + value: The 8 bit value to write + bitmask: The actual pins to write, indicated by 1 bits. +*/ + +/*============================================================================== + * Board Specific Configuration + *============================================================================*/ + +#ifndef digitalPinHasPWM +#define digitalPinHasPWM(p) IS_PIN_DIGITAL(p) +#endif + +#if defined(digitalPinToInterrupt) && defined(NOT_AN_INTERRUPT) +#define IS_PIN_INTERRUPT(p) (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT) +#else +#define IS_PIN_INTERRUPT(p) (0) +#endif + +// Arduino Duemilanove, Diecimila, and NG +#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) +#if defined(NUM_ANALOG_INPUTS) && NUM_ANALOG_INPUTS == 6 +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 20 // 14 digital + 6 analog +#else +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS 22 // 14 digital + 8 analog +#endif +#define VERSION_BLINK_PIN 13 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) +#define ARDUINO_PINOUT_OPTIMIZE 1 + + +// Wiring (and board) +#elif defined(WIRING) +#define VERSION_BLINK_PIN WLED +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= FIRST_ANALOG_PIN && (p) < (FIRST_ANALOG_PIN+TOTAL_ANALOG_PINS)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - FIRST_ANALOG_PIN) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// old Arduinos +#elif defined(__AVR_ATmega8__) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 20 // 14 digital + 6 analog +#define VERSION_BLINK_PIN 13 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 19) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) +#define ARDUINO_PINOUT_OPTIMIZE 1 + + +// Arduino Mega +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +#define TOTAL_ANALOG_PINS 16 +#define TOTAL_PINS 70 // 54 digital + 16 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 19 +#define PIN_SERIAL1_TX 18 +#define PIN_SERIAL2_RX 17 +#define PIN_SERIAL2_TX 16 +#define PIN_SERIAL3_RX 15 +#define PIN_SERIAL3_TX 14 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 54 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 2 && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) > 13 && (p) < 20) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 54) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Arduino DUE +#elif defined(__SAM3X8E__) +#define TOTAL_ANALOG_PINS 12 +#define TOTAL_PINS 66 // 54 digital + 12 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 19 +#define PIN_SERIAL1_TX 18 +#define PIN_SERIAL2_RX 17 +#define PIN_SERIAL2_TX 16 +#define PIN_SERIAL3_RX 15 +#define PIN_SERIAL3_TX 14 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 54 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 2 && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) // 70 71 +#define IS_PIN_SERIAL(p) ((p) > 13 && (p) < 20) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 54) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Arduino/Genuino MKR1000 +#elif defined(ARDUINO_SAMD_MKR1000) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 22 // 8 digital + 3 spi + 2 i2c + 2 uart + 7 analog +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21) && !IS_PIN_SERIAL(p)) +#define IS_PIN_ANALOG(p) ((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Arduino MKRZero +#elif defined(ARDUINO_SAMD_MKRZERO) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 34 // 8 digital + 3 spi + 2 i2c + 2 uart + 7 analog + 3 usb + 1 aref + 5 sd + 1 bottom pad + 1 led + 1 battery adc +#define IS_PIN_DIGITAL(p) ((((p) >= 0 && (p) <= 21) || (p) == 32) && !IS_PIN_SERIAL(p)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 33) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Arduino MKRFox1200 +#elif defined(ARDUINO_SAMD_MKRFox1200) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 33 // 8 digital + 3 spi + 2 i2c + 2 uart + 7 analog + 3 usb + 1 aref + 5 sd + 1 bottom pad + 1 battery adc +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 32) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Arduino MKR WAN 1300 +#elif defined(ARDUINO_SAMD_MKRWAN1300) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 33 +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 32) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + +// Arduino MKR GSM 1400 +#elif defined(ARDUINO_SAMD_MKRGSM1400) +#define TOTAL_ANALOG_PINS 7 +#define TOTAL_PINS 33 +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 21)) +#define IS_PIN_ANALOG(p) (((p) >= 15 && (p) < 15 + TOTAL_ANALOG_PINS) || (p) == 32) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 11 || (p) == 12) // SDA = 11, SCL = 12 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX) //defined in variant.h RX = 13, TX = 14 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 15) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Arduino Zero +// Note this will work with an Arduino Zero Pro, but not with an Arduino M0 Pro +// Arduino M0 Pro does not properly map pins to the board labeled pin numbers +#elif defined(_VARIANT_ARDUINO_ZERO_) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 25 // 14 digital + 6 analog + 2 i2c + 3 spi +#define TOTAL_PORTS 3 // set when TOTAL_PINS > num digitial I/O pins +#define VERSION_BLINK_PIN LED_BUILTIN +//#define PIN_SERIAL1_RX 0 // already defined in zero core variant.h +//#define PIN_SERIAL1_TX 1 // already defined in zero core variant.h +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) // SDA = 20, SCL = 21 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) // SS = A2 +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Arduino Primo +#elif defined(ARDUINO_PRIMO) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 22 //14 digital + 6 analog + 2 i2c +#define VERSION_BLINK_PIN LED_BUILTIN +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < 20) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 20) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS+2) +#define IS_PIN_I2C(p) ((p) == PIN_WIRE_SDA || (p) == PIN_WIRE_SCL) // SDA = 20, SCL = 21 +#define IS_PIN_SPI(p) ((p) == SS || (p)== MOSI || (p) == MISO || (p == SCK)) // 10, 11, 12, 13 +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Arduino 101 +#elif defined(_VARIANT_ARDUINO_101_X_) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS NUM_DIGITAL_PINS // 15 digital (including ATN pin) + 6 analog +#define VERSION_BLINK_PIN LED_BUILTIN +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 20) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) // 3, 5, 6, 9 +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) // deprecated since v2.4 +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) // SDA = 18, SCL = 19 +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) // deprecated since v2.4 + + +// Teensy 1.0 +#elif defined(__AVR_AT90USB162__) +#define TOTAL_ANALOG_PINS 0 +#define TOTAL_PINS 21 // 21 digital + no analog +#define VERSION_BLINK_PIN 6 +#define PIN_SERIAL1_RX 2 +#define PIN_SERIAL1_TX 3 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) (0) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) (0) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 2 || (p) == 3) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (0) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy 2.0 +#elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) +#define TOTAL_ANALOG_PINS 12 +#define TOTAL_PINS 25 // 11 digital + 12 analog +#define VERSION_BLINK_PIN 11 +#define PIN_SERIAL1_RX 7 +#define PIN_SERIAL1_TX 8 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 11 && (p) <= 22) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 5 || (p) == 6) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 7 || (p) == 8) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (((p) < 22) ? 21 - (p) : 11) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy 3.5 and 3.6 +// reference: https://github.com/PaulStoffregen/cores/blob/master/teensy3/pins_arduino.h +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) +#define TOTAL_ANALOG_PINS 27 // 3.5 has 27 and 3.6 has 25 +#define TOTAL_PINS 70 // 43 digital + 21 analog-digital + 6 analog (64-69) +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define PIN_SERIAL2_RX 9 +#define PIN_SERIAL2_TX 10 +#define PIN_SERIAL3_RX 7 +#define PIN_SERIAL3_TX 8 +#define PIN_SERIAL4_RX 31 +#define PIN_SERIAL4_TX 32 +#define PIN_SERIAL5_RX 34 +#define PIN_SERIAL5_TX 33 +#define PIN_SERIAL6_RX 47 +#define PIN_SERIAL6_TX 48 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 63) +#define IS_PIN_ANALOG(p) (((p) >= 14 && (p) <= 23) || ((p) >= 31 && (p) <= 39) || ((p) >= 49 && (p) <= 50) || ((p) >= 64 && (p) <= 69)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SERIAL(p) (((p) > 6 && (p) < 11) || ((p) == 0 || (p) == 1) || ((p) > 30 && (p) < 35) || ((p) == 47 || (p) == 48)) +#define PIN_TO_DIGITAL(p) (p) +// A0-A9 = D14-D23; A12-A20 = D31-D39; A23-A24 = D49-D50; A10-A11 = D64-D65; A21-A22 = D66-D67; A25-A26 = D68-D69 +#define PIN_TO_ANALOG(p) (((p) <= 23) ? (p) - 14 : (((p) <= 39) ? (p) - 19 : (((p) <= 50) ? (p) - 26 : (((p) <= 65) ? (p) - 55 : (((p) <= 67) ? (p) - 45 : (p) - 43))))) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy 3.0, 3.1 and 3.2 +#elif defined(__MK20DX128__) || defined(__MK20DX256__) +#define TOTAL_ANALOG_PINS 14 +#define TOTAL_PINS 38 // 24 digital + 10 analog-digital + 4 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define PIN_SERIAL2_RX 9 +#define PIN_SERIAL2_TX 10 +#define PIN_SERIAL3_RX 7 +#define PIN_SERIAL3_TX 8 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 33) +#define IS_PIN_ANALOG(p) (((p) >= 14 && (p) <= 23) || ((p) >= 34 && (p) <= 38)) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) (((p) > 6 && (p) < 11) || ((p) == 0 || (p) == 1)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (((p) <= 23) ? (p) - 14 : (p) - 24) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy-LC +#elif defined(__MKL26Z64__) +#define TOTAL_ANALOG_PINS 13 +#define TOTAL_PINS 27 // 27 digital + 13 analog-digital +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define PIN_SERIAL2_RX 9 +#define PIN_SERIAL2_TX 10 +#define PIN_SERIAL3_RX 7 +#define PIN_SERIAL3_TX 8 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) <= 26) +#define IS_PIN_ANALOG(p) ((p) >= 14) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) (((p) > 6 && (p) < 11) || ((p) == 0 || (p) == 1)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Teensy++ 1.0 and 2.0 +#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS 46 // 38 digital + 8 analog +#define VERSION_BLINK_PIN 6 +#define PIN_SERIAL1_RX 2 +#define PIN_SERIAL1_TX 3 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 38 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 0 || (p) == 1) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 2 || (p) == 3) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 38) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Leonardo +#elif defined(__AVR_ATmega32U4__) +#define TOTAL_ANALOG_PINS 12 +#define TOTAL_PINS 30 // 14 digital + 12 analog + 4 SPI (D14-D17 on ISP header) +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 18 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) ((p) == 3 || (p) == 5 || (p) == 6 || (p) == 9 || (p) == 10 || (p) == 11 || (p) == 13) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 2 || (p) == 3) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p) - 18 +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Intel Galileo Board (gen 1 and 2) and Intel Edison +#elif defined(ARDUINO_LINUX) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 20 // 14 digital + 6 analog +#define VERSION_BLINK_PIN 13 +#define PIN_SERIAL1_RX 0 +#define PIN_SERIAL1_TX 1 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) +#define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 19) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 0 || (p) == 1) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 14) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// RedBearLab BLE Nano with factory switch settings (S1 - S10) +#elif defined(BLE_NANO) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 15 // 9 digital + 3 analog +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 14) +#define IS_PIN_ANALOG(p) ((p) == 8 || (p) == 9 || (p) == 10 || (p) == 11 || (p) == 12 || (p) == 14) //A0~A5 +#define IS_PIN_PWM(p) ((p) == 3 || (p) == 5 || (p) == 6) +#define IS_PIN_SERVO(p) ((p) >= 2 && (p) <= 7) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == CS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 8) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) + + +// Pinoccio Scout +// Note: digital pins 9-16 are usable but not labeled on the board numerically. +// SS=9, MOSI=10, MISO=11, SCK=12, RX1=13, TX1=14, SCL=15, SDA=16 +#elif defined(ARDUINO_PINOCCIO) +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS NUM_DIGITAL_PINS // 32 +#define VERSION_BLINK_PIN 23 +#define PIN_SERIAL1_RX 13 +#define PIN_SERIAL1_TX 14 +#define IS_PIN_DIGITAL(p) (((p) >= 2) && ((p) <= 16)) || (((p) >= 24) && ((p) <= 31)) +#define IS_PIN_ANALOG(p) ((p) >= 24 && (p) <= 31) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == SCL || (p) == SDA) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_SERIAL(p) ((p) == 13 || (p) == 14) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 24) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Sanguino +#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) +#define TOTAL_ANALOG_PINS 8 +#define TOTAL_PINS 32 // 24 digital + 8 analog +#define VERSION_BLINK_PIN 0 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 24 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 16 || (p) == 17) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 24) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// Illuminato +#elif defined(__AVR_ATmega645__) +#define TOTAL_ANALOG_PINS 6 +#define TOTAL_PINS 42 // 36 digital + 6 analog +#define VERSION_BLINK_PIN 13 +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) >= 36 && (p) < TOTAL_PINS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == 4 || (p) == 5) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - 36) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) ((p) - 2) + + +// ESP8266 +// note: boot mode GPIOs 0, 2 and 15 can be used as outputs, GPIOs 6-11 are in use for flash IO +#elif defined(ESP8266) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS A0 + NUM_ANALOG_INPUTS +#define PIN_SERIAL_RX 3 +#define PIN_SERIAL_TX 1 +#define IS_PIN_DIGITAL(p) (((p) >= 0 && (p) <= 5) || ((p) >= 12 && (p) < A0)) +#define IS_PIN_ANALOG(p) ((p) >= A0 && (p) < A0 + NUM_ANALOG_INPUTS) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) < MAX_SERVOS) +#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK) +#define IS_PIN_INTERRUPT(p) (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT) +#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL_RX || (p) == PIN_SERIAL_TX) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ((p) - A0) +#define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) +#define PIN_TO_SERVO(p) (p) +#define DEFAULT_PWM_RESOLUTION 10 + + +// STM32 based boards +#elif defined(ARDUINO_ARCH_STM32) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS NUM_DIGITAL_PINS +#define TOTAL_PORTS MAX_NB_PORT +#define VERSION_BLINK_PIN LED_BUILTIN +// PIN_SERIALY_RX/TX defined in the variant.h +#define IS_PIN_DIGITAL(p) (digitalPinIsValid(p) && !pinIsSerial(p)) +#define IS_PIN_ANALOG(p) ((p >= A0) && (p < (A0 + TOTAL_ANALOG_PINS)) && !pinIsSerial(p)) +#define IS_PIN_PWM(p) (IS_PIN_DIGITAL(p) && digitalPinHasPWM(p)) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) (IS_PIN_DIGITAL(p) && digitalPinHasI2C(p)) +#define IS_PIN_SPI(p) (IS_PIN_DIGITAL(p) && digitalPinHasSPI(p)) +#define IS_PIN_INTERRUPT(p) (IS_PIN_DIGITAL(p) && (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT))) +#define IS_PIN_SERIAL(p) (digitalPinHasSerial(p) && !pinIsSerial(p)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) (p-A0) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) +#define DEFAULT_PWM_RESOLUTION PWM_RESOLUTION + + +// Adafruit Bluefruit nRF52 boards +#elif defined(ARDUINO_NRF52_ADAFRUIT) +#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS +#define TOTAL_PINS 32 +#define VERSION_BLINK_PIN LED_BUILTIN +#define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) +#define IS_PIN_ANALOG(p) ((p) == PIN_A0 || (p) == PIN_A1 || (p) == PIN_A2 || (p) == PIN_A3 || \ + (p) == PIN_A4 || (p) == PIN_A5 || (p) == PIN_A6 || (p) == PIN_A7) +#define IS_PIN_PWM(p) digitalPinHasPWM(p) +#define IS_PIN_SERVO(p) IS_PIN_DIGITAL(p) +#define IS_PIN_I2C(p) ((p) == PIN_WIRE_SDA || (p) == PIN_WIRE_SCL) +#define IS_PIN_SPI(p) ((p) == SS || (p)== MOSI || (p) == MISO || (p == SCK)) +#define PIN_TO_DIGITAL(p) (p) +#define PIN_TO_ANALOG(p) ( ((p) == PIN_A0) ? 0 : ((p) == PIN_A1) ? 1 : ((p) == PIN_A2) ? 2 : ((p) == PIN_A3) ? 3 : \ + ((p) == PIN_A4) ? 4 : ((p) == PIN_A5) ? 5 : ((p) == PIN_A6) ? 6 : ((p) == PIN_A7) ? 7 : (127)) +#define PIN_TO_PWM(p) (p) +#define PIN_TO_SERVO(p) (p) + + +// anything else +#else +#error "Please edit Boards.h with a hardware abstraction for this board" +#endif + +// as long this is not defined for all boards: +#ifndef IS_PIN_SPI +#define IS_PIN_SPI(p) (0) +#endif + +#ifndef IS_PIN_SERIAL +#define IS_PIN_SERIAL(p) 0 +#endif + +#ifndef DEFAULT_PWM_RESOLUTION +#define DEFAULT_PWM_RESOLUTION 8 +#endif + +/*============================================================================== + * readPort() - Read an 8 bit port + *============================================================================*/ + +static inline unsigned char readPort(byte, byte) __attribute__((always_inline, unused)); +static inline unsigned char readPort(byte port, byte bitmask) +{ +#if defined(ARDUINO_PINOUT_OPTIMIZE) + if (port == 0) return (PIND & 0xFC) & bitmask; // ignore Rx/Tx 0/1 + if (port == 1) return ((PINB & 0x3F) | ((PINC & 0x03) << 6)) & bitmask; + if (port == 2) return ((PINC & 0x3C) >> 2) & bitmask; + return 0; +#else + unsigned char out = 0, pin = port * 8; + if (IS_PIN_DIGITAL(pin + 0) && (bitmask & 0x01) && digitalRead(PIN_TO_DIGITAL(pin + 0))) out |= 0x01; + if (IS_PIN_DIGITAL(pin + 1) && (bitmask & 0x02) && digitalRead(PIN_TO_DIGITAL(pin + 1))) out |= 0x02; + if (IS_PIN_DIGITAL(pin + 2) && (bitmask & 0x04) && digitalRead(PIN_TO_DIGITAL(pin + 2))) out |= 0x04; + if (IS_PIN_DIGITAL(pin + 3) && (bitmask & 0x08) && digitalRead(PIN_TO_DIGITAL(pin + 3))) out |= 0x08; + if (IS_PIN_DIGITAL(pin + 4) && (bitmask & 0x10) && digitalRead(PIN_TO_DIGITAL(pin + 4))) out |= 0x10; + if (IS_PIN_DIGITAL(pin + 5) && (bitmask & 0x20) && digitalRead(PIN_TO_DIGITAL(pin + 5))) out |= 0x20; + if (IS_PIN_DIGITAL(pin + 6) && (bitmask & 0x40) && digitalRead(PIN_TO_DIGITAL(pin + 6))) out |= 0x40; + if (IS_PIN_DIGITAL(pin + 7) && (bitmask & 0x80) && digitalRead(PIN_TO_DIGITAL(pin + 7))) out |= 0x80; + return out; +#endif +} + +/*============================================================================== + * writePort() - Write an 8 bit port, only touch pins specified by a bitmask + *============================================================================*/ + +static inline unsigned char writePort(byte, byte, byte) __attribute__((always_inline, unused)); +static inline unsigned char writePort(byte port, byte value, byte bitmask) +{ +#if defined(ARDUINO_PINOUT_OPTIMIZE) + if (port == 0) { + bitmask = bitmask & 0xFC; // do not touch Tx & Rx pins + byte valD = value & bitmask; + byte maskD = ~bitmask; + cli(); + PORTD = (PORTD & maskD) | valD; + sei(); + } else if (port == 1) { + byte valB = (value & bitmask) & 0x3F; + byte valC = (value & bitmask) >> 6; + byte maskB = ~(bitmask & 0x3F); + byte maskC = ~((bitmask & 0xC0) >> 6); + cli(); + PORTB = (PORTB & maskB) | valB; + PORTC = (PORTC & maskC) | valC; + sei(); + } else if (port == 2) { + bitmask = bitmask & 0x0F; + byte valC = (value & bitmask) << 2; + byte maskC = ~(bitmask << 2); + cli(); + PORTC = (PORTC & maskC) | valC; + sei(); + } + return 1; +#else + byte pin = port * 8; + if ((bitmask & 0x01)) digitalWrite(PIN_TO_DIGITAL(pin + 0), (value & 0x01)); + if ((bitmask & 0x02)) digitalWrite(PIN_TO_DIGITAL(pin + 1), (value & 0x02)); + if ((bitmask & 0x04)) digitalWrite(PIN_TO_DIGITAL(pin + 2), (value & 0x04)); + if ((bitmask & 0x08)) digitalWrite(PIN_TO_DIGITAL(pin + 3), (value & 0x08)); + if ((bitmask & 0x10)) digitalWrite(PIN_TO_DIGITAL(pin + 4), (value & 0x10)); + if ((bitmask & 0x20)) digitalWrite(PIN_TO_DIGITAL(pin + 5), (value & 0x20)); + if ((bitmask & 0x40)) digitalWrite(PIN_TO_DIGITAL(pin + 6), (value & 0x40)); + if ((bitmask & 0x80)) digitalWrite(PIN_TO_DIGITAL(pin + 7), (value & 0x80)); + return 1; +#endif +} + + +#ifndef TOTAL_PORTS +#define TOTAL_PORTS ((TOTAL_PINS + 7) / 8) +#endif + + +#endif /* Firmata_Boards_h */ diff --git a/firmware/configurable_firmata/src/utility/FirmataStepper.cpp b/firmware/configurable_firmata/src/utility/FirmataStepper.cpp new file mode 100644 index 0000000..8632d1e --- /dev/null +++ b/firmware/configurable_firmata/src/utility/FirmataStepper.cpp @@ -0,0 +1,403 @@ +/** + FirmataStepper is a simple non-blocking stepper motor library + for 2 and 4 wire bipolar and unipolar stepper motor drive circuits + as well as EasyDriver (http://schmalzhaus.com/EasyDriver/) and + other step + direction drive circuits. + + FirmataStepper (0.3) by Jeff Hoefs + + EasyDriver support based on modifications by Chris Coleman + + Acceleration / Deceleration algorithms and code based on: + app note: http://www.atmel.com/dyn/resources/prod_documents/doc8017.pdf + source code: http://www.atmel.com/dyn/resources/prod_documents/AVR446.zip + + stepMotor function based on Stepper.cpp Stepper library for + Wiring/Arduino created by Tom Igoe, Sebastian Gassner + David Mellis and Noah Shibley. + + Relevant notes from Stepper.cpp: + + When wiring multiple stepper motors to a microcontroller, + you quickly run out of output pins, with each motor requiring 4 connections. + + By making use of the fact that at any time two of the four motor + coils are the inverse of the other two, the number of + control connections can be reduced from 4 to 2. + + A slightly modified circuit around a Darlington transistor array or an L293 H-bridge + connects to only 2 microcontroler pins, inverts the signals received, + and delivers the 4 (2 plus 2 inverted ones) output signals required + for driving a stepper motor. + + The sequence of control signals for 4 control wires is as follows: + + Step C0 C1 C2 C3 + 1 1 0 1 0 + 2 0 1 1 0 + 3 0 1 0 1 + 4 1 0 0 1 + + The sequence of controls signals for 2 control wires is as follows + (columns C1 and C2 from above): + + Step C0 C1 + 1 0 1 + 2 1 1 + 3 1 0 + 4 0 0 + + The circuits can be found at + http://www.arduino.cc/en/Tutorial/Stepper +*/ + +#include "FirmataStepper.h" + +/** + * Constructor. + * + * Configure a stepper for an EasyDriver or other step + direction interface or + * configure a bipolar or unipolar stepper motor for 2 wire drive mode. + * Configure a bipolar or unipolar stepper for 4 wire drive mode. + * @param interface Lower 3 bits: + * The interface type: FirmataStepper::DRIVER, + * FirmataStepper::TWO_WIRE or FirmataStepper::FOUR_WIRE + * Upper 4 bits: Any bits set = use 2 microsecond delay + * @param steps_per_rev The number of steps to make 1 revolution. + * @param first_pin The direction pin (EasyDriver) or the pin attached to the + * 1st motor coil (2 wire drive mode) + * @param second_pin The step pin (EasyDriver) or the pin attached to the 2nd + * motor coil (2 wire drive mode) + * @param motor_pin_3 The pin attached to the 3rd motor coil + * @param motor_pin_4 The pin attached to the 4th motor coil + */ +FirmataStepper::FirmataStepper(byte interface, + int steps_per_rev, + byte pin1, + byte pin2, + byte pin3, + byte pin4) +{ + this->step_number = 0; // which step the motor is on + this->direction = 0; // motor direction + this->last_step_time = 0; // time stamp in ms of the last step taken + this->steps_per_rev = steps_per_rev; // total number of steps for this motor + this->running = false; + this->interface = interface & 0x0F; // default to Easy Stepper (or other step + direction driver) + + // could update this in future to support additional delays if necessary + if (((interface & 0xF0) >> 4) > 0) { + // high current driver + this->stepDelay = 2; // microseconds + } else { + this->stepDelay = 1; // microseconds + } + + this->motor_pin_1 = pin1; + this->motor_pin_2 = pin2; + this->dir_pin = pin1; + this->step_pin = pin2; + + // setup the pins on the microcontroller: + pinMode(this->motor_pin_1, OUTPUT); + pinMode(this->motor_pin_2, OUTPUT); + + if (this->interface == FirmataStepper::FOUR_WIRE) { + this->motor_pin_3 = pin3; + this->motor_pin_4 = pin4; + pinMode(this->motor_pin_3, OUTPUT); + pinMode(this->motor_pin_4, OUTPUT); + } + + this->alpha = PI_2 / this->steps_per_rev; + this->at_x100 = (long)(this->alpha * T1_FREQ * 100); + this->ax20000 = (long)(this->alpha * 20000); + this->alpha_x2 = this->alpha * 2; +} + +/** + * Move the stepper a given number of steps at the specified + * speed (rad/sec), acceleration (rad/sec^2) and deceleration (rad/sec^2). + * + * @param steps_to_move The number ofsteps to move the motor + * @param speed Max speed in 0.01*rad/sec + * @param accel [optional] Acceleration in 0.01*rad/sec^2 + * @param decel [optional] Deceleration in 0.01*rad/sec^2 + */ +void FirmataStepper::setStepsToMove(long steps_to_move, int speed, int accel, int decel) +{ + unsigned long maxStepLimit; + unsigned long accelerationLimit; + + this->step_number = 0; + this->lastAccelDelay = 0; + this->stepCount = 0; + this->rest = 0; + + // ensure steps_to_move is always a positive value + if (steps_to_move < 0) { + this->direction = FirmataStepper::CCW; + steps_to_move = -steps_to_move; + } else { + this->direction = FirmataStepper::CW; + } + + this->steps_to_move = steps_to_move; + + // set max speed limit, by calc min_delay + // min_delay = (alpha / tt)/w + this->min_delay = this->at_x100 / speed; + + // if acceleration or deceleration are not defined + // start in RUN state and do no decelerate + if (accel == 0 || decel == 0) { + this->step_delay = this->min_delay; + + this->decel_start = steps_to_move; + this->run_state = FirmataStepper::RUN; + this->accel_count = 0; + this->running = true; + + return; + } + + // if only moving 1 step + if (steps_to_move == 1) { + + // move one step + this->accel_count = -1; + this->run_state = FirmataStepper::DECEL; + + this->step_delay = this->min_delay; + this->running = true; + } else if (steps_to_move != 0) { + // set initial step delay + // step_delay = 1/tt * sqrt(2*alpha/accel) + // step_delay = ( tfreq*0.676/100 )*100 * sqrt( (2*alpha*10000000000) / (accel*100) )/10000 + this->step_delay = (long)((T1_FREQ_148 * sqrt(alpha_x2 / accel)) * 1000); + + // find out after how many steps does the speed hit the max speed limit. + // maxSpeedLimit = speed^2 / (2*alpha*accel) + maxStepLimit = (long)speed * speed / (long)(((long)this->ax20000 * accel) / 100); + + // if we hit max spped limit before 0.5 step it will round to 0. + // but in practice we need to move at least 1 step to get any speed at all. + if (maxStepLimit == 0) { + maxStepLimit = 1; + } + + // find out after how many steps we must start deceleration. + // n1 = (n1+n2)decel / (accel + decel) + accelerationLimit = (long)((steps_to_move * decel) / (accel + decel)); + + // we must accelerate at least 1 step before we can start deceleration + if (accelerationLimit == 0) { + accelerationLimit = 1; + } + + // use the limit we hit first to calc decel + if (accelerationLimit <= maxStepLimit) { + this->decel_val = accelerationLimit - steps_to_move; + } else { + this->decel_val = -(long)(maxStepLimit * accel) / decel; + } + + // we must decelerate at least 1 step to stop + if (this->decel_val == 0) { + this->decel_val = -1; + } + + // find step to start deceleration + this->decel_start = steps_to_move + this->decel_val; + + // if the max speed is so low that we don't need to go via acceleration state. + if (this->step_delay <= this->min_delay) { + this->step_delay = this->min_delay; + this->run_state = FirmataStepper::RUN; + } else { + this->run_state = FirmataStepper::ACCEL; + } + + // reset counter + this->accel_count = 0; + this->running = true; + } +} + +bool FirmataStepper::update() +{ + bool done = false; + unsigned long newStepDelay = 0; + + unsigned long curTimeVal = micros(); + unsigned long timeDiff = curTimeVal - this->last_step_time; + + if (this->running == true && + (timeDiff >= this->step_delay || this->run_state == FirmataStepper::STOP)) { + + this->last_step_time = curTimeVal; + + switch (this->run_state) { + case FirmataStepper::STOP: + this->stepCount = 0; + this->rest = 0; + done = true; + this->running = false; + break; + + case FirmataStepper::ACCEL: + updateStepPosition(); + this->stepCount++; + this->accel_count++; + newStepDelay = this->step_delay - + (((2 * (long)this->step_delay) + this->rest) / (4 * this->accel_count + 1)); + // Keep track of remainder from new_step-delay calculation to incrase accurancy + this->rest = ((2 * (long)this->step_delay) + this->rest) % (4 * this->accel_count + 1); + + // check if we should start deceleration + if (this->stepCount >= this->decel_start) { + this->accel_count = this->decel_val; + this->run_state = FirmataStepper::DECEL; + // check if we hit max speed + } else if (newStepDelay <= this->min_delay) { + this->lastAccelDelay = newStepDelay; + newStepDelay = this->min_delay; + this->rest = 0; + this->run_state = FirmataStepper::RUN; + } + break; + + case FirmataStepper::RUN: + updateStepPosition(); + this->stepCount++; + newStepDelay = this->min_delay; + + // if no accel or decel was specified, go directly to STOP state + if ((long)this->stepCount >= this->steps_to_move) { + this->run_state = FirmataStepper::STOP; + // check if we should start deceleration + } else if (this->stepCount >= this->decel_start) { + this->accel_count = this->decel_val; + // start deceleration with same delay that accel ended with + newStepDelay = this->lastAccelDelay; + this->run_state = FirmataStepper::DECEL; + } + break; + + case FirmataStepper::DECEL: + updateStepPosition(); + this->stepCount++; + this->accel_count++; + + newStepDelay = this->step_delay - + (((2 * (long)this->step_delay) + this->rest) / (4 * this->accel_count + 1)); + this->rest = ((2 * (long)this->step_delay) + this->rest) % (4 * this->accel_count + 1); + + // check if we ar at the last step + if (this->accel_count >= 0) { + this->run_state = FirmataStepper::STOP; + } + break; + } + + this->step_delay = newStepDelay; + + } + + return done; +} + +/** + * Update the step position. + * @private + */ +void FirmataStepper::updateStepPosition() +{ + // increment or decrement the step number, + // depending on direction: + if (this->direction == FirmataStepper::CW) { + this->step_number++; + if (this->step_number >= this->steps_per_rev) { + this->step_number = 0; + } + } else { + if (this->step_number <= 0) { + this->step_number = this->steps_per_rev; + } + this->step_number--; + } + + // step the motor to step number 0, 1, 2, or 3: + stepMotor(this->step_number % 4, this->direction); +} + +/** + * Moves the motor forward or backwards. + * @param step_num For 2 or 4 wire configurations, this is the current step in + * the 2 or 4 step sequence. + * @param direction The direction of rotation + */ +void FirmataStepper::stepMotor(byte step_num, byte direction) +{ + if (this->interface == FirmataStepper::DRIVER) { + digitalWrite(dir_pin, direction); + delayMicroseconds(this->stepDelay); + digitalWrite(step_pin, LOW); + delayMicroseconds(this->stepDelay); + digitalWrite(step_pin, HIGH); + } else if (this->interface == FirmataStepper::TWO_WIRE) { + switch (step_num) { + case 0: /* 01 */ + digitalWrite(motor_pin_1, LOW); + digitalWrite(motor_pin_2, HIGH); + break; + case 1: /* 11 */ + digitalWrite(motor_pin_1, HIGH); + digitalWrite(motor_pin_2, HIGH); + break; + case 2: /* 10 */ + digitalWrite(motor_pin_1, HIGH); + digitalWrite(motor_pin_2, LOW); + break; + case 3: /* 00 */ + digitalWrite(motor_pin_1, LOW); + digitalWrite(motor_pin_2, LOW); + break; + } + } else if (this->interface == FirmataStepper::FOUR_WIRE) { + switch (step_num) { + case 0: // 1010 + digitalWrite(motor_pin_1, HIGH); + digitalWrite(motor_pin_2, LOW); + digitalWrite(motor_pin_3, HIGH); + digitalWrite(motor_pin_4, LOW); + break; + case 1: // 0110 + digitalWrite(motor_pin_1, LOW); + digitalWrite(motor_pin_2, HIGH); + digitalWrite(motor_pin_3, HIGH); + digitalWrite(motor_pin_4, LOW); + break; + case 2: //0101 + digitalWrite(motor_pin_1, LOW); + digitalWrite(motor_pin_2, HIGH); + digitalWrite(motor_pin_3, LOW); + digitalWrite(motor_pin_4, HIGH); + break; + case 3: //1001 + digitalWrite(motor_pin_1, HIGH); + digitalWrite(motor_pin_2, LOW); + digitalWrite(motor_pin_3, LOW); + digitalWrite(motor_pin_4, HIGH); + break; + } + } +} + +/** + * @return The version number of this library. + */ +byte FirmataStepper::version(void) +{ + return 3; +} diff --git a/firmware/configurable_firmata/src/utility/FirmataStepper.h b/firmware/configurable_firmata/src/utility/FirmataStepper.h new file mode 100644 index 0000000..8db3470 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/FirmataStepper.h @@ -0,0 +1,145 @@ +/* + FirmataStepper is a simple non-blocking stepper motor library + for 2 and 4 wire bipolar and unipolar stepper motor drive circuits + as well as EasyDriver (http://schmalzhaus.com/EasyDriver/) and + other step + direction drive circuits. + + FirmataStepper (0.3) by Jeff Hoefs + + EasyDriver support based on modifications by Chris Coleman + + Acceleration / Deceleration algorithms and code based on: + app note: http://www.atmel.com/dyn/resources/prod_documents/doc8017.pdf + source code: http://www.atmel.com/dyn/resources/prod_documents/AVR446.zip + + stepMotor function based on Stepper.cpp Stepper library for + Wiring/Arduino created by Tom Igoe, Sebastian Gassner + David Mellis and Noah Shibley. + + Relevant notes from Stepper.cpp: + + When wiring multiple stepper motors to a microcontroller, + you quickly run out of output pins, with each motor requiring 4 connections. + + By making use of the fact that at any time two of the four motor + coils are the inverse of the other two, the number of + control connections can be reduced from 4 to 2. + + A slightly modified circuit around a Darlington transistor array or an L293 H-bridge + connects to only 2 microcontroler pins, inverts the signals received, + and delivers the 4 (2 plus 2 inverted ones) output signals required + for driving a stepper motor. + + The sequence of control signals for 4 control wires is as follows: + + Step C0 C1 C2 C3 + 1 1 0 1 0 + 2 0 1 1 0 + 3 0 1 0 1 + 4 1 0 0 1 + + The sequence of controls signals for 2 control wires is as follows + (columns C1 and C2 from above): + + Step C0 C1 + 1 0 1 + 2 1 1 + 3 1 0 + 4 0 0 + + The circuits can be found at + http://www.arduino.cc/en/Tutorial/Stepper +*/ + +// ensure this library description is only included once +#ifndef FirmataStepper_h +#define FirmataStepper_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#define PI_2 2*3.14159 +#define T1_FREQ 1000000L // provides the most accurate step delay values +#define T1_FREQ_148 ((long)((T1_FREQ*0.676)/100)) // divided by 100 and scaled by 0.676 + +// library interface description +class FirmataStepper +{ + public: + FirmataStepper(byte interface = FirmataStepper::DRIVER, + int steps_per_rev = 200, + byte pin1 = 2, + byte pin2 = 3, + byte pin3 = 4, + byte pin4 = 5); + + enum Interface + { + DRIVER = 1, + TWO_WIRE = 2, + FOUR_WIRE = 4 + }; + + enum RunState + { + STOP = 0, + ACCEL = 1, + DECEL = 2, + RUN = 3 + }; + + enum Direction + { + CCW = 0, + CW = 1 + }; + + void setStepsToMove(long steps_to_move, int speed, int accel = 0, int decel = 0); + + // update the stepper position + bool update(); + + byte version(void); + + private: + void stepMotor(byte step_num, byte direction); + void updateStepPosition(); + bool running; + byte interface; // Type of interface: DRIVER, TWO_WIRE or FOUR_WIRE + byte direction; // Direction of rotation + unsigned long step_delay; // delay between steps, in microseconds + int steps_per_rev; // number of steps to make one revolution + long step_number; // which step the motor is on + long steps_to_move; // total number of teps to move + byte stepDelay; // delay between steps (default = 1, increase for high current drivers) + + byte run_state; + int accel_count; + unsigned long min_delay; + unsigned long decel_start; + int decel_val; + + long lastAccelDelay; + unsigned long stepCount; + unsigned int rest; + + float alpha; // PI * 2 / steps_per_rev + long at_x100; // alpha * T1_FREQ * 100 + long ax20000; // alph a* 20000 + float alpha_x2; // alpha * 2 + + // motor pin numbers: + byte dir_pin; + byte step_pin; + byte motor_pin_1; + byte motor_pin_2; + byte motor_pin_3; + byte motor_pin_4; + + unsigned long last_step_time; // time stamp in microseconds of when the last step was taken +}; + +#endif diff --git a/firmware/configurable_firmata/src/utility/MultiStepper.cpp b/firmware/configurable_firmata/src/utility/MultiStepper.cpp new file mode 100644 index 0000000..de0ec94 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/MultiStepper.cpp @@ -0,0 +1,73 @@ +// MultiStepper.cpp +// +// Copyright (C) 2015 Mike McCauley +// $Id: MultiStepper.cpp,v 1.2 2015/10/04 05:16:38 mikem Exp $ + +#include "MultiStepper.h" +#include "AccelStepper.h" + +MultiStepper::MultiStepper() + : _num_steppers(0) +{ +} + +boolean MultiStepper::addStepper(AccelStepper& stepper) +{ + if (_num_steppers >= MULTISTEPPER_MAX_STEPPERS) + return false; // No room for more + _steppers[_num_steppers++] = &stepper; + return true; +} + +void MultiStepper::moveTo(long absolute[]) +{ + // First find the stepper that will take the longest time to move + float longestTime = 0.0; + + uint8_t i; + for (i = 0; i < _num_steppers; i++) + { + long thisDistance = absolute[i] - _steppers[i]->currentPosition(); + float thisTime = abs(thisDistance) / _steppers[i]->maxSpeed(); + + if (thisTime > longestTime) + longestTime = thisTime; + } + + if (longestTime > 0.0) + { + // Now work out a new max speed for each stepper so they will all + // arrived at the same time of longestTime + for (i = 0; i < _num_steppers; i++) + { + long thisDistance = absolute[i] - _steppers[i]->currentPosition(); + float thisSpeed = thisDistance / longestTime; + _steppers[i]->moveTo(absolute[i]); // New target position (resets speed) + _steppers[i]->setSpeed(thisSpeed); // New speed + } + } +} + +// Returns true if any motor is still running to the target position. +boolean MultiStepper::run() +{ + uint8_t i; + boolean ret = false; + for (i = 0; i < _num_steppers; i++) + { + if ( _steppers[i]->distanceToGo() != 0) + { + _steppers[i]->runSpeed(); + ret = true; + } + } + return ret; +} + +// Blocks until all steppers reach their target position and are stopped +void MultiStepper::runSpeedToPosition() +{ + while (run()) + ; +} + diff --git a/firmware/configurable_firmata/src/utility/MultiStepper.h b/firmware/configurable_firmata/src/utility/MultiStepper.h new file mode 100644 index 0000000..d801bb0 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/MultiStepper.h @@ -0,0 +1,78 @@ +// MultiStepper.h + +#ifndef MultiStepper_h +#define MultiStepper_h + +#include +#if ARDUINO >= 100 +#include +#else +#include +#include +#endif + +#define MULTISTEPPER_MAX_STEPPERS 10 + +class AccelStepper; + +///////////////////////////////////////////////////////////////////// +/// \class MultiStepper MultiStepper.h +/// \brief Operate multiple AccelSteppers in a co-ordinated fashion +/// +/// This class can manage multiple AccelSteppers (up to MULTISTEPPER_MAX_STEPPERS = 10), +/// and cause them all to move +/// to selected positions at such a (constant) speed that they all arrive at their +/// target position at the same time. This can be used to support devices with multiple steppers +/// on say multiple axes to cause linear diagonal motion. Suitable for use with X-Y plotters, flatbeds, +/// 3D printers etc +/// to get linear straight line movement between arbitrary 2d (or 3d or ...) positions. +/// +/// Caution: only constant speed stepper motion is supported: acceleration and deceleration is not supported +/// All the steppers managed by MultiStepper will step at a constant speed to their +/// target (albeit perhaps different speeds for each stepper). +class MultiStepper +{ +public: + /// Constructor + MultiStepper(); + + /// Add a stepper to the set of managed steppers + /// There is an upper limit of MULTISTEPPER_MAX_STEPPERS = 10 to the number of steppers that can be managed + /// \param[in] stepper Reference to a stepper to add to the managed list + /// \return true if successful. false if the number of managed steppers would exceed MULTISTEPPER_MAX_STEPPERS + boolean addStepper(AccelStepper& stepper); + + /// Set the target positions of all managed steppers + /// according to a coordinate array. + /// New speeds will be computed for each stepper so they will all arrive at their + /// respective targets at very close to the same time. + /// \param[in] absolute An array of desired absolute stepper positions. absolute[0] will be used to set + /// the absolute position of the first stepper added by addStepper() etc. The array must be at least as long as + /// the number of steppers that have been added by addStepper, else results are undefined. + void moveTo(long absolute[]); + + /// Calls runSpeed() on all the managed steppers + /// that have not acheived their target position. + /// \return true if any stepper is still in the process of running to its target position. + boolean run(); + + /// Runs all managed steppers until they acheived their target position. + /// Blocks until all that position is acheived. If you dont + /// want blocking consider using run() instead. + void runSpeedToPosition(); + +private: + /// Array of pointers to the steppers we are controlling. + /// Fills from 0 onwards + AccelStepper* _steppers[MULTISTEPPER_MAX_STEPPERS]; + + /// Number of steppers we are controlling and the number + /// of steppers in _steppers[] + uint8_t _num_steppers; +}; + +/// @example MultiStepper.pde +/// Use MultiStepper class to manage multiple steppers and make them all move to +/// the same position at the same time for linear 2d (or 3d) motion. + +#endif diff --git a/firmware/configurable_firmata/src/utility/OneWire.cpp b/firmware/configurable_firmata/src/utility/OneWire.cpp new file mode 100644 index 0000000..cf34933 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/OneWire.cpp @@ -0,0 +1,567 @@ +/* +Copyright (c) 2007, Jim Studt (original old version - many contributors since) + +The latest version of this library may be found at: + http://www.pjrc.com/teensy/td_libs_OneWire.html + +OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since +January 2010. At the time, it was in need of many bug fixes, but had +been abandoned the original author (Jim Studt). None of the known +contributors were interested in maintaining OneWire. Paul typically +works on OneWire every 6 to 12 months. Patches usually wait that +long. If anyone is interested in more actively maintaining OneWire, +please contact Paul. + +Version 2.3: + Unknonw chip fallback mode, Roger Clark + Teensy-LC compatibility, Paul Stoffregen + Search bug fix, Love Nystrom + +Version 2.2: + Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com + Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 + Fix DS18B20 example negative temperature + Fix DS18B20 example's low res modes, Ken Butcher + Improve reset timing, Mark Tillotson + Add const qualifiers, Bertrik Sikken + Add initial value input to crc16, Bertrik Sikken + Add target_search() function, Scott Roberts + +Version 2.1: + Arduino 1.0 compatibility, Paul Stoffregen + Improve temperature example, Paul Stoffregen + DS250x_PROM example, Guillermo Lovato + PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com + Improvements from Glenn Trewitt: + - crc16() now works + - check_crc16() does all of calculation/checking work. + - Added read_bytes() and write_bytes(), to reduce tedious loops. + - Added ds2408 example. + Delete very old, out-of-date readme file (info is here) + +Version 2.0: Modifications by Paul Stoffregen, January 2010: +http://www.pjrc.com/teensy/td_libs_OneWire.html + Search fix from Robin James + http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + Use direct optimized I/O in all cases + Disable interrupts during timing critical sections + (this solves many random communication errors) + Disable interrupts during read-modify-write I/O + Reduce RAM consumption by eliminating unnecessary + variables and trimming many to 8 bits + Optimize both crc8 - table version moved to flash + +Modified to work with larger numbers of devices - avoids loop. +Tested in Arduino 11 alpha with 12 sensors. +26 Sept 2008 -- Robin James +http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + +Updated to work with arduino-0008 and to include skip() as of +2007/07/06. --RJL20 + +Modified to calculate the 8-bit CRC directly, avoiding the need for +the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 +-- Tom Pollard, Jan 23, 2008 + +Jim Studt's original library was modified by Josh Larios. + +Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 + +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. + +Much of the code was inspired by Derek Yerger's code, though I don't +think much of that remains. In any event that was.. + (copyleft) 2006 by Derek Yerger - Free to distribute freely. + +The CRC code was excerpted and inspired by the Dallas Semiconductor +sample code bearing this copyright. +//--------------------------------------------------------------------------- +// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. +// +// 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 DALLAS SEMICONDUCTOR 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. +// +// Except as contained in this notice, the name of Dallas Semiconductor +// shall not be used except as stated in the Dallas Semiconductor +// Branding Policy. +//-------------------------------------------------------------------------- +*/ + +#include "OneWire.h" + + +OneWire::OneWire(uint8_t pin) +{ + pinMode(pin, INPUT); + bitmask = PIN_TO_BITMASK(pin); + baseReg = PIN_TO_BASEREG(pin); +#if ONEWIRE_SEARCH + reset_search(); +#endif +} + + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return a 0; +// +// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// +uint8_t OneWire::reset(void) +{ + IO_REG_TYPE mask = bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + uint8_t r; + uint8_t retries = 125; + + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); + interrupts(); + // wait until the wire is high... just in case + do { + if (--retries == 0) return 0; + delayMicroseconds(2); + } while ( !DIRECT_READ(reg, mask)); + + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + interrupts(); + delayMicroseconds(480); + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); // allow it to float + delayMicroseconds(70); + r = !DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(410); + return r; +} + +// +// Write a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +void OneWire::write_bit(uint8_t v) +{ + IO_REG_TYPE mask=bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + + if (v & 1) { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(10); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(55); + } else { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(65); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(5); + } +} + +// +// Read a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +uint8_t OneWire::read_bit(void) +{ + IO_REG_TYPE mask=bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + uint8_t r; + + noInterrupts(); + DIRECT_MODE_OUTPUT(reg, mask); + DIRECT_WRITE_LOW(reg, mask); + delayMicroseconds(3); + DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise + delayMicroseconds(10); + r = DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(53); + return r; +} + +// +// Write a byte. The writing code uses the active drivers to raise the +// pin high, if you need power after the write (e.g. DS18S20 in +// parasite power mode) then set 'power' to 1, otherwise the pin will +// go tri-state at the end of the write to avoid heating in a short or +// other mishap. +// +void OneWire::write(uint8_t v, uint8_t power /* = 0 */) { + uint8_t bitMask; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + OneWire::write_bit( (bitMask & v)?1:0); + } + if ( !power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) { + for (uint16_t i = 0 ; i < count ; i++) + write(buf[i]); + if (!power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +// +// Read a byte +// +uint8_t OneWire::read() { + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if ( OneWire::read_bit()) r |= bitMask; + } + return r; +} + +void OneWire::read_bytes(uint8_t *buf, uint16_t count) { + for (uint16_t i = 0 ; i < count ; i++) + buf[i] = read(); +} + +// +// Do a ROM select +// +void OneWire::select(const uint8_t rom[8]) +{ + uint8_t i; + + write(0x55); // Choose ROM + + for (i = 0; i < 8; i++) write(rom[i]); +} + +// +// Do a ROM skip +// +void OneWire::skip() +{ + write(0xCC); // Skip ROM +} + +void OneWire::depower() +{ + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + interrupts(); +} + +#if ONEWIRE_SEARCH + +// +// You need to use this function to start a search again from the beginning. +// You do not need to do it for the first search, though you could. +// +void OneWire::reset_search() +{ + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + for(int i = 7; ; i--) { + ROM_NO[i] = 0; + if ( i == 0) break; + } +} + +// Setup the search to find the device type 'family_code' on the next call +// to search(*newAddr) if it is present. +// +void OneWire::target_search(uint8_t family_code) +{ + // set the search state to find SearchFamily type devices + ROM_NO[0] = family_code; + for (uint8_t i = 1; i < 8; i++) + ROM_NO[i] = 0; + LastDiscrepancy = 64; + LastFamilyDiscrepancy = 0; + LastDeviceFlag = FALSE; +} + +// +// Perform a search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return TRUE : device found, ROM number in ROM_NO buffer +// FALSE : device not found, end of search +// +uint8_t OneWire::search(uint8_t *newAddr, bool search_mode /* = true */) +{ + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number, search_result; + uint8_t id_bit, cmp_id_bit; + + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!LastDeviceFlag) + { + // 1-Wire reset + if (!reset()) + { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + return FALSE; + } + + // issue the search command + if (search_mode == true) { + write(0xF0); // NORMAL SEARCH + } else { + write(0xEC); // CONDITIONAL SEARCH + } + + // loop to do the search + do + { + // read a bit and its complement + id_bit = read_bit(); + cmp_id_bit = read_bit(); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) + break; + else + { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) + search_direction = id_bit; // bit write value for search + else + { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + else + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + + // if 0 was picked then record its position in LastZero + if (search_direction == 0) + { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) + LastFamilyDiscrepancy = last_zero; + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) + ROM_NO[rom_byte_number] |= rom_byte_mask; + else + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + write_bit(search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) + { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } + while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) + { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) + LastDeviceFlag = TRUE; + + search_result = TRUE; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !ROM_NO[0]) + { + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + search_result = FALSE; + } else { + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; + } + return search_result; + } + +#endif + +#if ONEWIRE_CRC +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#if ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (C) 2000 Dallas Semiconductor Corporation +static const uint8_t PROGMEM dscrc_table[] = { + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + crc = pgm_read_byte(dscrc_table + (crc ^ *addr++)); + } + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} +#endif + +#if ONEWIRE_CRC16 +bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc) +{ + crc = ~crc16(input, len, crc); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) +{ + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + for (uint16_t i = 0 ; i < len ; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + return crc; +} +#endif + +#endif diff --git a/firmware/configurable_firmata/src/utility/OneWire.h b/firmware/configurable_firmata/src/utility/OneWire.h new file mode 100644 index 0000000..8753e91 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/OneWire.h @@ -0,0 +1,367 @@ +#ifndef OneWire_h +#define OneWire_h + +#include + +#if ARDUINO >= 100 +#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc +#else +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc +#endif + +// You can exclude certain features from OneWire. In theory, this +// might save some space. In practice, the compiler automatically +// removes unused code (technically, the linker, using -fdata-sections +// and -ffunction-sections when compiling, and Wl,--gc-sections +// when linking), so most of these will not result in any code size +// reduction. Well, unless you try to use the missing features +// and redesign your program to not need them! ONEWIRE_CRC8_TABLE +// is the exception, because it selects a fast but large algorithm +// or a small but slow algorithm. + +// you can exclude onewire_search by defining that to 0 +#ifndef ONEWIRE_SEARCH +#define ONEWIRE_SEARCH 1 +#endif + +// You can exclude CRC checks altogether by defining this to 0 +#ifndef ONEWIRE_CRC +#define ONEWIRE_CRC 1 +#endif + +// Select the table-lookup method of computing the 8-bit CRC +// by setting this to 1. The lookup table enlarges code size by +// about 250 bytes. It does NOT consume RAM (but did in very +// old versions of OneWire). If you disable this, a slower +// but very compact algorithm is used. +#ifndef ONEWIRE_CRC8_TABLE +#define ONEWIRE_CRC8_TABLE 1 +#endif + +// You can allow 16-bit CRC checks by defining this to 1 +// (Note that ONEWIRE_CRC must also be 1.) +#ifndef ONEWIRE_CRC16 +#define ONEWIRE_CRC16 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +// Platform specific I/O definitions + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM asm("r30") +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (*((base)+512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) + +#elif defined(__MKL26Z64__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) + +#elif defined(__SAM3X8E__) +// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. +// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 +// If you have trouble with OneWire on Arduino Due, please check the +// status of delayMicroseconds() before reporting a bug in OneWire! +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const uint8_t *)(addr)) +#endif + +#elif defined(__PIC32MX__) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#elif defined(ARDUINO_ARCH_ESP8266) +#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) +#define PIN_TO_BITMASK(pin) (1 << pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS +#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS +#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS +#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS +#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS + +#elif defined(__SAMD21G18A__) +#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) + +#elif defined(RBL_NRF51822) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) +#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) +#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) +#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) +#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) + +#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ + +#include "scss_registers.h" +#include "portable.h" +#include "avr/pgmspace.h" + +#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) +#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) +#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) +#define DIR_OFFSET_SS 0x01 +#define DIR_OFFSET_SOC 0x04 +#define EXT_PORT_OFFSET_SS 0x0A +#define EXT_PORT_OFFSET_SOC 0x50 + +/* GPIO registers base address */ +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) +#define PIN_TO_BITMASK(pin) pin +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + IO_REG_TYPE ret; + if (SS_GPIO == GPIO_TYPE(pin)) { + ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); + } else { + ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); + } + return ((ret >> GPIO_ID(pin)) & 0x01); +} + +static inline __attribute__((always_inline)) +void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); + } +} + +#define DIRECT_READ(base, pin) directRead(base, pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) + +#else +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE unsigned int +#define IO_REG_ASM +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." + +#endif + + +class OneWire +{ + private: + IO_REG_TYPE bitmask; + volatile IO_REG_TYPE *baseReg; + +#if ONEWIRE_SEARCH + // global search state + unsigned char ROM_NO[8]; + uint8_t LastDiscrepancy; + uint8_t LastFamilyDiscrepancy; + uint8_t LastDeviceFlag; +#endif + + public: + OneWire( uint8_t pin); + + // Perform a 1-Wire reset cycle. Returns 1 if a device responds + // with a presence pulse. Returns 0 if there is no device or the + // bus is shorted or otherwise held low for more than 250uS + uint8_t reset(void); + + // Issue a 1-Wire rom select command, you do the reset first. + void select(const uint8_t rom[8]); + + // Issue a 1-Wire rom skip command, to address all on bus. + void skip(void); + + // Write a byte. If 'power' is one then the wire is held high at + // the end for parasitically powered devices. You are responsible + // for eventually depowering it by calling depower() or doing + // another read or write. + void write(uint8_t v, uint8_t power = 0); + + void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); + + // Read a byte. + uint8_t read(void); + + void read_bytes(uint8_t *buf, uint16_t count); + + // Write a bit. The bus is always left powered at the end, see + // note in write() about that. + void write_bit(uint8_t v); + + // Read a bit. + uint8_t read_bit(void); + + // Stop forcing power onto the bus. You only need to do this if + // you used the 'power' flag to write() or used a write_bit() call + // and aren't about to do another read or write. You would rather + // not leave this powered if you don't have to, just in case + // someone shorts your bus. + void depower(void); + +#if ONEWIRE_SEARCH + // Clear the search state so that if will start from the beginning again. + void reset_search(); + + // Setup the search to find the device type 'family_code' on the next call + // to search(*newAddr) if it is present. + void target_search(uint8_t family_code); + + // Look for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are + // no devices, or you have already retrieved all of them. It + // might be a good idea to check the CRC to make sure you didn't + // get garbage. The order is deterministic. You will always get + // the same devices in the same order. + uint8_t search(uint8_t *newAddr, bool search_mode = true); +#endif + +#if ONEWIRE_CRC + // Compute a Dallas Semiconductor 8 bit CRC, these are used in the + // ROM and scratchpad registers. + static uint8_t crc8(const uint8_t *addr, uint8_t len); + +#if ONEWIRE_CRC16 + // Compute the 1-Wire CRC16 and compare it against the received CRC. + // Example usage (reading a DS2408): + // // Put everything in a buffer so we can compute the CRC easily. + // uint8_t buf[13]; + // buf[0] = 0xF0; // Read PIO Registers + // buf[1] = 0x88; // LSB address + // buf[2] = 0x00; // MSB address + // WriteBytes(net, buf, 3); // Write 3 cmd bytes + // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + // if (!CheckCRC16(buf, 11, &buf[11])) { + // // Handle error. + // } + // + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param inverted_crc - The two CRC16 bytes in the received data. + // This should just point into the received data, + // *not* at a 16-bit integer. + // @param crc - The crc starting value (optional) + // @return True, iff the CRC matches. + static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0); + + // Compute a Dallas Semiconductor 16 bit CRC. This is required to check + // the integrity of data received from many 1-Wire devices. Note that the + // CRC computed here is *not* what you'll get from the 1-Wire network, + // for two reasons: + // 1) The CRC is transmitted bitwise inverted. + // 2) Depending on the endian-ness of your processor, the binary + // representation of the two-byte return value may have a different + // byte order than the two bytes you get from 1-Wire. + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param crc - The crc starting value (optional) + // @return The CRC16, as defined by Dallas Semiconductor. + static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0); +#endif +#endif +}; + +#endif diff --git a/firmware/configurable_firmata/src/utility/WiFiClientStream.h b/firmware/configurable_firmata/src/utility/WiFiClientStream.h new file mode 100644 index 0000000..7fd30af --- /dev/null +++ b/firmware/configurable_firmata/src/utility/WiFiClientStream.h @@ -0,0 +1,105 @@ +/* + WiFiClientStream.h + + An Arduino Stream that wraps an instance of a WiFiClient. For use + with legacy Arduino WiFi shield and other boards and shields that + are compatible with the Arduino WiFi library. + + Copyright (C) 2016 Jens B. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Parts of this class are based on + + - EthernetClientStream - Copyright (C) 2013 Norbert Truchsess. All rights reserved. + + published under the same license. + + Last updated April 23rd, 2016 + */ + +#ifndef WIFI_CLIENT_STREAM_H +#define WIFI_CLIENT_STREAM_H + +#include "WiFiStream.h" + +#define MILLIS_RECONNECT 5000 + +class WiFiClientStream : public WiFiStream +{ +protected: + uint32_t _time_connect = 0; + + /** + * check if TCP client is connected + * @return true if connected + */ + virtual inline bool connect_client() + { + if ( _connected ) + { + if ( _client && _client.connected() ) return true; + stop(); + } + + // active TCP connect + if ( WiFi.status() == WL_CONNECTED ) + { + // if the client is disconnected, try to reconnect every 5 seconds + if ( millis() - _time_connect >= MILLIS_RECONNECT ) + { + _connected = _client.connect( _remote_ip, _port ); + if ( !_connected ) + { + _time_connect = millis(); + } + else if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_CONNECTED); + } + } + } + + return _connected; + } + +public: + /** + * create a WiFi stream with a TCP client + */ + WiFiClientStream(IPAddress server_ip, uint16_t server_port) : WiFiStream(server_ip, server_port) {} + + /** + * maintain WiFi and TCP connection + * @return true if WiFi and TCP connection are established + */ + virtual inline bool maintain() + { + return connect_client(); + } + + /** + * stop client connection + */ + virtual inline void stop() + { + if ( _client) + { + _client.stop(); + if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_DISCONNECTED); + } + } + _connected = false; + _time_connect = millis(); + } + +}; + +#endif //WIFI_CLIENT_STREAM_H diff --git a/firmware/configurable_firmata/src/utility/WiFiServerStream.h b/firmware/configurable_firmata/src/utility/WiFiServerStream.h new file mode 100644 index 0000000..1404b05 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/WiFiServerStream.h @@ -0,0 +1,107 @@ +/* + WiFiServerStream.h + + An Arduino Stream extension for a WiFiClient or WiFiServer to be used + with legacy Arduino WiFi shield and other boards and shields that + are compatible with the Arduino WiFi library. + + Copyright (C) 2016 Jens B. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Parts of this class are based on + + - WiFiStream - Copyright (C) 2015-2016 Jesse Frush. All rights reserved. + + published under the same license. + + Last updated April 23rd, 2016 + */ + +#ifndef WIFI_SERVER_STREAM_H +#define WIFI_SERVER_STREAM_H + +#include "WiFiStream.h" + +class WiFiServerStream : public WiFiStream +{ +protected: + WiFiServer _server = WiFiServer(3030); + bool _listening = false; + + /** + * check if TCP client is connected + * @return true if connected + */ + virtual inline bool connect_client() + { + if ( _connected ) + { + if ( _client && _client.connected() ) return true; + stop(); + } + + // passive TCP connect (accept) + WiFiClient newClient = _server.available(); + if ( !newClient ) return false; + _client = newClient; + _connected = true; + if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_CONNECTED); + } + + return true; + } + +public: + /** + * create a WiFi stream with a TCP server + */ + WiFiServerStream(uint16_t server_port) : WiFiStream(server_port) {} + + /** + * maintain WiFi and TCP connection + * @return true if WiFi and TCP connection are established + */ + virtual inline bool maintain() + { + if ( connect_client() ) return true; + + stop(); + + if ( !_listening && WiFi.status() == WL_CONNECTED ) + { + // start TCP server after first WiFi connect + _server = WiFiServer(_port); + _server.begin(); + _listening = true; + } + + return false; + } + + /** + * stop client connection + */ + virtual inline void stop() + { + if ( _client) + { + _client.stop(); + if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_DISCONNECTED); + } + } + _connected = false; + } + +}; + +#endif //WIFI_SERVER_STREAM_H diff --git a/firmware/configurable_firmata/src/utility/WiFiStream.cpp b/firmware/configurable_firmata/src/utility/WiFiStream.cpp new file mode 100644 index 0000000..9b54a5a --- /dev/null +++ b/firmware/configurable_firmata/src/utility/WiFiStream.cpp @@ -0,0 +1,4 @@ +/* + * Implementation is in WiFiStream.h to avoid linker issues. Legacy WiFi and modern WiFi101 both define WiFiClass which + * will cause linker errors whenever Firmata.h is included. + */ diff --git a/firmware/configurable_firmata/src/utility/WiFiStream.h b/firmware/configurable_firmata/src/utility/WiFiStream.h new file mode 100644 index 0000000..1ad44bb --- /dev/null +++ b/firmware/configurable_firmata/src/utility/WiFiStream.h @@ -0,0 +1,226 @@ +/* + WiFiStream.h + + An Arduino Stream extension for a WiFiClient or WiFiServer to be used + with legacy Arduino WiFi shield and other boards and shields that + are compatible with the Arduino WiFi library. + + Copyright (C) 2015-2016 Jesse Frush. All rights reserved. + Copyright (C) 2016 Jens B. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Last updated April 23rd, 2016 + */ + +#ifndef WIFI_STREAM_H +#define WIFI_STREAM_H + +#include +#include + +#define HOST_CONNECTION_DISCONNECTED 0 +#define HOST_CONNECTION_CONNECTED 1 + +extern "C" { + // callback function types + typedef void (*hostConnectionCallbackFunction)(byte); +} + +class WiFiStream : public Stream +{ +protected: + WiFiClient _client; + bool _connected = false; + hostConnectionCallbackFunction _currentHostConnectionCallback; + + //configuration members + IPAddress _local_ip; // DHCP + IPAddress _subnet; + IPAddress _gateway; + IPAddress _remote_ip; + uint16_t _port; + uint8_t _key_idx; //WEP + const char *_key = nullptr; //WEP + const char *_passphrase = nullptr; //WPA + char *_ssid = nullptr; + + /** + * check if TCP client is connected + * @return true if connected + */ + virtual bool connect_client() = 0; + +public: + /** constructor for TCP server */ + WiFiStream(uint16_t server_port) : _port(server_port) {} + + /** constructor for TCP client */ + WiFiStream(IPAddress server_ip, uint16_t server_port) : _remote_ip(server_ip), _port(server_port) {} + + inline void attach( hostConnectionCallbackFunction newFunction ) { _currentHostConnectionCallback = newFunction; } + +/****************************************************************************** + * network configuration + ******************************************************************************/ + +#ifndef ESP8266 + /** + * configure a static local IP address without defining the local network + * DHCP will be used as long as local IP address is not defined + */ + inline void config(IPAddress local_ip) + { + _local_ip = local_ip; + WiFi.config( local_ip ); + } +#endif + + /** + * configure a static local IP address + * DHCP will be used as long as local IP address is not defined + */ + inline void config(IPAddress local_ip, IPAddress gateway, IPAddress subnet) + { + _local_ip = local_ip; + _subnet = subnet; + _gateway = gateway; +#ifndef ESP8266 + WiFi.config( local_ip, IPAddress(0, 0, 0, 0), gateway, subnet ); +#else + WiFi.config( local_ip, gateway, subnet ); +#endif + } + + /** + * @return local IP address + */ + inline IPAddress getLocalIP() + { + return WiFi.localIP(); + } + +/****************************************************************************** + * network functions + ******************************************************************************/ + + /** + * maintain WiFi and TCP connection + * @return true if WiFi and TCP connection are established + */ + virtual bool maintain() = 0; + +#ifdef ESP8266 + /** + * get status of TCP connection + * @return status of TCP connection + * CLOSED = 0 (typical) + * LISTEN = 1 (not used) + * SYN_SENT = 2 + * SYN_RCVD = 3 + * ESTABLISHED = 4 (typical) + * FIN_WAIT_1 = 5 + * FIN_WAIT_2 = 6 + * CLOSE_WAIT = 7 + * CLOSING = 8 + * LAST_ACK = 9 + * TIME_WAIT = 10 + */ + inline uint8_t status() + { + return _client.status(); + } +#endif + + /** + * close TCP client connection + */ + virtual void stop() = 0; + +/****************************************************************************** + * WiFi configuration + ******************************************************************************/ + + /** + * initialize WiFi without security (open) and initiate client connection + * if WiFi connection is already established + * @return WL_CONNECTED if WiFi connection is established + */ + inline int begin(char *ssid) + { + _ssid = ssid; + + WiFi.begin(ssid); + int result = WiFi.status(); + return WiFi.status(); + } + +#ifndef ESP8266 + /** + * initialize WiFi with WEP security and initiate client connection + * if WiFi connection is already established + * @return WL_CONNECTED if WiFi connection is established + */ + inline int begin(char *ssid, uint8_t key_idx, const char *key) + { + _ssid = ssid; + _key_idx = key_idx; + _key = key; + + WiFi.begin( ssid, key_idx, key ); + return WiFi.status(); + } +#endif + + /** + * initialize WiFi with WPA-PSK security and initiate client connection + * if WiFi connection is already established + * @return WL_CONNECTED if WiFi connection is established + */ + inline int begin(char *ssid, const char *passphrase) + { + _ssid = ssid; + _passphrase = passphrase; + + WiFi.begin(ssid, passphrase); + return WiFi.status(); + } + + +/****************************************************************************** + * stream functions + ******************************************************************************/ + + inline int available() + { + return connect_client() ? _client.available() : 0; + } + + inline void flush() + { + if( _client ) _client.flush(); + } + + inline int peek() + { + return connect_client() ? _client.peek(): 0; + } + + inline int read() + { + return connect_client() ? _client.read() : -1; + } + + inline size_t write(uint8_t byte) + { + return connect_client() ? _client.write( byte ) : 0; + } + +}; + +#endif //WIFI_STREAM_H diff --git a/firmware/configurable_firmata/src/utility/firmataDebug.h b/firmware/configurable_firmata/src/utility/firmataDebug.h new file mode 100644 index 0000000..6e364b0 --- /dev/null +++ b/firmware/configurable_firmata/src/utility/firmataDebug.h @@ -0,0 +1,14 @@ +#ifndef FIRMATA_DEBUG_H +#define FIRMATA_DEBUG_H + +#ifdef SERIAL_DEBUG + #define DEBUG_BEGIN(baud) Serial.begin(baud); while(!Serial) {;} + #define DEBUG_PRINTLN(x) Serial.println (x) + #define DEBUG_PRINT(x) Serial.print (x) +#else + #define DEBUG_BEGIN(baud) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINT(x) +#endif + +#endif /* FIRMATA_DEBUG_H */ diff --git a/firmware/configurable_firmata/test/firmata_test/firmata_test.ino b/firmware/configurable_firmata/test/firmata_test/firmata_test.ino new file mode 100644 index 0000000..f96d119 --- /dev/null +++ b/firmware/configurable_firmata/test/firmata_test/firmata_test.ino @@ -0,0 +1,136 @@ +/* + * To run this test suite, you must first install the ArduinoUnit library + * to your Arduino/libraries/ directory. + * You can get ArduinoUnit here: https://github.com/mmurdoch/arduinounit + * Download version 2.0 or greater. + */ + +#include +#include + +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Test::run(); +} + +test(beginPrintsVersion) +{ + FakeStream stream; + + Firmata.begin(stream); + + char expected[] = { + REPORT_VERSION, + FIRMATA_MAJOR_VERSION, + FIRMATA_MINOR_VERSION, + 0 + }; + assertEqual(expected, stream.bytesWritten()); +} + +void processMessage(const byte *message, size_t length) +{ + FakeStream stream; + Firmata.begin(stream); + + for (size_t i = 0; i < length; i++) { + stream.nextByte(message[i]); + Firmata.processInput(); + } +} + +byte _digitalPort; +int _digitalPortValue; +void writeToDigitalPort(byte port, int value) +{ + _digitalPort = port; + _digitalPortValue = value; +} + +void setupDigitalPort() +{ + _digitalPort = 0; + _digitalPortValue = 0; +} + +test(processWriteDigital_0) +{ + setupDigitalPort(); + Firmata.attach(DIGITAL_MESSAGE, writeToDigitalPort); + + byte message[] = { DIGITAL_MESSAGE, 0, 0 }; + processMessage(message, 3); + + assertEqual(0, _digitalPortValue); +} + +test(processWriteDigital_127) +{ + setupDigitalPort(); + Firmata.attach(DIGITAL_MESSAGE, writeToDigitalPort); + + byte message[] = { DIGITAL_MESSAGE, 127, 0 }; + processMessage(message, 3); + + assertEqual(127, _digitalPortValue); +} + +test(processWriteDigital_128) +{ + setupDigitalPort(); + Firmata.attach(DIGITAL_MESSAGE, writeToDigitalPort); + + byte message[] = { DIGITAL_MESSAGE, 0, 1 }; + processMessage(message, 3); + + assertEqual(128, _digitalPortValue); +} + +test(processWriteLargestDigitalValue) +{ + setupDigitalPort(); + Firmata.attach(DIGITAL_MESSAGE, writeToDigitalPort); + + byte message[] = { DIGITAL_MESSAGE, 0x7F, 0x7F }; + processMessage(message, 3); + + // Maximum of 14 bits can be set (B0011111111111111) + assertEqual(0x3FFF, _digitalPortValue); +} + +test(defaultDigitalWritePortIsZero) +{ + setupDigitalPort(); + Firmata.attach(DIGITAL_MESSAGE, writeToDigitalPort); + + byte message[] = { DIGITAL_MESSAGE, 0, 0 }; + processMessage(message, 3); + + assertEqual(0, _digitalPort); +} + +test(specifiedDigitalWritePort) +{ + setupDigitalPort(); + Firmata.attach(DIGITAL_MESSAGE, writeToDigitalPort); + + byte message[] = { DIGITAL_MESSAGE + 1, 0, 0 }; + processMessage(message, 3); + + assertEqual(1, _digitalPort); +} + +test(setFirmwareVersionDoesNotLeakMemory) +{ + Firmata.setFirmwareVersion(1, 0); + int initialMemory = freeMemory(); + + Firmata.setFirmwareVersion(1, 0); + + assertEqual(0, initialMemory - freeMemory()); +} diff --git a/firmware/configurable_firmata/test/readme.md b/firmware/configurable_firmata/test/readme.md new file mode 100644 index 0000000..726cbcb --- /dev/null +++ b/firmware/configurable_firmata/test/readme.md @@ -0,0 +1,13 @@ +#Testing Firmata + +Tests tests are written using the [ArduinoUnit](https://github.com/mmurdoch/arduinounit) library (version 2.0). + +Follow the instructions in the [ArduinoUnit readme](https://github.com/mmurdoch/arduinounit/blob/master/readme.md) to install the library. + +Compile and upload the test sketch as you would any other sketch. Then open the +Serial Monitor to view the test results. + +If you make changes to Firmata.cpp, run the tests in /test/ to ensure +that your changes have not produced any unexpected errors. + +You should also perform manual tests against actual hardware. diff --git a/firmware/configurable_firmata/test/stepper_test/stepper_test.ino b/firmware/configurable_firmata/test/stepper_test/stepper_test.ino new file mode 100644 index 0000000..065c4b5 --- /dev/null +++ b/firmware/configurable_firmata/test/stepper_test/stepper_test.ino @@ -0,0 +1,91 @@ +/* + * To run this test suite, you must first install the ArduinoUnit library + * to your Arduino/libraries/ directory. + * You can get ArduinoUnit here: https://github.com/mmurdoch/arduinounit + * Download version 2.0 or greater. + */ + +#include +#include + +void setup() +{ + Serial.begin(9600); +} + +test(decode_custom_float) +{ + AccelStepperFirmata stepper; + float result; + + result = stepper.decodeCustomFloat( 110, 92, 44, 32 ); + result = fabs(732.782 - result); + + assertLessOrEqual(result,.01); + +} + +test(decode_negative_custom_float) +{ + AccelStepperFirmata stepper; + float result; + + result = stepper.decodeCustomFloat( 110, 92, 44, 96 ); + result = fabs(-732.782 - result); + + assertLessOrEqual(result,.01); + +} + +test(encode_32_bit_signed_integer) +{ + AccelStepperFirmata stepper; + byte result[5]; + + stepper.encode32BitSignedInteger(5786, result); + + assertEqual(result[0], 26); + assertEqual(result[1], 45); + assertEqual(result[2], 0); + assertEqual(result[3], 0); + assertEqual(result[4], 0); +} + +test(encode_negative_32_bit_signed_integer) +{ + AccelStepperFirmata stepper; + byte result[5]; + + stepper.encode32BitSignedInteger(-5786, result); + + assertEqual(result[0], 26); + assertEqual(result[1], 45); + assertEqual(result[2], 0); + assertEqual(result[3], 0); + assertEqual(result[4], 8); +} + +test(decode_32_bit_signed_integer) +{ + AccelStepperFirmata stepper; + long result; + + result = stepper.decode32BitSignedInteger(26, 45, 0, 0, 0); + + assertEqual(result, 5786); +} + +test(decode_negative_32_bit_signed_integer) +{ + AccelStepperFirmata stepper; + long result; + + result = stepper.decode32BitSignedInteger(26, 45, 0, 0, 8); + + assertEqual(result, -5786); +} + +void loop() +{ + Test::run(); +} diff --git a/firmware/main.ino b/firmware/main.ino new file mode 100644 index 0000000..5f66dd5 --- /dev/null +++ b/firmware/main.ino @@ -0,0 +1,111 @@ +/* + * esp2io.ino generated by FirmataBuilder + * Thu Jun 11 2020 18:44:36 GMT-0400 (EDT) + * Modified by Josenalde Oliveira - June 2020 + */ + +#include + +//#include +//DigitalInputFirmata digitalInput; +// +//#include +//DigitalOutputFirmata digitalOutput; + +#include +AnalogInputFirmata analogInput; + +//#include +//AnalogOutputFirmata analogOutput; + +#include +FirmataExt firmataExt; + +//#include + +//#include +//irmataReporting reporting; + +void sysexCallback(byte, byte, byte*); +void systemResetCallback(); + +void initTransport() +{ + // Uncomment to save a couple of seconds by disabling the startup blink sequence. + // Firmata.disableBlinkVersion(); + Firmata.begin(57600); +} + +void initFirmata() +{ + Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + +// firmataExt.addFeature(digitalInput); +// firmataExt.addFeature(digitalOutput); + firmataExt.addFeature(analogInput); +// firmataExt.addFeature(analogOutput); unnecessary +// firmataExt.addFeature(reporting); +// + Firmata.attach(SYSTEM_RESET, systemResetCallback); + Firmata.attach(START_SYSEX, sysexCallback); +} + +void setup() +{ + initFirmata(); + + initTransport(); + // with these lines the framework does not respond! posted in 11.06.2020 + //Firmata.parse(SYSTEM_RESET); + //Firmata.parse(START_SYSEX); + + analogReadResolution(12); + // if you want to directly test specific esp-32 pins (without framework) + //pinMode(2, OUTPUT); + +} + +void loop() +{ + //digitalInput.report(); + + while(Firmata.available()) { + Firmata.processInput(); + } + + //if (reporting.elapsed()) { + // analogInput.report(); + //} + + #ifdef FIRMATA_SERIAL_FEATURE + serialFeature.update(); + #endif +} + +void systemResetCallback() +{ + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_ANALOG(i)) { + Firmata.setPinMode(i, ANALOG); + } else if (IS_PIN_DIGITAL(i)) { + Firmata.setPinMode(i, OUTPUT); + } + } + firmataExt.reset(); +} + +/*============================================================================== + * SYSEX-BASED commands + *============================================================================*/ + +void sysexCallback(byte command, byte argc, byte *argv) +{ + +// test if the callback is being called from framework + //digitalWrite(2, HIGH); + //delay(5000); + //digitalWrite(2, LOW); + firmataExt.handleSysex(command, argc, argv); + + +} diff --git a/server/esp32_firmata/firmata-io/.tesselignore b/server/esp32_firmata/firmata-io/.tesselignore new file mode 100644 index 0000000..ca90b42 --- /dev/null +++ b/server/esp32_firmata/firmata-io/.tesselignore @@ -0,0 +1,2 @@ +package-lock.json +readme.md diff --git a/server/esp32_firmata/firmata-io/lib/encoder7bit.js b/server/esp32_firmata/firmata-io/lib/encoder7bit.js new file mode 100644 index 0000000..0274bfc --- /dev/null +++ b/server/esp32_firmata/firmata-io/lib/encoder7bit.js @@ -0,0 +1,49 @@ +"use strict"; +/** + * "Inspired" by Encoder7Bit.h/Encoder7Bit.cpp in the + * Firmata source code. + */ +module.exports = { + to7BitArray(data) { + let shift = 0; + let previous = 0; + const output = []; + + for (let byte of data) { + if (shift === 0) { + output.push(byte & 0x7f); + shift++; + previous = byte >> 7; + } else { + output.push(((byte << shift) & 0x7f) | previous); + if (shift === 6) { + output.push(byte >> 1); + shift = 0; + } else { + shift++; + previous = byte >> (8 - shift); + } + } + } + + /* istanbul ignore else */ + if (shift > 0) { + output.push(previous); + } + + return output; + }, + from7BitArray(encoded) { + const expectedBytes = encoded.length * 7 >> 3; + const decoded = []; + + for (let i = 0; i < expectedBytes; i++) { + const j = i << 3; + const pos = (j / 7) >>> 0; + const shift = j % 7; + decoded[i] = (encoded[pos] >> shift) | ((encoded[pos + 1] << (7 - shift)) & 0xFF); + } + + return decoded; + } +}; diff --git a/server/esp32_firmata/firmata-io/lib/firmata.js b/server/esp32_firmata/firmata-io/lib/firmata.js new file mode 100644 index 0000000..d903dad --- /dev/null +++ b/server/esp32_firmata/firmata-io/lib/firmata.js @@ -0,0 +1,2691 @@ +"use strict"; + +// Built-in Dependencies +const Emitter = require("events"); + +// Internal Dependencies +const Encoder7Bit = require("./encoder7bit"); +const OneWire = require("./onewireutils"); + +// Program specifics +const i2cActive = new Map(); + +/** + * constants + */ + +const ANALOG_MAPPING_QUERY = 0x69; +const ANALOG_MAPPING_RESPONSE = 0x6A; +const ANALOG_MESSAGE = 0xE0; +const CAPABILITY_QUERY = 0x6B; +const CAPABILITY_RESPONSE = 0x6C; +const DIGITAL_MESSAGE = 0x90; +const END_SYSEX = 0xF7; +const EXTENDED_ANALOG = 0x6F; +const I2C_CONFIG = 0x78; +const I2C_REPLY = 0x77; +const I2C_REQUEST = 0x76; +const I2C_READ_MASK = 0x18; // 0b00011000 +// const I2C_END_TX_MASK = 0x40; // 0b01000000 +const ONEWIRE_CONFIG_REQUEST = 0x41; +const ONEWIRE_DATA = 0x73; +const ONEWIRE_DELAY_REQUEST_BIT = 0x10; +const ONEWIRE_READ_REPLY = 0x43; +const ONEWIRE_READ_REQUEST_BIT = 0x08; +const ONEWIRE_RESET_REQUEST_BIT = 0x01; +const ONEWIRE_SEARCH_ALARMS_REPLY = 0x45; +const ONEWIRE_SEARCH_ALARMS_REQUEST = 0x44; +const ONEWIRE_SEARCH_REPLY = 0x42; +const ONEWIRE_SEARCH_REQUEST = 0x40; +const ONEWIRE_WITHDATA_REQUEST_BITS = 0x3C; +const ONEWIRE_WRITE_REQUEST_BIT = 0x20; +const PIN_MODE = 0xF4; +const PIN_STATE_QUERY = 0x6D; +const PIN_STATE_RESPONSE = 0x6E; +const PING_READ = 0x75; +// const PULSE_IN = 0x74; +// const PULSE_OUT = 0x73; +const QUERY_FIRMWARE = 0x79; +const REPORT_ANALOG = 0xC0; +const REPORT_DIGITAL = 0xD0; +const REPORT_VERSION = 0xF9; +const SAMPLING_INTERVAL = 0x7A; +const SERVO_CONFIG = 0x70; +const SERIAL_MESSAGE = 0x60; +const SERIAL_CONFIG = 0x10; +const SERIAL_WRITE = 0x20; +const SERIAL_READ = 0x30; +const SERIAL_REPLY = 0x40; +const SERIAL_CLOSE = 0x50; +const SERIAL_FLUSH = 0x60; +const SERIAL_LISTEN = 0x70; +const START_SYSEX = 0xF0; +const STEPPER = 0x72; +const ACCELSTEPPER = 0x62; +const STRING_DATA = 0x71; +const SYSTEM_RESET = 0xFF; + +const MAX_PIN_COUNT = 128; + +const SYM_sendOneWireSearch = Symbol("sendOneWireSearch"); +const SYM_sendOneWireRequest = Symbol("sendOneWireRequest"); + +/** + * MIDI_RESPONSE contains functions to be called when we receive a MIDI message from the arduino. + * used as a switch object as seen here http://james.padolsey.com/javascript/how-to-avoid-switch-case-syndrome/ + * @private + */ + +const MIDI_RESPONSE = { + + /** + * Handles a REPORT_VERSION response and emits the reportversion event. + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [REPORT_VERSION](board) { + board.version.major = board.buffer[1]; + board.version.minor = board.buffer[2]; + board.emit("reportversion"); + }, + + /** + * Handles a ANALOG_MESSAGE response and emits "analog-read" and "analog-read-"+n events where n is the pin number. + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [ANALOG_MESSAGE](board) { + const pin = board.buffer[0] & 0x0F; + const value = board.buffer[1] | (board.buffer[2] << 7); + + /* istanbul ignore else */ + if (board.pins[board.analogPins[pin]]) { + board.pins[board.analogPins[pin]].value = value; + } + + board.emit(`analog-read-${pin}`, value); + board.emit("analog-read", { + pin, + value, + }); + }, + + /** + * Handles a DIGITAL_MESSAGE response and emits: + * "digital-read" + * "digital-read-"+n + * + * Where n is the pin number. + * + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [DIGITAL_MESSAGE](board) { + const port = board.buffer[0] & 0x0F; + const portValue = board.buffer[1] | (board.buffer[2] << 7); + + for (let i = 0; i < 8; i++) { + const pin = 8 * port + i; + const pinRec = board.pins[pin]; + const bit = 1 << i; + + if (pinRec && (pinRec.mode === board.MODES.INPUT || pinRec.mode === board.MODES.PULLUP)) { + pinRec.value = (portValue >> (i & 0x07)) & 0x01; + + if (pinRec.value) { + board.ports[port] |= bit; + } else { + board.ports[port] &= ~bit; + } + + let {value} = pinRec; + + board.emit(`digital-read-${pin}`, value); + board.emit("digital-read", { + pin, + value, + }); + } + } + }, +}; + +/** + * SYSEX_RESPONSE contains functions to be called when we receive a SYSEX message from the arduino. + * used as a switch object as seen here http://james.padolsey.com/javascript/how-to-avoid-switch-case-syndrome/ + * @private + */ + +const SYSEX_RESPONSE = { + + /** + * Handles a QUERY_FIRMWARE response and emits the "queryfirmware" event + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [QUERY_FIRMWARE](board) { + const length = board.buffer.length - 2; + const buffer = Buffer.alloc(Math.round((length - 4) / 2)); + let byte = 0; + let offset = 0; + + for (let i = 4; i < length; i += 2) { + byte = ((board.buffer[i] & 0x7F) | ((board.buffer[i + 1] & 0x7F) << 7)) & 0xFF; + buffer.writeUInt8(byte, offset++); + } + + board.firmware = { + name: buffer.toString(), + version: { + major: board.buffer[2], + minor: board.buffer[3], + }, + }, + + board.emit("queryfirmware"); + }, + + /** + * Handles a CAPABILITY_RESPONSE response and emits the "capability-query" event + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [CAPABILITY_RESPONSE](board) { + const modes = Object.keys(board.MODES).map((key) => board.MODES[key]); + let mode, resolution; + let capability = 0; + + function supportedModes(capability) { + return modes.reduce((accum, mode) => { + if (capability & (1 << mode)) { + accum.push(mode); + } + return accum; + }, []); + } + + // Only create pins if none have been previously created on the instance. + if (!board.pins.length) { + for (let i = 2, n = 0; i < board.buffer.length - 1; i++) { + if (board.buffer[i] === 0x7F) { + board.pins.push({ + supportedModes: supportedModes(capability), + mode: undefined, + value: 0, + report: 1, + }); + capability = 0; + n = 0; + continue; + } + if (n === 0) { + mode = board.buffer[i]; + resolution = (1 << board.buffer[i + 1]) - 1; + capability |= (1 << mode); + + // ADC Resolution of Analog Inputs + if (mode === board.MODES.ANALOG && board.RESOLUTION.ADC === null) { + board.RESOLUTION.ADC = resolution; + } + + // PWM Resolution of PWM Outputs + if (mode === board.MODES.PWM && board.RESOLUTION.PWM === null) { + board.RESOLUTION.PWM = resolution; + } + + // DAC Resolution of DAC Outputs + // if (mode === board.MODES.DAC && board.RESOLUTION.DAC === null) { + // board.RESOLUTION.DAC = resolution; + // } + } + n ^= 1; + } + } + + board.emit("capability-query"); + }, + + /** + * Handles a PIN_STATE response and emits the 'pin-state-'+n event where n is the pin number. + * + * Note about pin state: For output modes, the state is any value that has been + * previously written to the pin. For input modes, the state is the status of + * the pullup resistor. + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [PIN_STATE_RESPONSE](board) { + let pin = board.buffer[2]; + board.pins[pin].mode = board.buffer[3]; + board.pins[pin].state = board.buffer[4]; + if (board.buffer.length > 6) { + board.pins[pin].state |= (board.buffer[5] << 7); + } + if (board.buffer.length > 7) { + board.pins[pin].state |= (board.buffer[6] << 14); + } + board.emit(`pin-state-${pin}`); + }, + + /** + * Handles a ANALOG_MAPPING_RESPONSE response and emits the "analog-mapping-query" event. + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [ANALOG_MAPPING_RESPONSE](board) { + let pin = 0; + let currentValue; + for (let i = 2; i < board.buffer.length - 1; i++) { + currentValue = board.buffer[i]; + board.pins[pin].analogChannel = currentValue; + if (currentValue !== 127) { + board.analogPins.push(pin); + } + pin++; + } + board.emit("analog-mapping-query"); + }, + + /** + * Handles a I2C_REPLY response and emits the "I2C-reply-"+n event where n is the slave address of the I2C device. + * The event is passed the buffer of data sent from the I2C Device + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [I2C_REPLY](board) { + const reply = []; + const address = (board.buffer[2] & 0x7F) | ((board.buffer[3] & 0x7F) << 7); + const register = (board.buffer[4] & 0x7F) | ((board.buffer[5] & 0x7F) << 7); + + for (let i = 6, length = board.buffer.length - 1; i < length; i += 2) { + reply.push(board.buffer[i] | (board.buffer[i + 1] << 7)); + } + + board.emit(`I2C-reply-${address}-${register}`, reply); + }, + + [ONEWIRE_DATA](board) { + const subCommand = board.buffer[2]; + + if (!SYSEX_RESPONSE[subCommand]) { + return; + } + + SYSEX_RESPONSE[subCommand](board); + }, + + [ONEWIRE_SEARCH_REPLY](board) { + const pin = board.buffer[3]; + const buffer = board.buffer.slice(4, board.buffer.length - 1); + + board.emit(`1-wire-search-reply-${pin}`, OneWire.readDevices(buffer)); + }, + + [ONEWIRE_SEARCH_ALARMS_REPLY](board) { + const pin = board.buffer[3]; + const buffer = board.buffer.slice(4, board.buffer.length - 1); + + board.emit(`1-wire-search-alarms-reply-${pin}`, OneWire.readDevices(buffer)); + }, + + [ONEWIRE_READ_REPLY](board) { + const encoded = board.buffer.slice(4, board.buffer.length - 1); + const decoded = Encoder7Bit.from7BitArray(encoded); + const correlationId = (decoded[1] << 8) | decoded[0]; + + board.emit(`1-wire-read-reply-${correlationId}`, decoded.slice(2)); + }, + + /** + * Handles a STRING_DATA response and logs the string to the console. + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [STRING_DATA](board) { + board.emit("string", Buffer.from(board.buffer.slice(2, -1)).toString().replace(/\0/g, "")); + }, + + /** + * Response from pingRead + */ + + [PING_READ](board) { + const pin = (board.buffer[2] & 0x7F) | ((board.buffer[3] & 0x7F) << 7); + const durationBuffer = [ + (board.buffer[4] & 0x7F) | ((board.buffer[5] & 0x7F) << 7), + (board.buffer[6] & 0x7F) | ((board.buffer[7] & 0x7F) << 7), + (board.buffer[8] & 0x7F) | ((board.buffer[9] & 0x7F) << 7), + (board.buffer[10] & 0x7F) | ((board.buffer[11] & 0x7F) << 7), + ]; + const duration = ((durationBuffer[0] << 24) + + (durationBuffer[1] << 16) + + (durationBuffer[2] << 8) + + (durationBuffer[3])); + board.emit(`ping-read-${pin}`, duration); + }, + + /** + * Handles the message from a stepper completing move + * @param {Board} board + */ + + [STEPPER](board) { + const deviceNum = board.buffer[2]; + board.emit(`stepper-done-${deviceNum}`, true); + }, + + /** + * Handles the message from a stepper or group of steppers completing move + * @param {Board} board + */ + + [ACCELSTEPPER](board) { + const command = board.buffer[2]; + const deviceNum = board.buffer[3]; + const value = command === 0x06 || command === 0x0A ? + decode32BitSignedInteger(board.buffer.slice(4, 9)) : null; + + if (command === 0x06) { + board.emit(`stepper-position-${deviceNum}`, value); + } + if (command === 0x0A) { + board.emit(`stepper-done-${deviceNum}`, value); + } + if (command === 0x24) { + board.emit(`multi-stepper-done-${deviceNum}`); + } + }, + + /** + * Handles a SERIAL_REPLY response and emits the "serial-data-"+n event where n is the id of the + * serial port. + * The event is passed the buffer of data sent from the serial device + * @private + * @param {Board} board the current arduino board we are working with. + */ + + [SERIAL_MESSAGE](board) { + const command = board.buffer[2] & START_SYSEX; + const portId = board.buffer[2] & 0x0F; + const reply = []; + + /* istanbul ignore else */ + if (command === SERIAL_REPLY) { + for (let i = 3, len = board.buffer.length; i < len - 1; i += 2) { + reply.push((board.buffer[i + 1] << 7) | board.buffer[i]); + } + board.emit(`serial-data-${portId}`, reply); + } + }, + + +}; + +/** + * The default transport class + */ + +let Transport = null; + + +/** + * @class The Board object represents an arduino board. + * @augments EventEmitter + * @param {String} port This is the serial port the arduino is connected to. + * @param {function} function A function to be called when the arduino is ready to communicate. + * @property MODES All the modes available for pins on this arduino board. + * @property I2C_MODES All the I2C modes available. + * @property SERIAL_MODES All the Serial modes available. + * @property SERIAL_PORT_ID ID values to pass as the portId parameter when calling serialConfig. + * @property HIGH A constant to set a pins value to HIGH when the pin is set to an output. + * @property LOW A constant to set a pins value to LOW when the pin is set to an output. + * @property pins An array of pin object literals. + * @property analogPins An array of analog pins and their corresponding indexes in the pins array. + * @property version An object indicating the major and minor version of the firmware currently running. + * @property firmware An object indicating the name, major and minor version of the firmware currently running. + * @property buffer An array holding the current bytes received from the arduino. + * @property {SerialPort} sp The serial port object used to communicate with the arduino. + */ + +class Firmata extends Emitter { + constructor(port, options, callback) { + super(); + + if (typeof options === "function" || typeof options === "undefined") { + callback = options; + options = {}; + } + + const board = this; + const defaults = { + reportVersionTimeout: 5000, + samplingInterval: 19, + serialport: { + baudRate: 57600, + // https://github.com/node-serialport/node-serialport/blob/5.0.0/UPGRADE_GUIDE.md#open-options + highWaterMark: 256, + }, + }; + + const settings = Object.assign({}, defaults, options); + + this.isReady = false; + + this.MODES = { + INPUT: 0x00, + OUTPUT: 0x01, + ANALOG: 0x02, + PWM: 0x03, + SERVO: 0x04, + SHIFT: 0x05, + I2C: 0x06, + ONEWIRE: 0x07, + STEPPER: 0x08, + SERIAL: 0x0A, + PULLUP: 0x0B, + IGNORE: 0x7F, + PING_READ: 0x75, + UNKOWN: 0x10, + }; + + this.I2C_MODES = { + WRITE: 0, + READ: 1, + CONTINUOUS_READ: 2, + STOP_READING: 3, + }; + + this.STEPPER = { + TYPE: { + DRIVER: 1, + TWO_WIRE: 2, + THREE_WIRE: 3, + FOUR_WIRE: 4, + }, + STEP_SIZE: { + WHOLE: 0, + HALF: 1 + }, + RUN_STATE: { + STOP: 0, + ACCEL: 1, + DECEL: 2, + RUN: 3, + }, + DIRECTION: { + CCW: 0, + CW: 1, + }, + }; + + this.SERIAL_MODES = { + CONTINUOUS_READ: 0x00, + STOP_READING: 0x01, + }; + + // ids for hardware and software serial ports on the board + this.SERIAL_PORT_IDs = { + HW_SERIAL0: 0x00, + HW_SERIAL1: 0x01, + HW_SERIAL2: 0x02, + HW_SERIAL3: 0x03, + SW_SERIAL0: 0x08, + SW_SERIAL1: 0x09, + SW_SERIAL2: 0x10, + SW_SERIAL3: 0x11, + + // Default can be used by dependant libraries to key on a + // single property name when negotiating ports. + // + // Firmata elects SW_SERIAL0: 0x08 as its DEFAULT + DEFAULT: 0x08, + }; + + // map to the pin resolution value in the capability query response + this.SERIAL_PIN_TYPES = { + RES_RX0: 0x00, + RES_TX0: 0x01, + RES_RX1: 0x02, + RES_TX1: 0x03, + RES_RX2: 0x04, + RES_TX2: 0x05, + RES_RX3: 0x06, + RES_TX3: 0x07, + }; + + this.RESOLUTION = { + ADC: null, + DAC: null, + PWM: null, + }; + + this.HIGH = 1; + this.LOW = 0; + this.pins = []; + this.ports = Array(16).fill(0); + this.analogPins = []; + this.version = {}; + this.firmware = {}; + this.buffer = []; + this.versionReceived = false; + this.name = "Firmata"; + this.settings = settings; + this.pending = 0; + this.digitalPortQueue = 0x0000; + + if (typeof port === "object") { + this.transport = port; + } else { + if (!Transport) { + throw new Error("Missing Default Transport"); + } + this.transport = new Transport(port, settings.serialport); + } + + this.transport.on("close", event => { + + // https://github.com/node-serialport/node-serialport/blob/5.0.0/UPGRADE_GUIDE.md#opening-and-closing + if (event && event.disconnect && event.disconnected) { + this.emit("disconnect"); + return; + } + + this.emit("close"); + }); + + this.transport.on("open", event => { + this.emit("open", event); + // Legacy + this.emit("connect", event); + }); + + this.transport.on("error", error => { + if (!this.isReady && typeof callback === "function") { + callback(error); + } else { + this.emit("error", error); + } + }); + + this.transport.on("data", data => { + for (let i = 0; i < data.length; i++) { + let byte = data[i]; + // we dont want to push 0 as the first byte on our buffer + if (this.buffer.length === 0 && byte === 0) { + continue; + } else { + this.buffer.push(byte); + + let first = this.buffer[0]; + let last = this.buffer[this.buffer.length - 1]; + + // [START_SYSEX, ... END_SYSEX] + if (first === START_SYSEX && last === END_SYSEX) { + + let handler = SYSEX_RESPONSE[this.buffer[1]]; + + // Ensure a valid SYSEX_RESPONSE handler exists + // Only process these AFTER the REPORT_VERSION + // message has been received and processed. + if (handler && this.versionReceived) { + handler(this); + } + + // It is possible for the board to have + // existing activity from a previous run + // that will leave any of the following + // active: + // + // - ANALOG_MESSAGE + // - SERIAL_READ + // - I2C_REQUEST, CONTINUOUS_READ + // + // This means that we will receive these + // messages on transport "open", before any + // handshake can occur. We MUST assert + // that we will only process this buffer + // AFTER the REPORT_VERSION message has + // been received. Not doing so will result + // in the appearance of the program "hanging". + // + // Since we cannot do anything with this data + // until _after_ REPORT_VERSION, discard it. + // + this.buffer.length = 0; + + } else if (first === START_SYSEX && (this.buffer.length > 0)) { + // we have a new command after an incomplete sysex command + let currByte = data[i]; + if (currByte > 0x7F) { + this.buffer.length = 0; + this.buffer.push(currByte); + } + } else { + /* istanbul ignore else */ + if (first !== START_SYSEX) { + // Check if data gets out of sync: first byte in buffer + // must be a valid response if not START_SYSEX + // Identify response on first byte + let response = first < START_SYSEX ? (first & START_SYSEX) : first; + + // Check if the first byte is possibly + // a valid MIDI_RESPONSE (handler) + /* istanbul ignore else */ + if (response !== REPORT_VERSION && + response !== ANALOG_MESSAGE && + response !== DIGITAL_MESSAGE) { + // If not valid, then we received garbage and can discard + // whatever bytes have been been queued. + this.buffer.length = 0; + } + } + } + + // There are 3 bytes in the buffer and the first is not START_SYSEX: + // Might have a MIDI Command + if (this.buffer.length === 3 && first !== START_SYSEX) { + // response bytes under 0xF0 we have a multi byte operation + let response = first < START_SYSEX ? (first & START_SYSEX) : first; + + /* istanbul ignore else */ + if (MIDI_RESPONSE[response]) { + // It's ok that this.versionReceived will be set to + // true every time a valid MIDI_RESPONSE is received. + // This condition is necessary to ensure that REPORT_VERSION + // is called first. + if (this.versionReceived || first === REPORT_VERSION) { + this.versionReceived = true; + MIDI_RESPONSE[response](this); + } + this.buffer.length = 0; + } else { + // A bad serial read must have happened. + // Reseting the buffer will allow recovery. + this.buffer.length = 0; + } + } + } + } + }); + + // if we have not received the version within the allotted + // time specified by the reportVersionTimeout (user or default), + // then send an explicit request for it. + this.reportVersionTimeoutId = setTimeout(() => { + /* istanbul ignore else */ + if (this.versionReceived === false) { + this.reportVersion(function() {}); + this.queryFirmware(function() {}); + } + }, settings.reportVersionTimeout); + + function ready() { + board.isReady = true; + board.emit("ready"); + /* istanbul ignore else */ + if (typeof callback === "function") { + callback(); + } + } + + // Await the reported version. + this.once("reportversion", () => { + clearTimeout(this.reportVersionTimeoutId); + this.versionReceived = true; + this.once("queryfirmware", () => { + + // Only preemptively set the sampling interval if `samplingInterval` + // property was _explicitly_ set as a constructor option. + if (options.samplingInterval !== undefined) { + this.setSamplingInterval(options.samplingInterval); + } + if (settings.skipCapabilities) { + this.analogPins = settings.analogPins || this.analogPins; + this.pins = settings.pins || this.pins; + /* istanbul ignore else */ + if (!this.pins.length) { + for (var i = 0; i < (settings.pinCount || MAX_PIN_COUNT); i++) { + var supportedModes = []; + var analogChannel = this.analogPins.indexOf(i); + + if (analogChannel < 0) { + analogChannel = 127; + } + this.pins.push({supportedModes, analogChannel}); + } + } + + // If the capabilities query is skipped, + // default resolution values will be used. + // + // Based on ATmega328/P + // + this.RESOLUTION.ADC = 0x3FF; + this.RESOLUTION.PWM = 0x0FF; + + ready(); + } else { + this.queryCapabilities(() => { + this.queryAnalogMapping(ready); + }); + } + }); + }); + } + + /** + * Asks the arduino to tell us its version. + * @param {function} callback A function to be called when the arduino has reported its version. + */ + + reportVersion(callback) { + this.once("reportversion", callback); + writeToTransport(this, [REPORT_VERSION]); + } + + /** + * Asks the arduino to tell us its firmware version. + * @param {function} callback A function to be called when the arduino has reported its firmware version. + */ + + queryFirmware(callback) { + this.once("queryfirmware", callback); + writeToTransport(this, [ + START_SYSEX, + QUERY_FIRMWARE, + END_SYSEX + ]); + } + + + + /** + * Asks the arduino to read analog data. Turn on reporting for this pin. + * @param {number} pin The pin to read analog data + * @param {function} callback A function to call when we have the analag data. + */ + + analogRead(pin, callback) { + this.reportAnalogPin(pin, 1); + this.addListener(`analog-read-${pin}`, callback); + } + + /** + * Write a PWM value Asks the arduino to write an analog message. + * @param {number} pin The pin to write analog data to. + * @param {number} value The data to write to the pin between 0 and this.RESOLUTION.PWM. + */ + + pwmWrite(pin, value) { + let data; + + this.pins[pin].value = value; + + if (pin > 15) { + data = [ + START_SYSEX, + EXTENDED_ANALOG, + pin, + value & 0x7F, + (value >> 7) & 0x7F, + ]; + + if (value > 0x00004000) { + data[data.length] = (value >> 14) & 0x7F; + } + + if (value > 0x00200000) { + data[data.length] = (value >> 21) & 0x7F; + } + + if (value > 0x10000000) { + data[data.length] = (value >> 28) & 0x7F; + } + + data[data.length] = END_SYSEX; + } else { + data = [ + ANALOG_MESSAGE | pin, + value & 0x7F, + (value >> 7) & 0x7F + ]; + } + + writeToTransport(this, data); + } + + /** + * Set a pin to SERVO mode with an explicit PWM range. + * + * @param {number} pin The pin the servo is connected to + * @param {number} min A 14-bit signed int. + * @param {number} max A 14-bit signed int. + */ + + servoConfig(pin, min, max) { + if (typeof pin === "object" && pin !== null) { + let temp = pin; + pin = temp.pin; + min = temp.min; + max = temp.max; + } + + if (typeof pin === "undefined") { + throw new Error("servoConfig: pin must be specified"); + } + + if (typeof min === "undefined") { + throw new Error("servoConfig: min must be specified"); + } + + if (typeof max === "undefined") { + throw new Error("servoConfig: max must be specified"); + } + + // [0] START_SYSEX (0xF0) + // [1] SERVO_CONFIG (0x70) + // [2] pin number (0-127) + // [3] minPulse LSB (0-6) + // [4] minPulse MSB (7-13) + // [5] maxPulse LSB (0-6) + // [6] maxPulse MSB (7-13) + // [7] END_SYSEX (0xF7) + + this.pins[pin].mode = this.MODES.SERVO; + + writeToTransport(this, [ + START_SYSEX, + SERVO_CONFIG, + pin, + min & 0x7F, + (min >> 7) & 0x7F, + max & 0x7F, + (max >> 7) & 0x7F, + END_SYSEX, + ]); + } + + /** + * Asks the arduino to move a servo + * @param {number} pin The pin the servo is connected to + * @param {number} value The degrees to move the servo to. + */ + + servoWrite(...args) { + // Values less than 544 will be treated as angles in degrees + // (valid values in microseconds are handled as microseconds) + this.analogWrite(...args); + } + + /** + * Asks the arduino to set the pin to a certain mode. + * @param {number} pin The pin you want to change the mode of. + * @param {number} mode The mode you want to set. Must be one of board.MODES + */ + + pinMode(pin, mode) { + this.pins[pin].mode = mode; + writeToTransport(this, [ + PIN_MODE, + pin, + mode + ]); + } + + /** + * Asks the arduino to write a value to a digital pin + * @param {number} pin The pin you want to write a value to. + * @param {number} value The value you want to write. Must be board.HIGH or board.LOW + * @param {boolean} enqueue When true, the local state is updated but the command is not sent to the Arduino + */ + + digitalWrite(pin, value, enqueue) { + let port = this.updateDigitalPort(pin, value); + + if (enqueue) { + this.digitalPortQueue |= 1 << port; + } else { + this.writeDigitalPort(port); + } + } + + /** + * Update local store of digital port state + * @param {number} pin The pin you want to write a value to. + * @param {number} value The value you want to write. Must be board.HIGH or board.LOW + */ + + updateDigitalPort(pin, value) { + const port = pin >> 3; + const bit = 1 << (pin & 0x07); + + this.pins[pin].value = value; + + if (value) { + this.ports[port] |= bit; + } else { + this.ports[port] &= ~bit; + } + + return port; + } + + /** + * Write queued digital ports + */ + + flushDigitalPorts() { + for (let i = 0; i < this.ports.length; i++) { + if (this.digitalPortQueue >> i) { + this.writeDigitalPort(i); + } + } + this.digitalPortQueue = 0x0000; + } + + /** + * Update a digital port (group of 8 digital pins) on the Arduino + * @param {number} port The port you want to update. + */ + + writeDigitalPort(port) { + writeToTransport(this, [ + DIGITAL_MESSAGE | port, + this.ports[port] & 0x7F, + (this.ports[port] >> 7) & 0x7F + ]); + } + + /** + * Asks the arduino to read digital data. Turn on reporting for this pin's port. + * + * @param {number} pin The pin to read data from + * @param {function} callback The function to call when data has been received + */ + + digitalRead(pin, callback) { + this.reportDigitalPin(pin, 1); + this.addListener(`digital-read-${pin}`, callback); + } + + /** + * Asks the arduino to tell us its capabilities + * @param {function} callback A function to call when we receive the capabilities + */ + + queryCapabilities(callback) { + this.once("capability-query", callback); + writeToTransport(this, [ + START_SYSEX, + CAPABILITY_QUERY, + END_SYSEX + ]); + } + + /** + * Asks the arduino to tell us its analog pin mapping + * @param {function} callback A function to call when we receive the pin mappings. + */ + + queryAnalogMapping(callback) { + this.once("analog-mapping-query", callback); + writeToTransport(this, [ + START_SYSEX, + ANALOG_MAPPING_QUERY, + END_SYSEX + ]); + } + + /** + * Asks the arduino to tell us the current state of a pin + * @param {number} pin The pin we want to the know the state of + * @param {function} callback A function to call when we receive the pin state. + */ + + queryPinState(pin, callback) { + this.once(`pin-state-${pin}`, callback); + writeToTransport(this, [ + START_SYSEX, + PIN_STATE_QUERY, + pin, + END_SYSEX + ]); + } + + /** + * Sends a string to the arduino + * @param {String} string to send to the device + */ + + sendString(string) { + const bytes = Buffer.from(`${string}\0`, "utf8"); + const data = []; + + data.push(START_SYSEX, STRING_DATA); + for (let i = 0, length = bytes.length; i < length; i++) { + data.push( + bytes[i] & 0x7F, + (bytes[i] >> 7) & 0x7F + ); + } + data.push(END_SYSEX); + + writeToTransport(this, data); + } + + /** + * Sends a I2C config request to the arduino board with an optional + * value in microseconds to delay an I2C Read. Must be called before + * an I2C Read or Write + * @param {number} delay in microseconds to set for I2C Read + */ + + sendI2CConfig(delay) { + return this.i2cConfig(delay); + } + + /** + * Enable I2C with an optional read delay. Must be called before + * an I2C Read or Write + * + * Supersedes sendI2CConfig + * + * @param {number} delay in microseconds to set for I2C Read + * + * or + * + * @param {object} with a single property `delay` + */ + + i2cConfig(options) { + let settings = i2cActive.get(this); + let delay; + + if (!settings) { + settings = { + /* + Keys will be I2C peripheral addresses + */ + }; + i2cActive.set(this, settings); + } + + if (typeof options === "number") { + delay = options; + } else { + if (typeof options === "object" && options !== null) { + delay = Number(options.delay); + + // When an address was explicitly specified, there may also be + // peripheral specific instructions in the config. + if (typeof options.address !== "undefined") { + if (!settings[options.address]) { + settings[options.address] = { + stopTX: true, + }; + } + } + + // When settings have been explicitly provided, just bulk assign + // them to the existing settings, even if that's empty. This + // allows for reconfiguration as needed. + if (typeof options.settings !== "undefined") { + Object.assign(settings[options.address], options.settings); + /* + - stopTX: true | false + Set `stopTX` to `false` if this peripheral + expects Wire to keep the transmission connection alive between + setting a register and requesting bytes. + + Defaults to `true`. + */ + } + } + } + + settings.delay = delay = delay || 0; + + i2cRequest(this, [ + START_SYSEX, + I2C_CONFIG, + delay & 0xFF, + (delay >> 8) & 0xFF, + END_SYSEX, + ]); + + return this; + } + + /** + * Asks the arduino to send an I2C request to a device + * @param {number} slaveAddress The address of the I2C device + * @param {Array} bytes The bytes to send to the device + */ + + sendI2CWriteRequest(slaveAddress, bytes) { + const data = []; + /* istanbul ignore next */ + bytes = bytes || []; + + data.push( + START_SYSEX, + I2C_REQUEST, + slaveAddress, + this.I2C_MODES.WRITE << 3 + ); + + for (let i = 0, length = bytes.length; i < length; i++) { + data.push( + bytes[i] & 0x7F, + (bytes[i] >> 7) & 0x7F + ); + } + + data.push(END_SYSEX); + + i2cRequest(this, data); + } + + /** + * Write data to a register + * + * @param {number} address The address of the I2C device. + * @param {Array} cmdRegOrData An array of bytes + * + * Write a command to a register + * + * @param {number} address The address of the I2C device. + * @param {number} cmdRegOrData The register + * @param {Array} inBytes An array of bytes + * + */ + + i2cWrite(address, registerOrData, inBytes) { + /** + * registerOrData: + * [... arbitrary bytes] + * + * or + * + * registerOrData, inBytes: + * command [, ...] + * + */ + const data = [ + START_SYSEX, + I2C_REQUEST, + address, + this.I2C_MODES.WRITE << 3 + ]; + + // If i2cWrite was used for an i2cWriteReg call... + if (arguments.length === 3 && + !Array.isArray(registerOrData) && + !Array.isArray(inBytes)) { + + return this.i2cWriteReg(address, registerOrData, inBytes); + } + + // Fix arguments if called with Firmata.js API + if (arguments.length === 2) { + if (Array.isArray(registerOrData)) { + inBytes = registerOrData.slice(); + registerOrData = inBytes.shift(); + } else { + inBytes = []; + } + } + + const bytes = Buffer.from([registerOrData].concat(inBytes)); + + for (var i = 0, length = bytes.length; i < length; i++) { + data.push( + bytes[i] & 0x7F, + (bytes[i] >> 7) & 0x7F + ); + } + + data.push(END_SYSEX); + + i2cRequest(this, data); + + return this; + } + + /** + * Write data to a register + * + * @param {number} address The address of the I2C device. + * @param {number} register The register. + * @param {number} byte The byte value to write. + * + */ + + i2cWriteReg(address, register, byte) { + i2cRequest(this, [ + START_SYSEX, + I2C_REQUEST, + address, + this.I2C_MODES.WRITE << 3, + // register + register & 0x7F, + (register >> 7) & 0x7F, + // byte + byte & 0x7F, + (byte >> 7) & 0x7F, + END_SYSEX, + ]); + + return this; + } + + /** + * Asks the arduino to request bytes from an I2C device + * @param {number} slaveAddress The address of the I2C device + * @param {number} numBytes The number of bytes to receive. + * @param {function} callback A function to call when we have received the bytes. + */ + + sendI2CReadRequest(address, numBytes, callback) { + i2cRequest(this, [ + START_SYSEX, + I2C_REQUEST, + address, + this.I2C_MODES.READ << 3, + numBytes & 0x7F, + (numBytes >> 7) & 0x7F, + END_SYSEX, + ]); + this.once(`I2C-reply-${address}-0`, callback); + } + + // TODO: Refactor i2cRead and i2cReadOnce + // to share most operations. + + /** + * Initialize a continuous I2C read. + * + * @param {number} address The address of the I2C device + * @param {number} register Optionally set the register to read from. + * @param {number} numBytes The number of bytes to receive. + * @param {function} callback A function to call when we have received the bytes. + */ + + i2cRead(address, register, bytesToRead, callback) { + + if (arguments.length === 3 && + typeof register === "number" && + typeof bytesToRead === "function") { + callback = bytesToRead; + bytesToRead = register; + register = null; + } + + const data = [ + START_SYSEX, + I2C_REQUEST, + address, + this.I2C_MODES.CONTINUOUS_READ << 3, + ]; + let event = `I2C-reply-${address}-`; + + if (register !== null) { + data.push( + register & 0x7F, + (register >> 7) & 0x7F + ); + } else { + register = 0; + } + + event += register; + + data.push( + bytesToRead & 0x7F, + (bytesToRead >> 7) & 0x7F, + END_SYSEX + ); + + this.on(event, callback); + + i2cRequest(this, data); + + return this; + } + + /** + * Stop continuous reading of the specified I2C address or register. + * + * @param {object} options Options: + * bus {number} The I2C bus (on supported platforms) + * address {number} The I2C peripheral address to stop reading. + * + * @param {number} address The I2C peripheral address to stop reading. + */ + + i2cStop(options) { + // There may be more values in the future + // var options = {}; + + // null or undefined? Do nothing. + if (options == null) { + return; + } + + if (typeof options === "number") { + options = { + address: options + }; + } + + writeToTransport(this, [ + START_SYSEX, + I2C_REQUEST, + options.address, + this.I2C_MODES.STOP_READING << 3, + END_SYSEX, + ]); + + Object.keys(this._events).forEach(event => { + if (event.startsWith(`I2C-reply-${options.address}`)) { + this.removeAllListeners(event); + } + }); + } + + /** + * Perform a single I2C read + * + * Supersedes sendI2CReadRequest + * + * Read bytes from address + * + * @param {number} address The address of the I2C device + * @param {number} register Optionally set the register to read from. + * @param {number} numBytes The number of bytes to receive. + * @param {function} callback A function to call when we have received the bytes. + * + */ + + + i2cReadOnce(address, register, bytesToRead, callback) { + + if (arguments.length === 3 && + typeof register === "number" && + typeof bytesToRead === "function") { + callback = bytesToRead; + bytesToRead = register; + register = null; + } + + const data = [ + START_SYSEX, + I2C_REQUEST, + address, + this.I2C_MODES.READ << 3, + ]; + let event = `I2C-reply-${address}-`; + + if (register !== null) { + data.push( + register & 0x7F, + (register >> 7) & 0x7F + ); + } else { + register = 0; + } + + event += register; + + data.push( + bytesToRead & 0x7F, + (bytesToRead >> 7) & 0x7F, + END_SYSEX + ); + + this.once(event, callback); + + i2cRequest(this, data); + + return this; + } + + /** + * Configure the passed pin as the controller in a 1-wire bus. + * Pass as enableParasiticPower true if you want the data pin to power the bus. + * @param pin + * @param enableParasiticPower + */ + + sendOneWireConfig(pin, enableParasiticPower) { + writeToTransport(this, [ + START_SYSEX, + ONEWIRE_DATA, + ONEWIRE_CONFIG_REQUEST, + pin, + enableParasiticPower ? 0x01 : 0x00, + END_SYSEX, + ]); + } + + /** + * Searches for 1-wire devices on the bus. The passed callback should accept + * and error argument and an array of device identifiers. + * @param pin + * @param callback + */ + + sendOneWireSearch(pin, callback) { + this[SYM_sendOneWireSearch]( + ONEWIRE_SEARCH_REQUEST, + `1-wire-search-reply-${pin}`, + pin, + callback + ); + } + + /** + * Searches for 1-wire devices on the bus in an alarmed state. The passed callback + * should accept and error argument and an array of device identifiers. + * @param pin + * @param callback + */ + + sendOneWireAlarmsSearch(pin, callback) { + this[SYM_sendOneWireSearch]( + ONEWIRE_SEARCH_ALARMS_REQUEST, + `1-wire-search-alarms-reply-${pin}`, + pin, + callback + ); + } + + [SYM_sendOneWireSearch](type, event, pin, callback) { + writeToTransport(this, [ + START_SYSEX, + ONEWIRE_DATA, + type, + pin, + END_SYSEX + ]); + + const timeout = setTimeout(() => { + /* istanbul ignore next */ + callback(new Error("1-Wire device search timeout - are you running ConfigurableFirmata?")); + }, 5000); + this.once(event, devices => { + clearTimeout(timeout); + callback(null, devices); + }); + } + + /** + * Reads data from a device on the bus and invokes the passed callback. + * + * N.b. ConfigurableFirmata will issue the 1-wire select command internally. + * @param pin + * @param device + * @param numBytesToRead + * @param callback + */ + + sendOneWireRead(pin, device, numBytesToRead, callback) { + const correlationId = Math.floor(Math.random() * 255); + /* istanbul ignore next */ + const timeout = setTimeout(() => { + /* istanbul ignore next */ + callback(new Error("1-Wire device read timeout - are you running ConfigurableFirmata?")); + }, 5000); + this[SYM_sendOneWireRequest]( + pin, + ONEWIRE_READ_REQUEST_BIT, + device, + numBytesToRead, + correlationId, + null, + null, + `1-wire-read-reply-${correlationId}`, + data => { + clearTimeout(timeout); + callback(null, data); + } + ); + } + + /** + * Resets all devices on the bus. + * @param pin + */ + + sendOneWireReset(pin) { + this[SYM_sendOneWireRequest]( + pin, + ONEWIRE_RESET_REQUEST_BIT + ); + } + + /** + * Writes data to the bus to be received by the passed device. The device + * should be obtained from a previous call to sendOneWireSearch. + * + * N.b. ConfigurableFirmata will issue the 1-wire select command internally. + * @param pin + * @param device + * @param data + */ + + sendOneWireWrite(pin, device, data) { + this[SYM_sendOneWireRequest]( + pin, + ONEWIRE_WRITE_REQUEST_BIT, + device, + null, + null, + null, + Array.isArray(data) ? data : [data] + ); + } + + /** + * Tells firmata to not do anything for the passed amount of ms. For when you + * need to give a device attached to the bus time to do a calculation. + * @param pin + */ + + sendOneWireDelay(pin, delay) { + this[SYM_sendOneWireRequest]( + pin, + ONEWIRE_DELAY_REQUEST_BIT, + null, + null, + null, + delay + ); + } + + /** + * Sends the passed data to the passed device on the bus, reads the specified + * number of bytes and invokes the passed callback. + * + * N.b. ConfigurableFirmata will issue the 1-wire select command internally. + * @param pin + * @param device + * @param data + * @param numBytesToRead + * @param callback + */ + + sendOneWireWriteAndRead(pin, device, data, numBytesToRead, callback) { + const correlationId = Math.floor(Math.random() * 255); + /* istanbul ignore next */ + const timeout = setTimeout(() => { + /* istanbul ignore next */ + callback(new Error("1-Wire device read timeout - are you running ConfigurableFirmata?")); + }, 5000); + this[SYM_sendOneWireRequest]( + pin, + ONEWIRE_WRITE_REQUEST_BIT | ONEWIRE_READ_REQUEST_BIT, + device, + numBytesToRead, + correlationId, + null, + Array.isArray(data) ? data : [data], + `1-wire-read-reply-${correlationId}`, + data => { + clearTimeout(timeout); + callback(null, data); + } + ); + } + + // see http://firmata.org/wiki/Proposals#OneWire_Proposal + [SYM_sendOneWireRequest](pin, subcommand, device, numBytesToRead, correlationId, delay, dataToWrite, event, callback) { + const bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + if (device || numBytesToRead || correlationId || delay || dataToWrite) { + subcommand = subcommand | ONEWIRE_WITHDATA_REQUEST_BITS; + } + + if (device) { + bytes.splice(...[0, 8].concat(device)); + } + + if (numBytesToRead) { + bytes[8] = numBytesToRead & 0xFF; + bytes[9] = (numBytesToRead >> 8) & 0xFF; + } + + if (correlationId) { + bytes[10] = correlationId & 0xFF; + bytes[11] = (correlationId >> 8) & 0xFF; + } + + if (delay) { + bytes[12] = delay & 0xFF; + bytes[13] = (delay >> 8) & 0xFF; + bytes[14] = (delay >> 16) & 0xFF; + bytes[15] = (delay >> 24) & 0xFF; + } + + if (dataToWrite) { + bytes.push(...dataToWrite); + } + + const output = [ + START_SYSEX, + ONEWIRE_DATA, + subcommand, + pin, + ...Encoder7Bit.to7BitArray(bytes), + END_SYSEX, + ]; + + writeToTransport(this, output); + + if (event && callback) { + this.once(event, callback); + } + } + + /** + * Set sampling interval in millis. Default is 19 ms + * @param {number} interval The sampling interval in ms > 10 + */ + + setSamplingInterval(interval) { + const safeint = interval < 10 ? 10 : (interval > 65535 ? 65535 : interval); + this.settings.samplingInterval = safeint; + writeToTransport(this, [ + START_SYSEX, + SAMPLING_INTERVAL, + (safeint & 0x7F), + ((safeint >> 7) & 0x7F), + END_SYSEX, + ]); + } + + /** + * Get sampling interval in millis. Default is 19 ms + * + * @return {number} samplingInterval + */ + + getSamplingInterval() { + return this.settings.samplingInterval; + } + + /** + * Set reporting on pin + * @param {number} pin The pin to turn on/off reporting + * @param {number} value Binary value to turn reporting on/off + */ + + reportAnalogPin(pin, value) { + /* istanbul ignore else */ + if (value === 0 || value === 1) { + this.pins[this.analogPins[pin]].report = value; + writeToTransport(this, [ + REPORT_ANALOG | pin, + value + ]); + } + } + + /** + * Set reporting on pin + * @param {number} pin The pin to turn on/off reporting + * @param {number} value Binary value to turn reporting on/off + */ + + reportDigitalPin(pin, value) { + const port = pin >> 3; + /* istanbul ignore else */ + if (value === 0 || value === 1) { + this.pins[pin].report = value; + writeToTransport(this, [ + REPORT_DIGITAL | port, + value + ]); + } + } + + /** + * + * + */ + + pingRead(options, callback) { + + if (!this.pins[options.pin].supportedModes.includes(PING_READ)) { + throw new Error("Please upload PingFirmata to the board"); + } + + const { + pin, + value, + pulseOut = 0, + timeout = 1000000 + } = options; + + writeToTransport(this, [ + START_SYSEX, + PING_READ, + pin, + value, + ...Firmata.encode([ + (pulseOut >> 24) & 0xFF, + (pulseOut >> 16) & 0xFF, + (pulseOut >> 8) & 0xFF, + (pulseOut & 0xFF), + ]), + ...Firmata.encode([ + (timeout >> 24) & 0xFF, + (timeout >> 16) & 0xFF, + (timeout >> 8) & 0xFF, + (timeout & 0xFF), + ]), + END_SYSEX, + ]); + + this.once(`ping-read-${pin}`, callback); + } + + /** + * Stepper functions to support version 2 of ConfigurableFirmata's asynchronous control of stepper motors + * https://github.com/soundanalogous/ConfigurableFirmata + */ + + /** + * Asks the arduino to configure a stepper motor with the given config to allow asynchronous control of the stepper + * @param {object} opts Options: + * {number} deviceNum: Device number for the stepper (range 0-9) + * {number} type: One of this.STEPPER.TYPE.* + * {number} stepSize: One of this.STEPPER.STEP_SIZE.* + * {number} stepPin: Only used if STEPPER.TYPE.DRIVER + * {number} directionPin: Only used if STEPPER.TYPE.DRIVER + * {number} motorPin1: motor pin 1 + * {number} motorPin2: motor pin 2 + * {number} [motorPin3]: Only required if type == this.STEPPER.TYPE.THREE_WIRE || this.STEPPER.TYPE.FOUR_WIRE + * {number} [motorPin4]: Only required if type == this.STEPPER.TYPE.FOUR_WIRE + * {number} [enablePin]: Enable pin + * {array} [invertPins]: Array of pins to invert + */ + + accelStepperConfig(options) { + + let { + deviceNum, + invertPins, + motorPin1, + motorPin2, + motorPin3, + motorPin4, + enablePin, + stepSize = this.STEPPER.STEP_SIZE.WHOLE, + type = this.STEPPER.TYPE.FOUR_WIRE, + } = options; + + const data = [ + START_SYSEX, + ACCELSTEPPER, + 0x00, // STEPPER_CONFIG from firmware + deviceNum + ]; + + let iface = ((type & 0x07) << 4) | ((stepSize & 0x07) << 1); + let pinsToInvert = 0x00; + + if (typeof enablePin !== "undefined") { + iface = iface | 0x01; + } + + data.push(iface); + + [ + "stepPin", + "motorPin1", + "directionPin", + "motorPin2", + "motorPin3", + "motorPin4", + "enablePin" + ].forEach(pin => { + if (typeof options[pin] !== "undefined") { + data.push(options[pin]); + } + }); + + if (Array.isArray(invertPins)) { + if (invertPins.includes(motorPin1)) { + pinsToInvert |= 0x01; + } + if (invertPins.includes(motorPin2)) { + pinsToInvert |= 0x02; + } + if (invertPins.includes(motorPin3)) { + pinsToInvert |= 0x04; + } + if (invertPins.includes(motorPin4)) { + pinsToInvert |= 0x08; + } + if (invertPins.includes(enablePin)) { + pinsToInvert |= 0x10; + } + } + + data.push( + pinsToInvert, + END_SYSEX + ); + + writeToTransport(this, data); + } + + /** + * Asks the arduino to set the stepper position to 0 + * Note: This is not a move. We are setting the current position equal to zero + * @param {number} deviceNum Device number for the stepper (range 0-9) + */ + + accelStepperZero(deviceNum) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x01, // STEPPER_ZERO from firmware + deviceNum, + END_SYSEX, + ]); + } + + /** + * Asks the arduino to move a stepper a number of steps + * (and optionally with and acceleration and deceleration) + * speed is in units of steps/sec + * @param {number} deviceNum Device number for the stepper (range 0-5) + * @param {number} steps Number of steps to make + */ + accelStepperStep(deviceNum, steps, callback) { + + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x02, // STEPPER_STEP from firmware + deviceNum, + ...encode32BitSignedInteger(steps), + END_SYSEX, + ]); + + if (callback) { + this.once(`stepper-done-${deviceNum}`, callback); + } + } + + /** + * Asks the arduino to move a stepper to a specific location + * @param {number} deviceNum Device number for the stepper (range 0-5) + * @param {number} position Desired position + */ + accelStepperTo(deviceNum, position, callback) { + + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x03, // STEPPER_TO from firmware + deviceNum, + ...encode32BitSignedInteger(position), + END_SYSEX, + ]); + + if (callback) { + this.once(`stepper-done-${deviceNum}`, callback); + } + } + + /** + * Asks the arduino to enable/disable a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + * @param {boolean} [enabled] + */ + + accelStepperEnable(deviceNum, enabled = true) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x04, // ENABLE from firmware + deviceNum, + enabled & 0x01, + END_SYSEX, + ]); + } + + /** + * Asks the arduino to stop a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + */ + + accelStepperStop(deviceNum) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x05, // STEPPER_STOP from firmware + deviceNum, + END_SYSEX, + ]); + } + + /** + * Asks the arduino to report the position of a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + */ + + accelStepperReportPosition(deviceNum, callback) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x06, // STEPPER_REPORT_POSITION from firmware + deviceNum, + END_SYSEX, + ]); + + /* istanbul ignore else */ + if (callback) { + this.once(`stepper-position-${deviceNum}`, callback); + } + } + + /** + * Asks the arduino to set the acceleration for a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + * @param {number} acceleration Desired acceleration in steps per sec^2 + */ + + accelStepperAcceleration(deviceNum, acceleration) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x08, // STEPPER_SET_ACCELERATION from firmware + deviceNum, + ...encodeCustomFloat(acceleration), + END_SYSEX, + ]); + } + + /** + * Asks the arduino to set the max speed for a stepper + * @param {number} deviceNum Device number for the stepper (range 0-9) + * @param {number} speed Desired speed or maxSpeed in steps per second + * @param {function} [callback] + */ + + accelStepperSpeed(deviceNum, speed) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x09, // STEPPER_SET_SPEED from firmware + deviceNum, + ...encodeCustomFloat(speed), + END_SYSEX, + ]); + } + + /** + * Asks the arduino to configure a multiStepper group + * @param {object} options Options: + * {number} groupNum: Group number for the multiSteppers (range 0-5) + * {number} devices: array of accelStepper device numbers in group + **/ + + multiStepperConfig(options) { + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x20, // MULTISTEPPER_CONFIG from firmware + options.groupNum, + ...options.devices, + END_SYSEX, + ]); + } + + /** + * Asks the arduino to move a multiStepper group + * @param {number} groupNum Group number for the multiSteppers (range 0-5) + * @param {number} positions array of absolute stepper positions + **/ + + multiStepperTo(groupNum, positions, callback) { + if (groupNum < 0 || groupNum > 5) { + throw new RangeError(`Invalid "groupNum": ${groupNum}. Expected "groupNum" between 0-5`); + } + + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x21, // MULTISTEPPER_TO from firmware + groupNum, + ...positions.reduce((a, b) => a.concat(...encode32BitSignedInteger(b)), []), + END_SYSEX, + ]); + + /* istanbul ignore else */ + if (callback) { + this.once(`multi-stepper-done-${groupNum}`, callback); + } + } + + /** + * Asks the arduino to stop a multiStepper group + * @param {number} groupNum: Group number for the multiSteppers (range 0-5) + **/ + + multiStepperStop(groupNum) { + /* istanbul ignore else */ + if (groupNum < 0 || groupNum > 5) { + throw new RangeError(`Invalid "groupNum": ${groupNum}. Expected "groupNum" between 0-5`); + } + writeToTransport(this, [ + START_SYSEX, + ACCELSTEPPER, + 0x23, // MULTISTEPPER_STOP from firmware + groupNum, + END_SYSEX, + ]); + } + + /** + * Stepper functions to support AdvancedFirmata's asynchronous control of stepper motors + * https://github.com/soundanalogous/AdvancedFirmata + */ + + /** + * Asks the arduino to configure a stepper motor with the given config to allow asynchronous control of the stepper + * @param {number} deviceNum Device number for the stepper (range 0-5, expects steppers to be setup in order from 0 to 5) + * @param {number} type One of this.STEPPER.TYPE.* + * @param {number} stepsPerRev Number of steps motor takes to make one revolution + * @param {number} stepOrMotor1Pin If using EasyDriver type stepper driver, this is direction pin, otherwise it is motor 1 pin + * @param {number} dirOrMotor2Pin If using EasyDriver type stepper driver, this is step pin, otherwise it is motor 2 pin + * @param {number} [motorPin3] Only required if type == this.STEPPER.TYPE.FOUR_WIRE + * @param {number} [motorPin4] Only required if type == this.STEPPER.TYPE.FOUR_WIRE + */ + + stepperConfig(deviceNum, type, stepsPerRev, dirOrMotor1Pin, dirOrMotor2Pin, motorPin3, motorPin4) { + writeToTransport(this, [ + START_SYSEX, + STEPPER, + 0x00, // STEPPER_CONFIG from firmware + deviceNum, + type, + stepsPerRev & 0x7F, + (stepsPerRev >> 7) & 0x7F, + dirOrMotor1Pin, + dirOrMotor2Pin, + ...(type === this.STEPPER.TYPE.FOUR_WIRE ? [motorPin3, motorPin4] : []), + END_SYSEX + ]); + } + + /** + * Asks the arduino to move a stepper a number of steps at a specific speed + * (and optionally with and acceleration and deceleration) + * speed is in units of .01 rad/sec + * accel and decel are in units of .01 rad/sec^2 + * TODO: verify the units of speed, accel, and decel + * @param {number} deviceNum Device number for the stepper (range 0-5) + * @param {number} direction One of this.STEPPER.DIRECTION.* + * @param {number} steps Number of steps to make + * @param {number} speed + * @param {number|function} accel Acceleration or if accel and decel are not used, then it can be the callback + * @param {number} [decel] + * @param {function} [callback] + */ + + stepperStep(deviceNum, direction, steps, speed, accel, decel, callback) { + if (typeof accel === "function") { + callback = accel; + accel = 0; + decel = 0; + } + + writeToTransport(this, [ + START_SYSEX, + STEPPER, + 0x01, // STEPPER_STEP from firmware + deviceNum, + direction, // one of this.STEPPER.DIRECTION.* + steps & 0x7F, (steps >> 7) & 0x7F, (steps >> 14) & 0x7F, + speed & 0x7F, (speed >> 7) & 0x7F, + + ...(accel > 0 || decel > 0 ? + [accel & 0x7F, (accel >> 7) & 0x7F, decel & 0x7F, (decel >> 7) & 0x7F] : []), + + END_SYSEX, + ]); + + /* istanbul ignore else */ + if (callback) { + this.once(`stepper-done-${deviceNum}`, callback); + } + } + + /** + * Asks the Arduino to configure a hardware or serial port. + * @param {object} options Options: + * portId {number} The serial port to use (HW_SERIAL1, HW_SERIAL2, HW_SERIAL3, SW_SERIAL0, + * SW_SERIAL1, SW_SERIAL2, SW_SERIAL3) + * baud {number} The baud rate of the serial port + * rxPin {number} [SW Serial only] The RX pin of the SoftwareSerial instance + * txPin {number} [SW Serial only] The TX pin of the SoftwareSerial instance + */ + + serialConfig(options) { + + let portId; + let baud; + let rxPin; + let txPin; + + /* istanbul ignore else */ + if (typeof options === "object" && options !== null) { + portId = options.portId; + baud = options.baud; + rxPin = options.rxPin; + txPin = options.txPin; + } + + /* istanbul ignore else */ + if (typeof portId === "undefined") { + throw new Error("portId must be specified, see SERIAL_PORT_IDs for options."); + } + + baud = baud || 57600; + + const data = [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_CONFIG | portId, + baud & 0x7F, + (baud >> 7) & 0x7F, + (baud >> 14) & 0x7F + ]; + if (portId > 7 && typeof rxPin !== "undefined" && typeof txPin !== "undefined") { + data.push( + rxPin, + txPin + ); + } else if (portId > 7) { + throw new Error("Both RX and TX pins must be defined when using Software Serial."); + } + + data.push(END_SYSEX); + writeToTransport(this, data); + } + + /** + * Write an array of bytes to the specified serial port. + * @param {number} portId The serial port to write to. + * @param {Array} inBytes An array of bytes to write to the serial port. + */ + + serialWrite(portId, bytes) { + const data = [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_WRITE | portId, + ]; + for (let i = 0, len = bytes.length; i < len; i++) { + data.push( + bytes[i] & 0x7F, + (bytes[i] >> 7) & 0x7F + ); + } + data.push(END_SYSEX); + /* istanbul ignore else */ + if (bytes.length > 0) { + writeToTransport(this, data); + } + } + + /** + * Start continuous reading of the specified serial port. The port is checked for data each + * iteration of the main Arduino loop. + * @param {number} portId The serial port to start reading continuously. + * @param {number} maxBytesToRead [Optional] The maximum number of bytes to read per iteration. + * If there are less bytes in the buffer, the lesser number of bytes will be returned. A value of 0 + * indicates that all available bytes in the buffer should be read. + * @param {function} callback A function to call when we have received the bytes. + */ + + serialRead(portId, maxBytesToRead, callback) { + const data = [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_READ | portId, + this.SERIAL_MODES.CONTINUOUS_READ + ]; + + if (arguments.length === 2 && typeof maxBytesToRead === "function") { + callback = maxBytesToRead; + } else { + data.push( + maxBytesToRead & 0x7F, + (maxBytesToRead >> 7) & 0x7F + ); + } + + data.push(END_SYSEX); + writeToTransport(this, data); + + this.on(`serial-data-${portId}`, callback); + } + + /** + * Stop continuous reading of the specified serial port. This does not close the port, it stops + * reading it but keeps the port open. + * @param {number} portId The serial port to stop reading. + */ + + serialStop(portId) { + writeToTransport(this, [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_READ | portId, + this.SERIAL_MODES.STOP_READING, + END_SYSEX, + ]); + + this.removeAllListeners(`serial-data-${portId}`); + } + + /** + * Close the specified serial port. + * @param {number} portId The serial port to close. + */ + + serialClose(portId) { + writeToTransport(this, [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_CLOSE | portId, + END_SYSEX, + ]); + } + + /** + * Flush the specified serial port. For hardware serial, this waits for the transmission of + * outgoing serial data to complete. For software serial, this removed any buffered incoming serial + * data. + * @param {number} portId The serial port to flush. + */ + + serialFlush(portId) { + writeToTransport(this, [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_FLUSH | portId, + END_SYSEX, + ]); + } + + /** + * For SoftwareSerial only. Only a single SoftwareSerial instance can read data at a time. + * Call this method to set this port to be the reading port in the case there are multiple + * SoftwareSerial instances. + * @param {number} portId The serial port to listen on. + */ + + serialListen(portId) { + // listen only applies to software serial ports + if (portId < 8) { + return; + } + writeToTransport(this, [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_LISTEN | portId, + END_SYSEX, + ]); + } + + /** + * Allow user code to handle arbitrary sysex responses + * + * @param {number} commandByte The commandByte must be associated with some message + * that's expected from the slave device. The handler is + * called with an array of _raw_ data from the slave. Data + * decoding must be done within the handler itself. + * + * Use Firmata.decode(data) to extract useful values from + * the incoming response data. + * + * @param {function} handler Function which handles receipt of responses matching + * commandByte. + */ + + sysexResponse(commandByte, handler) { + //console.log('Inside sysexResponse@framework ', commandByte); + //console.log(Firmata.SYSEX_RESPONSE[commandByte]); + //changed by Josenalde Oliveira in 12.06 - this IF statement throws exception after one execution, + //since at the first execution it returns undefined, from the second henceforth, it returns [Function] and throws the exception + + // if (Firmata.SYSEX_RESPONSE[commandByte]) { + // throw new Error(`${commandByte} is not an available SYSEX_RESPONSE byte`); + // } + + Firmata.SYSEX_RESPONSE[commandByte] = board => { + + + handler.call(board, board.buffer.slice(2, -1)) + }; + + return this; + } + + /* + * Allow user to remove sysex response handlers. + * + * @param {number} commandByte The commandByte to disassociate with a handler + * previously set via `sysexResponse( commandByte, handler)`. + */ + + clearSysexResponse(commandByte) { + /* istanbul ignore else */ + if (Firmata.SYSEX_RESPONSE[commandByte]) { + delete Firmata.SYSEX_RESPONSE[commandByte]; + } + } + + /** + * Allow user code to send arbitrary sysex messages + * + * @param {Array} message The message array is expected to be all necessary bytes + * between START_SYSEX and END_SYSEX (non-inclusive). It will + * be assumed that the data in the message array is + * already encoded as 2 7-bit bytes LSB first. + * + * + */ + + sysexCommand(message) { + //console.log(message); + if (!message || !message.length) { + throw new Error("Sysex Command cannot be empty"); + } + + writeToTransport(this, [ + START_SYSEX, + ...message.slice(), + END_SYSEX + ]); + return this; + } + + /** + * Send SYSTEM_RESET to arduino + */ + + reset() { + writeToTransport(this, [SYSTEM_RESET]); + } + + /** + * Firmata.isAcceptablePort Determines if a `port` object (from SerialPort.list()) + * is a valid Arduino (or similar) device. + * @return {Boolean} true if port can be connected to by Firmata + */ + + static isAcceptablePort(port) { + let rport = /usb|acm|^com/i; + + if (rport.test(port.path)) { + return true; + } + + return false; + } + + /** + * Firmata.requestPort(callback) Request an acceptable port to connect to. + * callback(error, port) + */ + + static requestPort(callback) { + if (!Transport || (Transport && typeof Transport.list !== "function")) { + process.nextTick(() => { + callback(new Error("No Transport provided"), null); + }); + return + } + Transport.list().then((ports) => { + const port = ports.find(port => Firmata.isAcceptablePort(port) && port); + if (port) { + callback(null, port); + } else { + callback(new Error("No Acceptable Port Found"), null); + } + }).catch(error => { + callback(error, null); + }); + } + + // Expose encode/decode for custom sysex messages + static encode(data) { + const encoded = []; + const length = data.length; + + for (let i = 0; i < length; i++) { + encoded.push( + data[i] & 0x7F, + (data[i] >> 7) & 0x7F + ); + } + + return encoded; + } + + static decode(data) { + const decoded = []; + + if (data.length % 2 !== 0) { + throw new Error("Firmata.decode(data) called with odd number of data bytes"); + } + + while (data.length) { + const lsb = data.shift(); + const msb = data.shift(); + decoded.push(lsb | (msb << 7)); + } + + return decoded; + } +} + +// Prototype Compatibility Aliases +Firmata.prototype.analogWrite = Firmata.prototype.pwmWrite; + +// Static Compatibility Aliases +Firmata.Board = Firmata; +Firmata.SYSEX_RESPONSE = SYSEX_RESPONSE; +Firmata.MIDI_RESPONSE = MIDI_RESPONSE; + +// The following are used internally. + +/** + * writeToTransport Due to the non-blocking behaviour of transport write + * operations, dependent programs need a way to know + * when all writes are complete. Every write increments + * a `pending` value, when the write operation has + * completed, the `pending` value is decremented. + * + * @param {Board} board An active Board instance + * @param {Array} data An array of 8 and 7 bit values that will be + * wrapped in a Buffer and written to the transport. + */ +function writeToTransport(board, data) { + board.pending++; + board.transport.write(Buffer.from(data), () => board.pending--); +} + +function i2cRequest(board, bytes) { + const active = i2cActive.get(board); + + if (!active) { + throw new Error("I2C is not enabled for this board. To enable, call the i2cConfig() method."); + } + + // Do not tamper with I2C_CONFIG messages + if (bytes[1] === I2C_REQUEST) { + const address = bytes[2]; + + // If no peripheral settings exist, make them. + if (!active[address]) { + active[address] = { + stopTX: true, + }; + } + + // READ (8) or CONTINUOUS_READ (16) + // value & 0b00011000 + if (bytes[3] & I2C_READ_MASK) { + // Invert logic to accomodate default = true, + // which is actually stopTX = 0 + bytes[3] |= Number(!active[address].stopTX) << 6; + } + } + + writeToTransport(board, bytes); +} + + +function encode32BitSignedInteger(data) { + const negative = data < 0; + + data = Math.abs(data); + + const encoded = [ + data & 0x7F, + (data >> 7) & 0x7F, + (data >> 14) & 0x7F, + (data >> 21) & 0x7F, + (data >> 28) & 0x07 + ]; + + if (negative) { + encoded[encoded.length - 1] |= 0x08; + } + + return encoded; +} + +function decode32BitSignedInteger(bytes) { + let result = (bytes[0] & 0x7F) | + ((bytes[1] & 0x7F) << 7) | + ((bytes[2] & 0x7F) << 14) | + ((bytes[3] & 0x7F) << 21) | + ((bytes[4] & 0x07) << 28); + + if (bytes[4] >> 3) { + result *= -1; + } + + return result; +} + +const MAX_SIGNIFICAND = Math.pow(2, 23); + +function encodeCustomFloat(input) { + const sign = input < 0 ? 1 : 0; + + input = Math.abs(input); + + const base10 = Math.floor(Math.log10(input)); + // Shift decimal to start of significand + let exponent = 0 + base10; + input /= Math.pow(10, base10); + + // Shift decimal to the right as far as we can + while (!Number.isInteger(input) && input < MAX_SIGNIFICAND) { + exponent -= 1; + input *= 10; + } + + // Reduce precision if necessary + while (input > MAX_SIGNIFICAND) { + exponent += 1; + input /= 10; + } + + input = Math.trunc(input); + exponent += 11; + + const encoded = [ + input & 0x7F, + (input >> 7) & 0x7F, + (input >> 14) & 0x7F, + (input >> 21) & 0x03 | (exponent & 0x0F) << 2 | (sign & 0x01) << 6 + ]; + + return encoded; +} + +function decodeCustomFloat(input) { + const exponent = ((input[3] >> 2) & 0x0F) - 11; + const sign = (input[3] >> 6) & 0x01; + + let result = input[0] | + (input[1] << 7) | + (input[2] << 14) | + (input[3] & 0x03) << 21; + + if (sign) { + result *= -1; + } + return result * Math.pow(10, exponent); +} + + +/* istanbul ignore else */ +if (process.env.IS_TEST_MODE) { + let transport = null; + Firmata.test = { + i2cPeripheralSettings(board) { + return i2cActive.get(board); + }, + get i2cActive() { + return i2cActive; + }, + set transport(value) { + transport = Transport; + Transport = value; + }, + restoreTransport() { + Transport = transport; + }, + encode32BitSignedInteger, + decode32BitSignedInteger, + encodeCustomFloat, + decodeCustomFloat, + writeToTransport, + + symbols: { + SYM_sendOneWireRequest, + SYM_sendOneWireSearch, + } + }; +} + +const bindTransport = function(transport) { + Transport = transport; + return Firmata; +}; + +bindTransport.Firmata = Firmata; + +module.exports = bindTransport; diff --git a/server/esp32_firmata/firmata-io/lib/onewireutils.js b/server/esp32_firmata/firmata-io/lib/onewireutils.js new file mode 100644 index 0000000..29ea60e --- /dev/null +++ b/server/esp32_firmata/firmata-io/lib/onewireutils.js @@ -0,0 +1,47 @@ +"use strict"; +const Encoder7Bit = require("./encoder7bit"); +const OneWireUtils = { + crc8(data) { + let crc = 0; + + for (let inbyte of data) { + for (let n = 8; n; n--) { + const mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + + if (mix) { + crc ^= 0x8C; + } + + inbyte >>= 1; + } + } + + return crc; + }, + + readDevices(data) { + const deviceBytes = Encoder7Bit.from7BitArray(data); + const devices = []; + + for (let i = 0; i < deviceBytes.length; i += 8) { + const device = deviceBytes.slice(i, i + 8); + + if (device.length !== 8) { + continue; + } + + const check = OneWireUtils.crc8(device.slice(0, 7)); + + if (check !== device[7]) { + console.error("ROM invalid!"); + } + + devices.push(device); + } + + return devices; + } +}; + +module.exports = OneWireUtils; diff --git a/server/esp32_firmata/firmata-io/package.json b/server/esp32_firmata/firmata-io/package.json new file mode 100644 index 0000000..d1e3d5f --- /dev/null +++ b/server/esp32_firmata/firmata-io/package.json @@ -0,0 +1,14 @@ +{ + "name": "firmata-io", + "description": "Firmata protocol implementation", + "version": "2.2.0", + "author": "Julian Gautier", + "license": "MIT", + "homepage": "http://www.github.com/firmata/firmata.js", + "repository": { + "type": "git", + "url": "git://github.com/firmata/firmata.js.git" + }, + "main": "lib/firmata", + "gitHead": "723cdae1831406781c935ac44faec25f79bced51" +} diff --git a/server/esp32_firmata/firmata-io/readme.md b/server/esp32_firmata/firmata-io/readme.md new file mode 100644 index 0000000..a297631 --- /dev/null +++ b/server/esp32_firmata/firmata-io/readme.md @@ -0,0 +1,107 @@ +# Firmata-io + +This is Firmata.js without a default `Transport`. + +# Install + +Install Firmata-io: + +```sh +npm install firmata-io --save +``` + +Install a Transport: + + +```sh +npm install serialport --save +``` + +## Transports + +- Serialport +- Etherport + +# Basic Usage + +## With A _Transport Class_ + +Here's an example using the `Serialport` class: + +```js +// Require your Transport! +const Serialport = require("serialport"); +// Pass the Transport class to the transport binding +// function exported by firmata-io. The transport binding +// function will return the Firmata class object with +// the Transport class bound in its scope. +const Firmata = require("firmata-io")(Serialport); + +Firmata.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Firmata(port.path); + + board.on("close", () => { + // Unplug the board to see this event! + console.log("Closed!"); + }); +}); +``` + +## With A _Transport Instance_ + +Here's an example using a `Serialport` instance: + +```js +// Require your Transport! +const Serialport = require("serialport"); +// Get the Firmata class without a bound transport. +const Firmata = require("firmata-io").Firmata; + +Serialport.list().then(ports => { + // Figure which port to use... + const port = ports.find(port => port.manufacturer.startsWith("Arduino")); + + // Instantiate an instance of your Transport class + const transport = new Serialport(port.path); + + // Pass the new instance directly to the Firmata class + const board = new Firmata(transport); + + board.on("close", () => { + // Unplug the board to see this event! + console.log("Closed!"); + }); +}); +``` + + +## License + +(The MIT License) + +Copyright (c) 2011-2015 Julian Gautier \ +Copyright (c) 2015-2019 The Firmata.js Authors (see AUTHORS.md) + +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. diff --git a/server/esp32_firmata/firmata/.tesselignore b/server/esp32_firmata/firmata/.tesselignore new file mode 100644 index 0000000..92661df --- /dev/null +++ b/server/esp32_firmata/firmata/.tesselignore @@ -0,0 +1,7 @@ +changelog +gruntfile.js +repl.js +package-lock.json +readme.md +examples/ +test/ diff --git a/server/esp32_firmata/firmata/examples/adxl345.js b/server/esp32_firmata/firmata/examples/adxl345.js new file mode 100644 index 0000000..e727b0c --- /dev/null +++ b/server/esp32_firmata/firmata/examples/adxl345.js @@ -0,0 +1,49 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const register = { + POWER: 0x2D, + RANGE: 0x31, + READ: 0xB2, + }; + + const board = new Board(port.path); + + board.on("ready", function() { + console.log("Ready"); + + const adxl345 = 0x53; + const sensitivity = 0.00390625; + + // Enable I2C + this.i2cConfig(); + + // Toggle power to reset + this.i2cWrite(adxl345, register.POWER, 0); + this.i2cWrite(adxl345, register.POWER, 8); + + // Set range (this is 2G range) + this.i2cWrite(adxl345, register.RANGE, 8); + + // Set register to READ position and request 6 bytes + this.i2cRead(adxl345, register.READ, 6, data => { + const x = (data[1] << 8) | data[0]; + const y = (data[3] << 8) | data[2]; + const z = (data[5] << 8) | data[4]; + + // Wrap and clamp 16 bits; + const X = (x >> 15 ? ((x ^ 0xFFFF) + 1) * -1 : x) * sensitivity; + const Y = (y >> 15 ? ((y ^ 0xFFFF) + 1) * -1 : y) * sensitivity; + const Z = (z >> 15 ? ((z ^ 0xFFFF) + 1) * -1 : z) * sensitivity; + + console.log("X: ", X); + console.log("Y: ", Y); + console.log("Z: ", Z); + }); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/blink.js b/server/esp32_firmata/firmata/examples/blink.js new file mode 100644 index 0000000..6934fef --- /dev/null +++ b/server/esp32_firmata/firmata/examples/blink.js @@ -0,0 +1,21 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + board.on("ready", () => { + const pin = 13; + let state = 1; + + board.pinMode(pin, board.MODES.OUTPUT); + + setInterval(() => { + board.digitalWrite(pin, (state ^= 1)); + }, 500); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/close-events.js b/server/esp32_firmata/firmata/examples/close-events.js new file mode 100644 index 0000000..fc777f7 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/close-events.js @@ -0,0 +1,15 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + board.on("close", () => { + // Unplug the board to see this event! + console.log("Closed!"); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/hw-serial-read-gps.js b/server/esp32_firmata/firmata/examples/hw-serial-read-gps.js new file mode 100644 index 0000000..53c9c23 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/hw-serial-read-gps.js @@ -0,0 +1,40 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + board.on("ready", () => { + console.log("READY"); + + const HW_SERIAL1 = board.SERIAL_PORT_IDs.HW_SERIAL1; + + board.serialConfig({ + portId: HW_SERIAL1, + baud: 9600 + }); + + board.serialRead(HW_SERIAL1, data => { + console.log(new Buffer(data).toString("ascii")); + }); + + board.on("string", message => { + console.log(message); + }); + + // log serial pin numbers + for (const pin in board.pins) { + const modes = board.pins[pin].supportedModes; + for (const mode in modes) { + if (modes[mode] === board.MODES.SERIAL) { + console.log(`serial pin: ${pin}`); + } + } + } + + }); +}); diff --git a/server/esp32_firmata/firmata/examples/johnny-five-io-plugin.js b/server/esp32_firmata/firmata/examples/johnny-five-io-plugin.js new file mode 100644 index 0000000..1e43d15 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/johnny-five-io-plugin.js @@ -0,0 +1,28 @@ +const SerialPort = require("serialport"); +const five = require("johnny-five"); +const Firmata = require("../"); + +SerialPort.list().then(ports => { + const device = ports.reduce((accum, item) => { + if (item.manufacturer.indexOf("Arduino") === 0) { + return item; + } + return accum; + }, null); + + + /* + The following demonstrates using Firmata + as an IO Plugin for Johnny-Five + */ + + const board = new five.Board({ + io: new Firmata(device.path) + }); + + board.on("ready", () => { + const led = new five.Led(13); + led.blink(500); + }); +}); + diff --git a/server/esp32_firmata/firmata/examples/k22.js b/server/esp32_firmata/firmata/examples/k22.js new file mode 100644 index 0000000..93a36d1 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/k22.js @@ -0,0 +1,33 @@ +/** + * Sample script to take readings from a k22 co2 sensor. + * http://www.co2meter.com/collections/co2-sensors/products/k-22-oc-co2-sensor-module + */ + +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + const board = new Board(port.path); + + board.on("ready", () => { + const k22 = 0x68; + + board.i2cConfig(); + board.i2cWrite(k22, [0x22, 0x00, 0x08, 0x2A]); + board.i2cRead(k22, 4, data => { + let ppms = 0; + ppms |= data[1] & 0xFF; + ppms = ppms << 8; + ppms |= data[2] & 0xFF; + const checksum = data[0] + data[1] + data[2]; + if (checksum === data[3]) { + console.log(`Current PPMs: ${ppms}`); + } else { + console.log("Checksum failure"); + } + }); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/mma8452.js b/server/esp32_firmata/firmata/examples/mma8452.js new file mode 100644 index 0000000..6301a28 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/mma8452.js @@ -0,0 +1,104 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const register = { + CTRL_REG1: 0x2A, + XYZ_DATA_CFG: 0x0E, + READ_X_MSB: 0x01, + }; + + const board = new Board(port.path); + // var board = new Board("/dev/cu.usbmodem1411"); + + board.on("ready", function() { + console.log("Ready"); + + const mma8452 = 0x1D; + const scale = 2; // 2G + const options = { + address: mma8452, + settings: { + stopTX: false, + }, + }; + + this.i2cConfig(options); + + function mode(which, callback) { + board.i2cReadOnce(mma8452, register.CTRL_REG1, 1, data => { + let value = data[0]; + if (which === "standby") { + // Clear the first bit + value &= ~1; + } else { + // Set the first bit + value |= 1; + } + + board.i2cWrite(mma8452, register.CTRL_REG1, value); + + callback(); + }); + } + + new Promise(resolve => { + mode("standby", () => { + + // 00: 2G (0b00000000) + // 01: 4G (0b00000001) + // 10: 8G (0b00000010) + const fsr = scale >> 2; // 2G (0b00000000) + board.i2cWrite(mma8452, register.XYZ_DATA_CFG, fsr); + + // 0: 800 Hz + // 1: 400 Hz + // 2: 200 Hz * + // 3: 100 Hz + // 4: 50 Hz + // 5: 12.5 Hz + // 6: 6.25 Hz + // 7: 1.56 Hz + const ctrlreg1 = 0b00000101 << 3; // 5 0b00[101]000 + board.i2cWrite(mma8452, register.CTRL_REG1, ctrlreg1); + + mode("active", resolve); + }); + }).then(() => { + + board.i2cRead(mma8452, 0x00, 1, data => { + const available = data[0]; + + if ((available & 0x08) >> 3) { + board.i2cReadOnce(mma8452, register.READ_X_MSB, 6, data => { + let x = (data[0] << 8 | data[1]) >> 4; + let y = (data[2] << 8 | data[3]) >> 4; + let z = (data[4] << 8 | data[5]) >> 4; + + if (data[0] > 0x7F) { + x = -(1 + 0xFFF - x); + } + + if (data[2] > 0x7F) { + y = -(1 + 0xFFF - y); + } + + if (data[4] > 0x7F) { + z = -(1 + 0xFFF - z); + } + + console.log({ + x: x / ((1 << 12) / (2 * scale)), + y: y / ((1 << 12) / (2 * scale)), + z: z / ((1 << 12) / (2 * scale)), + }); + }); + } + }); + }); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/reporting.js b/server/esp32_firmata/firmata/examples/reporting.js new file mode 100644 index 0000000..c9641f7 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/reporting.js @@ -0,0 +1,48 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + const board = new Board(port.path); + + board.on("ready", function() { + const a = 6; + const b = 7; + + console.log("Ready."); + + this.pinMode(a, this.MODES.PWM); + this.pinMode(b, this.MODES.OUTPUT); + + const states = { + 5: 0, + 8: 0 + }; + + Object.keys(states).forEach(function(pin) { + pin = +pin; + this.pinMode(pin, this.MODES.INPUT); + this.digitalRead(pin, function(value) { + console.log("pin: %d value: %d", pin, value); + if (states[pin] !== value) { + states[pin] = value; + this.digitalWrite(b, value); + } + }); + }, this); + + // var analogs = [0, 1, 2, 3, 4, 5]; + const analogs = [3]; + + analogs.forEach(function(pin) { + pin = +pin; + this.pinMode(pin, this.MODES.ANALOG); + this.analogRead(pin, function(value) { + this.analogWrite(a, value >> 2); + }); + }, this); + + }); +}); diff --git a/server/esp32_firmata/firmata/examples/servo-config.js b/server/esp32_firmata/firmata/examples/servo-config.js new file mode 100644 index 0000000..024174c --- /dev/null +++ b/server/esp32_firmata/firmata/examples/servo-config.js @@ -0,0 +1,20 @@ +const Board = require("../"); +const board = new Board("/dev/tty.usbmodem1421"); + +board.on("ready", () => { + let degrees = 10; + let incrementer = 10; + + // This will map 0-180 to 1000-1500 + board.servoConfig(9, 1000, 1500); + board.servoWrite(9, 0); + + setInterval(() => { + if (degrees >= 180 || degrees === 0) { + incrementer *= -1; + } + degrees += incrementer; + board.servoWrite(9, degrees); + console.log(degrees); + }, 500); +}); diff --git a/server/esp32_firmata/firmata/examples/servosweep.js b/server/esp32_firmata/firmata/examples/servosweep.js new file mode 100644 index 0000000..cb04a1f --- /dev/null +++ b/server/esp32_firmata/firmata/examples/servosweep.js @@ -0,0 +1,23 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + const board = new Board(port.path); + + board.on("ready", () => { + let degrees = 10; + let incrementer = 10; + board.pinMode(9, board.MODES.SERVO); + board.servoWrite(9, 0); + setInterval(() => { + if (degrees >= 180 || degrees === 0) { + incrementer *= -1; + } + degrees += incrementer; + board.servoWrite(9, degrees); + }, 500); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/stepper-accel.js b/server/esp32_firmata/firmata/examples/stepper-accel.js new file mode 100644 index 0000000..4620e17 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/stepper-accel.js @@ -0,0 +1,29 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + board.on("ready", () => { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.FOUR_WIRE, + motorPin1: 4, + motorPin2: 5, + motorPin3: 6, + motorPin4: 7, + stepSize: board.STEPPER.STEP_SIZE.WHOLE + }); + + board.accelStepperSpeed(0, 300); + board.accelStepperAcceleration(0, 100); + board.accelStepperStep(0, 2000, position => { + console.log(`Current position: ${position}`); + }); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/stepper-driver.js b/server/esp32_firmata/firmata/examples/stepper-driver.js new file mode 100644 index 0000000..4431652 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/stepper-driver.js @@ -0,0 +1,30 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + board.on("ready", () => { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.DRIVER, + stepPin: 5, + directionPin: 6, + enablePin: 2, + invertPins: [2] + }); + + board.accelStepperSpeed(0, 400); + board.accelStepperAcceleration(0, 100); + board.accelStepperEnable(0, true); + board.accelStepperStep(0, 200, position => { + console.log(`Current position: ${position}`); + }); + + }); +}); diff --git a/server/esp32_firmata/firmata/examples/stepper-multi.js b/server/esp32_firmata/firmata/examples/stepper-multi.js new file mode 100644 index 0000000..32f4394 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/stepper-multi.js @@ -0,0 +1,54 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + board.on("ready", () => { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.FOUR_WIRE, + motorPin1: 5, + motorPin2: 6, + motorPin3: 7, + motorPin4: 8, + stepSize: board.STEPPER.STEP_SIZE.WHOLE + }); + + board.accelStepperConfig({ + deviceNum: 1, + type: board.STEPPER.TYPE.FOUR_WIRE, + motorPin1: 9, + motorPin2: 10, + motorPin3: 11, + motorPin4: 12, + stepSize: board.STEPPER.STEP_SIZE.HALF + }); + + board.accelStepperSpeed(0, 400); + board.accelStepperSpeed(1, 400); + + board.multiStepperConfig({ + groupNum: 0, + devices: [0, 1] + }); + + board.multiStepperTo(0, [2000, 3000], () => { + + board.accelStepperReportPosition(0, value => { + console.log(`Stepper 0 position: ${value}`); + }); + + board.accelStepperReportPosition(1, value => { + console.log(`Stepper 1 position: ${value}`); + }); + + }); + + }); +}); diff --git a/server/esp32_firmata/firmata/examples/stepper-three-wire.js b/server/esp32_firmata/firmata/examples/stepper-three-wire.js new file mode 100644 index 0000000..687fc9d --- /dev/null +++ b/server/esp32_firmata/firmata/examples/stepper-three-wire.js @@ -0,0 +1,27 @@ +const Board = require("../"); + +Board.requestPort(function(error, port) { + if (error) { + console.log(error); + return; + } + + var board = new Board(port.path); + + board.on("ready", function() { + + board.accelStepperConfig({ + deviceNum: 0, + type: board.STEPPER.TYPE.THREE_WIRE, + motorPin1: 2, + motorPin2: 3, + motorPin3: 4 + }); + + board.accelStepperSpeed(0, 100); + board.accelStepperStep(0, 1000, function(position) { + console.log("Current position: " + position); + }); + + }); +}); diff --git a/server/esp32_firmata/firmata/examples/sw-serial-read-gps.js b/server/esp32_firmata/firmata/examples/sw-serial-read-gps.js new file mode 100644 index 0000000..442e253 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/sw-serial-read-gps.js @@ -0,0 +1,27 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + const board = new Board(port.path); + + board.on("ready", () => { + console.log("READY"); + + const SW_SERIAL0 = board.SERIAL_PORT_IDs.SW_SERIAL0; + const maxBytesToRead = 4; + + board.serialConfig({ + portId: SW_SERIAL0, + baud: 9600, + rxPin: 10, + txPin: 11 + }); + + board.serialRead(SW_SERIAL0, maxBytesToRead, data => { + console.log(new Buffer(data).toString("ascii")); + }); + }); +}); diff --git a/server/esp32_firmata/firmata/examples/test-analog-read.js b/server/esp32_firmata/firmata/examples/test-analog-read.js new file mode 100644 index 0000000..7b7a30d --- /dev/null +++ b/server/esp32_firmata/firmata/examples/test-analog-read.js @@ -0,0 +1,48 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + console.log(__filename); + console.log("------------------------------"); + + board.on("open", () => { + console.log(" ✔ open"); + }); + + board.on("reportversion", () => { + console.log(" ✔ reportversion"); + }); + + board.on("queryfirmware", () => { + console.log(" ✔ queryfirmware"); + }); + + board.on("capability-query", () => { + console.log(" ✔ capability-query"); + }); + + board.on("ready", function() { + console.log(" ✔ ready"); + clearTimeout(timeout); + + this.pinMode(0, 2); + this.analogRead(0, () => { + console.log(" ✔ received data (exiting)"); + console.log("------------------------------"); + process.exit(); + }); + }); + + var timeout = setTimeout(() => { + console.log(board.currentBuffer); + console.log(">>>>>>>>>>>>>>TIMEOUT<<<<<<<<<<<<<<"); + console.log("------------------------------"); + process.exit(); + }, 10000); +}); diff --git a/server/esp32_firmata/firmata/examples/test-i2c-read.js b/server/esp32_firmata/firmata/examples/test-i2c-read.js new file mode 100644 index 0000000..fae9ca1 --- /dev/null +++ b/server/esp32_firmata/firmata/examples/test-i2c-read.js @@ -0,0 +1,48 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + console.log(__filename); + console.log("------------------------------"); + + board.on("open", () => { + console.log(" ✔ open"); + }); + + board.on("reportversion", () => { + console.log(" ✔ reportversion"); + }); + + board.on("queryfirmware", () => { + console.log(" ✔ queryfirmware"); + }); + + board.on("capability-query", () => { + console.log(" ✔ capability-query"); + }); + + board.on("ready", () => { + console.log(" ✔ ready"); + clearTimeout(timeout); + + board.i2cConfig(); + board.i2cRead(0x0A, 1, () => { + console.log(" ✔ received data (exiting)"); + console.log("------------------------------"); + process.exit(); + }); + }); + + var timeout = setTimeout(() => { + console.log(board.currentBuffer); + console.log(">>>>>>>>>>>>>>TIMEOUT<<<<<<<<<<<<<<"); + console.log("------------------------------"); + process.exit(); + }, 10000); +}); diff --git a/server/esp32_firmata/firmata/examples/test-serial-read.js b/server/esp32_firmata/firmata/examples/test-serial-read.js new file mode 100644 index 0000000..191f79a --- /dev/null +++ b/server/esp32_firmata/firmata/examples/test-serial-read.js @@ -0,0 +1,56 @@ +const Board = require("../"); + +Board.requestPort((error, port) => { + if (error) { + console.log(error); + return; + } + + const board = new Board(port.path); + + console.log(__filename); + console.log("------------------------------"); + + board.on("open", () => { + console.log(" ✔ open"); + }); + + board.on("reportversion", () => { + console.log(" ✔ reportversion"); + }); + + board.on("queryfirmware", () => { + console.log(" ✔ queryfirmware"); + }); + + board.on("capability-query", () => { + console.log(" ✔ capability-query"); + }); + + board.on("ready", () => { + console.log(" ✔ ready"); + clearTimeout(timeout); + + const SW_SERIAL0 = board.SERIAL_PORT_IDs.SW_SERIAL0; + + board.serialConfig({ + portId: SW_SERIAL0, + baud: 9600, + rxPin: 2, + txPin: 3 + }); + + board.serialRead(SW_SERIAL0, () => { + console.log(" ✔ received data (exiting)"); + console.log("------------------------------"); + process.exit(); + }); + }); + + var timeout = setTimeout(() => { + console.log(board.currentBuffer); + console.log(">>>>>>>>>>>>>>TIMEOUT<<<<<<<<<<<<<<"); + console.log("------------------------------"); + process.exit(); + }, 10000); +}); diff --git a/server/esp32_firmata/firmata/lib/com.js b/server/esp32_firmata/firmata/lib/com.js new file mode 100644 index 0000000..ace962d --- /dev/null +++ b/server/esp32_firmata/firmata/lib/com.js @@ -0,0 +1,65 @@ +"use strict"; + +const Emitter = require("events"); + +class TransportStub extends Emitter { + constructor(path/*, options, openCallback*/) { + super(); + this.isOpen = true; + this.baudRate = 0; + this.path = path; + } + + write(buffer) { + // Tests are written to work with arrays not buffers + // this shouldn't impact the data, just the container + // This also should be changed in future test rewrites + /* istanbul ignore else */ + if (Buffer.isBuffer(buffer)) { + buffer = Array.from(buffer); + } + + this.lastWrite = buffer; + this.emit("write", buffer); + } + + static list() { + /* istanbul ignore next */ + return Promise.resolve([]); + } +} + +// This trash is necessary for stubbing with sinon. +TransportStub.SerialPort = TransportStub; + +let com; +let error; +let SerialPort; + +try { + /* istanbul ignore else */ + if (process.env.IS_TEST_MODE) { + com = TransportStub; + } else { + SerialPort = require("serialport"); + com = SerialPort; + } +} catch (err) { + /* istanbul ignore next */ + error = err; +} + +/* istanbul ignore if */ +if (com == null) { + if (process.env.IS_TEST_MODE) { + com = TransportStub; + } else { + console.log("It looks like serialport didn't install properly."); + console.log("More information can be found here https://serialport.io/docs/guide-installation"); + console.log(`The result of requiring the package is: ${SerialPort}`); + console.log(error); + throw "Missing serialport dependency"; + } +} + +module.exports = com; diff --git a/server/esp32_firmata/firmata/lib/firmata.js b/server/esp32_firmata/firmata/lib/firmata.js new file mode 100644 index 0000000..716a669 --- /dev/null +++ b/server/esp32_firmata/firmata/lib/firmata.js @@ -0,0 +1,4 @@ +"use strict"; + +module.exports = require("../../firmata-io/lib/firmata")(require("./com")); + diff --git a/server/esp32_firmata/firmata/package.json b/server/esp32_firmata/firmata/package.json new file mode 100644 index 0000000..8d62873 --- /dev/null +++ b/server/esp32_firmata/firmata/package.json @@ -0,0 +1,31 @@ +{ + "name": "firmata", + "description": "Firmata protocol implementation for programmatic interaction with Arduino and Arduino compatible development boards. Includes Serialport", + "version": "2.2.0", + "author": "Julian Gautier", + "license": "MIT", + "homepage": "http://www.github.com/firmata/firmata.js", + "repository": { + "type": "git", + "url": "git://github.com/firmata/firmata.js.git" + }, + "main": "./lib/firmata", + "dependencies": { + "firmata-io": "^2.2.0", + "serialport": "^8.0.5" + }, + "scripts": { + "test": "grunt", + "test-cover": "nyc grunt test", + "coveralls": "nyc --reporter=lcov grunt test && cat ./coverage/lcov.info | coveralls" + }, + "bugs": { + "url": "https://github.com/firmata/firmata.js/issues" + }, + "directories": { + "example": "examples", + "lib": "lib" + }, + "keywords": [], + "gitHead": "723cdae1831406781c935ac44faec25f79bced51" +} diff --git a/server/esp32_firmata/firmata/readme.md b/server/esp32_firmata/firmata/readme.md new file mode 100644 index 0000000..86161c7 --- /dev/null +++ b/server/esp32_firmata/firmata/readme.md @@ -0,0 +1,636 @@ +# Firmata.js + + +[![Travis Build Status](https://travis-ci.org/firmata/firmata.js.svg?branch=master)](https://travis-ci.org/firmata/firmata.js) +[![Appveyor Build status](https://ci.appveyor.com/api/projects/status/w026oorwsq44223j?svg=true)](https://ci.appveyor.com/project/rwaldron/firmata) +[![Coverage Status](https://coveralls.io/repos/github/firmata/firmata.js/badge.svg?branch=master)](https://coveralls.io/github/firmata/firmata.js?branch=master) + + +[Firmata protocol](https://github.com/firmata/protocol) implementation for programmatic interaction with Arduino and Arduino compatible development boards. + +# Install + +As a project dependency: + +```sh +npm install firmata +``` + + +For global cli use: + +```sh +npm install -g firmata +``` + + +# REPL + +If you run *firmata* from the command line it will prompt you for the serial port. Then it will present you with a REPL with a board variable available. + +# Basic Usage + +### Using the `"ready"` event... + +#### With a path string: + +```js +const Firmata = require("firmata"); +const board = new Firmata("system path or name"); + +board.on("ready", () => { + // Arduino is ready to communicate +}); +``` + +#### With a Serialport object: + +```js +const Serialport = require("serialport"); +const Firmata = require("firmata"); +const board = new Firmata(new Serialport(...)); + +board.on("ready", () => { + // Arduino is ready to communicate +}); +``` + +#### With an Etherport object: + +[Etherport](https://github.com/rwaldron/etherport) is a TCP server that can be used with [StandardFirmataEthernet](https://github.com/firmata/arduino/tree/master/examples/StandardFirmataEthernet) or [StandardFirmataWiFi](https://github.com/firmata/arduino/tree/master/examples/StandardFirmataWiFi) when configured as a TCP client (StandardFirmataEthernet can currently only be configured as a TCP client). There is also [etherport-client](https://github.com/mwittig/etherport-client) which is a TCP client that can be used with StandardFirmataWiFi when configured as a TCP server. + + +```js +const Etherport = require("etherport"); +const Firmata = require("firmata"); +const board = new Firmata(new Etherport(...)); + +board.on("ready", () => { + // Arduino is ready to communicate +}); +``` + +### Using the `readyCallback`: + +```js +const Firmata = require("firmata"); +const board = new Firmata("system path or name", () => { + // Arduino is ready to communicate +}); +``` + +#### With a Serialport object: + +```js +const Serialport = require("serialport"); +const Firmata = require("firmata"); +const board = new Firmata(new Serialport(...), () => { + // Arduino is ready to communicate +}); +``` + +#### With an Etherport object: + +```js +const Etherport = require("etherport"); +const Firmata = require("firmata"); +const board = new Firmata(new Etherport(...), () => { + // Arduino is ready to communicate +}); +``` + + +**Any object can be a `Transport` object, as long as it emits an "open" event and a "data" event, which match the semantics of a `Serialport` object.** + + +# `Firmata` + +The `Firmata` constructor creates an instance that represents a physical board. + +- `new Firmata(path[, options][, readyCallback])` +- `new Firmata(port[, options][, readyCallback])` + + | Parameter | Type | Description | Default | Required | + |-----------|------- |------------ |--------- |----------| + | path | String | A system path or port name. | none | Yes\* | + | port | Transport | A Transport object. | none | Yes\* | + | [options] | object | Optional settings to used when constructing. | [See Below](#board-options) | No | + | [readyCallback] | function | Optional "ready" callback to call when connection to board is complete. | none | No | + + \* _**Either**_ a **path** or a **port** are required. + + - Notes: + - `new Firmata(path: string)`: instances can be constructed using only a system path of the serial port to open or name, for example: + + `new Firmata("/dev/usb.whatever")` + + `new Firmata("/dev/ttyACM0")` + + `new Firmata("COM1")` + - `new Firmata(port: Transport)`: instances can be constructed using a "Transport" object, for example: + + `new Firmata(new Serialport(...))` + + `new Firmata(new Etherport(...))` + +- Options + + | Property | Type | Description | Default | Required | + |-----------|------- |------------ |--------- |----------| + | skipCapabilities | Boolean | Set to `true` to skip the `CAPABILITY_QUERY` | `true` | No | + | reportVersionTimeout | Number | Time in milliseconds to wait before timing out the initial request for the firmware version. | 5000 | No | + | samplingInterval | Number | Time in milliseconds of the sampling interval on the actual board. | 19 | No | + | serialport | Object | See: [Serialport:openOptions](https://github.com/EmergingTechnologyAdvisors/node-serialport#module_serialport--SerialPort..openOptions). These will be ignored if the first argument is a Transport object. | \* | No | + + \* Defaults are defined in `Serialport`. + + + + + + ## Firmata Instance + +- `board.MODES` + This is an enumeration of the different modes available. These are used in calls to the *pinMode* function. + ```js + { + INPUT: 0x00, + OUTPUT: 0x01, + ANALOG: 0x02, + PWM: 0x03, + SERVO: 0x04, + SHIFT: 0x05, + I2C: 0x06, + ONEWIRE: 0x07, + STEPPER: 0x08, + SERIAL: 0x0A, + PULLUP: 0x0B, + IGNORE: 0x7F, + UNKOWN: 0x10 + } + ``` + + +- `board.HIGH* and *board.LOW` + + Constants used to set a digital pin's voltage will be set to the corresponding value: 5V (or 3.3V, or 1.8V, depending on board) for `HIGH`, 0V (Ground) for `LOW`. + +- `board.pins` + + This is an array of all the pins on the board. + + Each value in the array is an object: + + ```js + { + mode: Number, // Current mode of pin which is on the the board.MODES. + value: Number, // Current value of the pin. when pin is digital and set to output it will be + // Firmata.HIGH or Firmata.LOW. If the pin is an analog pin it will be an numeric + // value between 0 and 1023. + supportedModes: [ ...Number ], // Array of modes from board.MODES that are supported on this pin. + analogChannel: Number, // Will be 127 for digital pins and the pin number for analog pins. + state: Number // For output pins this is the value of the pin on the board, for digital input + // it's the status of the pullup resistor (1 = pullup enabled, 0 = pullup disabled) + } + ``` + + This array holds all pins digital and analog. To get the analog pin number as seen on the arduino board use the analogChannel attribute. + +- `board.analogPins` + + This is an array of all the array indexes of the analog pins in the `board.pins` array. For example to get the analog pin 5 from the `board.pins` attributes use: + + ```js + board.pins[board.analogPins[5]];` + ``` + +## Firmata Prototype API + +### Pin + +- `board.pinMode(pin,mode)` + + Set a mode for a pin. pin is the number of the pin and the mode is on of the Firmata.MODES values. All digital pins are set to board.MODES.OUTPUT by default (because this is what the Firmata firmware running on the board defaults to) and all analog pins are set to board.MODES.ANALOG (analog input) by default. + +- `board.digitalWrite(pin,value,enqueue)` + + Write an output to a digital pin. pin is the number of the pin and the value is either board.HIGH or board.LOW. enqueue is optional and when true will update the local pin value but will not write the data until `flushDigitalPorts()` is called. + +- `board.flushDigitalPorts()` + + Directs firmata to update all ports whose values have been changed via digitalWrite with the `enqueue` parameter set to true. + +- `board.digitalRead(pin,callback)` + + Register to get the digital value (board.HIGH or board.LOW). The value is reported via the callback whenever it changes. To get the locally stored value at any other time you can use `board.pins[pinNumber].value`. + + Example: + + ```js + board.digitalRead(2, function(value) { + console.log("The value of digital pin 2 changed to: " + value); + }); + ``` + + To stop reporting digital values for a pin, call `board.reportDigitalPin(digitalPinNumber, 0)`. To restart, call `digitalRead(pin,callback)` or use `board.reportDigitalPin(digitalPinNumber, 1)` if you don't want to call digitalRead again. + + *Note if you are familiar with the use of digitalRead when writing an Arduino sketch, the firmata.js implementation of digitalRead is very different in that it's reporting-based rather than immediately returning a value as in an Arduino sketch.* + +- `board.analogWrite(pin,value)` + + Write an output to an analog pin (PWM). pin is the number of the pin and the value is between 0 and 255. + +- `board.analogRead(pin,callback)` + + Register to get the analog value (0 - 1023) of the pin. The value is reported via the callback at the current sampling interval. The sampling interval is 19 milliseconds by default so the analog value is reported every 19 ms unless the sampling interval is changed. See documentation for `board.setSamplingInterval` below. To get the locally stored value at any other time you can use `board.pins[board.analogPins[analogPinNumber]].value`, but the value will only be as fresh as the most recent report via the sampling interval. + + Example: + + ```js + board.analogRead(0, function(value) { + console.log("The value of pin A0 is " + value + " as reported at the sampling interval"); + }); + ``` + + To stop reporting analog values for a pin, call `board.reportAnalogPin(analogPinNumber, 0)`. To restart, call `analogRead(pin,callback)` or use `board.reportAnalogPin(analogPinNumber, 1)` if you don't want to call analogRead again. + + *Note if you are familiar with the use of analogRead when writing an Arduino sketch, the firmata.js implementation of analogRead is very different in that it's reporting-based rather than immediately returning a value as in an Arduino sketch.* + +- `board.setSamplingInterval(interval)` + + Set the sampling interval in milliseconds. Default is 19 ms. Minimum is 10 ms, max is 65535 ms. The sampling interval controls how often analog values are reported when using `board.analogRead` and how often i2c device values are reported when using `board.i2cRead`. The same sampling interval is used for both analog and i2c value reporting. + + You can alternatively set the sampling interval when creating a new Firmata instance: + + ```js + // set sampling interval to 30 milliseconds + const board = new Firmata(serialPortName, {samplingInterval: 30}); + ``` + + +- `board.getSamplingInterval()` + + Get the current sampling interval value in milliseconds. + +### Servo + +- `board.servoWrite(pin, degree)` +- `board.servoWrite(pin, pulse)` + + Write a degree value to a servo pin. + +- `board.servoConfig(pin, min, max)` + + Setup a servo with a specific min and max pulse (call instead of `pinMode`, which will provide default). + +### I2C + +- `board.i2cConfig(delay)` + + Configure and enable I2C, optionally provide a value in μs to delay between reads (defaults to `0`). Required to enable I2C communication. + +- `board.i2cConfig(options)` + + Configure and enable I2C, optionally provide an object that contains properties to use for whose value is a number in μs to delay between reads. Required to enable I2C communication. + + | Option | Description | Default | Required? | + |---------|-------------|---------|-----------| + | delay | µS delay between setting a register and requesting bytes from the register | 0 | No | + | address | Valid I2C address, used when there are specific configurations for a given address | none | No | + | settings | An object of properties to associate with a given address. | none | No | + + + | Setting | Description | Default | Required? | + |---------|-------------|---------|-----------| + | stopTX | Stop transmission after setting a register to read from. Setting to `false` will keep the transmission connection active. An example of the `false` behavior is the [MMA8452](https://github.com/sparkfun/MMA8452_Accelerometer/blob/master/Libraries/Arduino/src/SparkFun_MMA8452Q.cpp#L242-L270) | true | No | + + +- `board.i2cWrite(address, [...bytes])` + + Write an arbitrary number of bytes. May not exceed 64 Bytes. + + - `board.i2cWrite(address, byte)` + + Write a single byte to the current register. + +- `board.i2cWrite(address, register, [...bytes])` + + Write an arbitrary number of bytes to the specified register. May not exceed 64 Bytes. + +- `board.i2cWrite(address, register, bytes)` + + Write a single byte to the specified register. + +- `board.i2cWriteReg(address, register, byte)` + + Write a byte value to a specific register. + +- `board.i2cRead(address, numberOfBytesToRead, handler(data))` + + Read a specified number of bytes, continuously. `handler` receives an array of values, with a length corresponding to the number of read bytes. + +- `board.i2cRead(address, register, numberOfBytesToRead, handler(data))` + + Read a specified number of bytes from a register, continuously. `handler` receives an array of values, with a length corresponding to the number of read bytes. + +- `board.i2cReadOnce(address, numberOfBytesToRead, handler(data))` + + Read a specified number of bytes, one time. `handler` receives an array of values, with a length corresponding to the number of read bytes. + +- `board.i2cReadOnce(address, register, numberOfBytesToRead, handler(data))` + + Read a specified number of bytes from a register, one time. `handler` receives an array of values, with a length corresponding to the number of read bytes. + +- `board.sendI2CConfig(delay)` **Deprecated** + + Set I2C Config on the arduino + +- `board.sendI2CWriteRequest(slaveAddress, [bytes])` **Deprecated** + + Write an array of bytes to a an I2C device. + +- `board.sendI2CReadRequest(slaveAddress, numBytes, function(data))` **Deprecated** + + Requests a number of bytes from a slave I2C device. When the bytes are received from the I2C device the callback is called with the byte array. + +### Debug + +- `board.sendString("a string")` + + Send an arbitrary string. + +### One-Wire + +- `board.sendOneWireConfig(pin, enableParasiticPower)` + + Configure the pin as the controller in a 1-wire bus. Set `enableParasiticPower` to `true` if you want the data pin to power the bus. + +- `board.sendOneWireSearch(pin, callback)` + + Searches for 1-wire devices on the bus. The callback should accept an error argument and an array of device identifiers. + +- `board.sendOneWireAlarmsSearch(pin, callback)` + + Searches for 1-wire devices on the bus in an alarmed state. The callback should accept and error argument and an array of device identifiers. + +- `board.sendOneWireRead(pin, device, numBytesToRead, callback)` + + Reads data from a device on the bus and invokes the callback. + +- `board.sendOneWireReset()` + + Resets all devices on the bus. + +- `board.sendOneWireWrite(pin, device, data)` + + Writes data to the bus to be received by the device. The device should be obtained from a previous call to `sendOneWireSearch`. + +- `board.sendOneWireDelay(pin, delay)` + + Tells Firmata to not do anything for the amount of ms. Use when you need to give a device attached to the bus time to do a calculation. + +- `board.sendOneWireWriteAndRead(pin, device, data, numBytesToRead, callback)` + + Sends the `data` to the `device` on the bus, reads the specified number of bytes and invokes the `callback`. + + +### Serial + +- `board.SERIAL_PORT_IDs` + + IDs for both hardware and software serial ports on the board. + + ```js + { + HW_SERIAL0: 0x00, + HW_SERIAL1: 0x01, + HW_SERIAL2: 0x02, + HW_SERIAL3: 0x03, + SW_SERIAL0: 0x08, + SW_SERIAL1: 0x09, + SW_SERIAL2: 0x10, + SW_SERIAL3: 0x11, + } + ``` + +- `board.serialConfig(options)` + + Configure a hardware or serial port -- required before using serial read/write functions + + ``` + { + portId: board.SERIAL_PORT_IDs.HW_SERIAL1, // The serial port to use (HW_SERIAL2, SW_SERIAL0, SW_SERIAL1...) + baud: 115200, // (optional) The baud rate of the serial port; default is 57600 + rxPin: 5, // (optional)[SW Serial only] The RX pin of the SoftwareSerial instance + txPin: 6 // (optional)[SW Serial only] The TX pin of the SoftwareSerial instance + } + ``` + + +- `board.serialWrite(portId, inBytes)` + + Write an array of bytes to the specified serial port. + + +- `board.serialRead(portId, callback)` +- `board.serialRead(portId, maxBytesToRead, callback)` + + Start continuous reading of the specified serial port. The port is checked for data each iteration of the main Arduino loop. + +> `maxBytesToRead` specifies the maximum number of bytes to read per iteration. If there are less bytes in the buffer, the lesser number of bytes will be returned. A value of 0 indicates that all available bytes in the buffer should be read. + +- `board.serialStop(portId)` + + Stop continuous reading of the specified serial port. This does not close the port, it stops reading it but keeps the port open. + +- `board.serialClose(portId)` + + Close the specified serial port. + +- `board.serialFlush(portId)` + + Flush the specified serial port. For hardware serial, this waits for the transmission of outgoing serial data to complete. For software serial, this removes any buffered incoming serial data. + +- `board.serialListen(portId)` + + **For SoftwareSerial only**. Only a single SoftwareSerial instance can read data at a time. Call this method to set this port to be the reading port in the case there are multiple SoftwareSerial instances. + +### AccelStepperFirmata + +AccelStepperFirmata in configurableFirmata wraps [Mike McCauley’s AccelStepper library](http://www.airspayce.com/mikem/arduino/AccelStepper/). Accelstepper gives basic acceleration for individual steppers and support for multiSteppers. multiSteppers allow you to coordinate the movements of a group of steppers so that they arrive at their desired positions simultaneously. + +Requests for stepper movements are made asyncrhonously and movements can be interrupted with a call to stop or by setting a new target position with accelStepperTo or accelStepperMove. + +accelStepper support 2, 3, and 4 wire configurations as well as step + direction controllers like the easyDriver. + +- `board.STEPPER.TYPE` + + Available Stepper or controller types. + + ```js + { + DRIVER: 1, + TWO_WIRE: 2, + THREE_WIRE: 3, + FOUR_WIRE: 4, + } + ``` + +- `board.STEPPER.STEP_SIZE` + + Available step sizes. + + ```js + { + WHOLE: 0, + HALF: 1 + } + ``` + +- `board.STEPPER.DIRECTION` + + Stepper directions. + + ```js + { + CCW: 0, + CW: 1 + } + ``` + + - `board.accelStepperConfig(options)` + + Configure a stepper motor + + ``` + { + deviceNum: 0, // Device number for the stepper (range 0-9) + type: board.STEPPER.TYPE.DRIVER, // (optional) Type of stepper or controller; default is FOUR_WIRE + stepSize: board.STEPPER.STEP_SIZE.HALF, // (optional) Size of step; default is WHOLE + stepPin: 2, // (required if type === DRIVER) The step pin for a step+direction stepper driver + directionPin: 3, // (required if type === DRIVER) The direction pin for a step+direction stepper driver + motorPin1: 2, // (required if type !== DRIVER) Motor control pin 1 + motorPin2: 3, // (required if type !== DRIVER) Motor control pin 2 + motorPin3: 4, // (required if type === THREE_WIRE or FOUR_WIRE) Motor control pin 3 + motorPin4: 5, // (required if type === FOUR_WIRE) Motor control pin 4 + enablePin: 6, // (optional) Enable pin for motor controller pin + invertPins: 0 // (optional) Controls which pins to invert (see table below); default is 0 + } + ``` + + **invertPins** + + The invertPins value is a 5-bit number + + bit 5 |bit 4 |bit 3 |bit 2 |bit 1 + ----------------|----------------|----------------|----------------|---------------- + invert motorPin1|invert motorPin2|invert motorPin3|invert motorPin4|invert enablePin + + Examples: + + 1. Invert motor pins 1, 2, 3 & 4 = 0b11110 = 30 + + 1. Invert motor pins 1, 2 & enablePin = 0b11001 = 25 + + +- `board.accelStepperZero(deviceNum)` + + Set the current stepper position to zero + +- `board.accelStepperStep(deviceNum, steps, callback)` + + Move the stepper motor by a number of steps. Optional callback will be called when motor has finished moving or stop is called + +- `board.accelStepperTo(deviceNum, position, callback)` + + Move the stepper motor to a specified position. Optional callback will be called when motor has finished moving or stop is called + +- `board.accelStepperEnable(deviceNum, enabled)` + + If enabled param is set to false, stepper will be disabled, otherwise stepper will be enabled + +- `board.accelStepperStop(deviceNum)` + + Stop the stepper motor. Triggers a stepper-done event + +- `board.accelStepperReportPosition(deviceNum)` + + Request the current position of the stepper. Triggers a `"stepper-position"` event. + +- `board.accelStepperSpeed(deviceNum, speed)` + + Set the speed of the stepper in steps per second + +- `board.accelStepperAcceleration(deviceNum, acceleration)` + + Set the acceleration and deceleration for the stepper in steps / sec^2 + +- `board.multiStepperConfig(opts)` + + Configure a multStepper group. multiStepper groups allow you to pass an array of targeted positions and have all the steppers move to their targets and arrive at the same time. Note that acceleration cannot be used when moving a multiStepper group. + + ``` + opts = { + groupNum: 0, // Group number for the stepper group (range 0-4) + devices: board.STEPPER.TYPE.DRIVER // [] Array of deviceNum's used in group + } + ``` + +- `board.multiStepperTo(groupNum, positions, callback)` + + Move a goup of steppers to and array of desired positions. Optional callback will be called when group has finished moving or multiStepperStop is called + +- `board.multiStepperStop(groupNum)` + + Stop a group of stepper motors. Triggers a multi-stepper-done event + +### Sysex + +- `board.sysexResponse(commandByte, handler)` + + Allow user code to handle arbitrary sysex responses. `commandByte` must be associated with some message that's expected from the slave device. The `handler` is called with an array of _raw_ data from the slave. Data decoding must be done within the handler itself. + + - Use `Firmata.decode(data)` to extract useful values from the incoming response data. + +- `board.sysexCommand(message)` + + Allow user code to send arbitrary sysex messages. The `message` array is expected to be all necessary bytes between `START_SYSEX` and `END_SYSEX` (non-inclusive). It will be assumed that the data in the message array is already encoded as 2 7-bit bytes LSB first. + + - Use `Firmata.encode(data)` to encode data values into an array of 7-bit byte pairs. + +- `board.clearSysexResponse(commandByte)` + + Allow user to remove sysex response handler such as one previously set through board.sysexResponse(commandByte, handler). + + +### Encode/Decode + +- `Firmata.encode(data)` + + Encode an array of 8-bit data values as an array of two 7-bit byte pairs (each). (LSB first). + +- `Firmata.decode(data)` + + Decode an array of 7-bit byte pairs into a an array of 8-bit data values. (LSB first) + + +## License + +(The MIT License) + +Copyright (c) 2011-2015 Julian Gautier \ +Copyright (c) 2015-2019 The Firmata.js Authors (see AUTHORS.md) + +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. diff --git a/server/esp32_firmata/firmata/repl.js b/server/esp32_firmata/firmata/repl.js new file mode 100644 index 0000000..5fba995 --- /dev/null +++ b/server/esp32_firmata/firmata/repl.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +const firmata = require("./lib/firmata"); +const repl = require("repl"); +console.log("Enter USB Port and press enter:"); +process.stdin.resume(); +process.stdin.setEncoding("utf8"); +process.stdin.once("data", (chunk) => { + const port = chunk.replace("\n", ""); + const board = new firmata.Board(port, () => { + console.log(`Successfully Connected to ${port}`); + repl.start("firmata>").context.board = board; + }); +}); diff --git a/server/esp32_firmata/firmata/test/common/bootstrap.js b/server/esp32_firmata/firmata/test/common/bootstrap.js new file mode 100644 index 0000000..168acda --- /dev/null +++ b/server/esp32_firmata/firmata/test/common/bootstrap.js @@ -0,0 +1,29 @@ +process.env.IS_TEST_MODE = true; + +// Built-in Dependencies +global.cp = require("child_process"); +global.Emitter = require("events"); +global.fs = require("fs"); +global.path = require("path"); + +// Third-Party Dependencies +global.assert = require("should"); +global.browserify = require("browserify"); +global.sinon = require("sinon"); +global.webpack = require("webpack"); + +// Internal Dependencies +global.Encoder7Bit = require("../../../../packages/firmata-io/lib/encoder7bit"); +global.OneWire = require("../../../../packages/firmata-io/lib/onewireutils"); +global.com = require("../../lib/com"); + +global.firmata = require("../../lib/firmata"); + +// Fixtures +global.fixtures = { + unexpected: { + adc: require("../../test/unit/fixtures/unexpected-data-adc"), + i2c: require("../../test/unit/fixtures/unexpected-data-i2c"), + serial: require("../../test/unit/fixtures/unexpected-data-serial"), + } +}; diff --git a/server/esp32_firmata/firmata/test/unit/com.test.js b/server/esp32_firmata/firmata/test/unit/com.test.js new file mode 100644 index 0000000..9162fad --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/com.test.js @@ -0,0 +1,26 @@ +// This test file is primarily for rounding out test coverage +// and gaurding against changes to the "com" stubs. +require("../common/bootstrap"); + +const sandbox = sinon.createSandbox(); + +describe("com.*", () => { + + const response = { + error: null, + port: { + path: null + }, + }; + + it("com.SerialPort", done => { + assert.equal(typeof com.SerialPort, "function"); + done(); + }); + + it("com.list", done => { + assert.equal(typeof com.list, "function"); + done(); + }); +}); + diff --git a/server/esp32_firmata/firmata/test/unit/encoder7bit.test.js b/server/esp32_firmata/firmata/test/unit/encoder7bit.test.js new file mode 100644 index 0000000..28ccade --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/encoder7bit.test.js @@ -0,0 +1,12 @@ +require("../common/bootstrap"); + +describe("Encoder7Bit", () => { + it("must encode and decode via in-memory array", done => { + const input = [40, 219, 239, 33, 5, 0, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0]; + const encoded = Encoder7Bit.to7BitArray(input); + const decoded = Encoder7Bit.from7BitArray(encoded); + + assert.deepEqual(decoded, input); + done(); + }); +}); diff --git a/server/esp32_firmata/firmata/test/unit/firmata.test.js b/server/esp32_firmata/firmata/test/unit/firmata.test.js new file mode 100644 index 0000000..3538265 --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/firmata.test.js @@ -0,0 +1,3837 @@ +"use strict"; + +require("../common/bootstrap"); + +// Test specific internals +// +const Board = firmata.Board; + +const ANALOG_MAPPING_QUERY = 0x69; +const ANALOG_MAPPING_RESPONSE = 0x6A; +const ANALOG_MESSAGE = 0xE0; +const CAPABILITY_QUERY = 0x6B; +const CAPABILITY_RESPONSE = 0x6C; +const DIGITAL_MESSAGE = 0x90; +const END_SYSEX = 0xF7; +const EXTENDED_ANALOG = 0x6F; +const I2C_CONFIG = 0x78; +const I2C_REPLY = 0x77; +const I2C_REQUEST = 0x76; +const I2C_READ_MASK = 0x18; // 0b00011000 +const I2C_END_TX_MASK = 0x40; // 0b01000000 +const ONEWIRE_CONFIG_REQUEST = 0x41; +const ONEWIRE_DATA = 0x73; +const ONEWIRE_DELAY_REQUEST_BIT = 0x10; +const ONEWIRE_READ_REPLY = 0x43; +const ONEWIRE_READ_REQUEST_BIT = 0x08; +const ONEWIRE_RESET_REQUEST_BIT = 0x01; +const ONEWIRE_SEARCH_ALARMS_REPLY = 0x45; +const ONEWIRE_SEARCH_ALARMS_REQUEST = 0x44; +const ONEWIRE_SEARCH_REPLY = 0x42; +const ONEWIRE_SEARCH_REQUEST = 0x40; +const ONEWIRE_WITHDATA_REQUEST_BITS = 0x3C; +const ONEWIRE_WRITE_REQUEST_BIT = 0x20; +const PIN_MODE = 0xF4; +const PIN_STATE_QUERY = 0x6D; +const PIN_STATE_RESPONSE = 0x6E; +const PING_READ = 0x75; +const PULSE_IN = 0x74; +const PULSE_OUT = 0x73; +const QUERY_FIRMWARE = 0x79; +const REPORT_ANALOG = 0xC0; +const REPORT_DIGITAL = 0xD0; +const REPORT_VERSION = 0xF9; +const SAMPLING_INTERVAL = 0x7A; +const SERVO_CONFIG = 0x70; +const SERIAL_MESSAGE = 0x60; +const SERIAL_CONFIG = 0x10; +const SERIAL_WRITE = 0x20; +const SERIAL_READ = 0x30; +const SERIAL_REPLY = 0x40; +const SERIAL_CLOSE = 0x50; +const SERIAL_FLUSH = 0x60; +const SERIAL_LISTEN = 0x70; +const START_SYSEX = 0xF0; +const STEPPER = 0x72; +const ACCELSTEPPER = 0x62; +const STRING_DATA = 0x71; +const SYSTEM_RESET = 0xFF; + +// Used by custom sysex tests +const NON_STANDARD_REPLY = 0x11; + +const sandbox = sinon.createSandbox(); + +describe("Board.requestPort", () => { + + const response = { + error: null, + port: { + path: null + }, + }; + + beforeEach(() => { + sandbox.stub(com, "list").callsFake(() => { + return response.error ? Promise.reject(error) : Promise.resolve([response.port]) + }); + }); + + afterEach(() => { + sandbox.restore(); + response.error = null; + response.port.path = null; + }); + + it("can identify an acceptable port", done => { + response.port.path = "/dev/usb.whatever"; + assert.equal(Board.isAcceptablePort(response.port), true); + + response.port.path = "/dev/ttyACM0"; + assert.equal(Board.isAcceptablePort(response.port), true); + + response.port.path = "COM0"; + assert.equal(Board.isAcceptablePort(response.port), true); + + done(); + }); + + it("can identify an unacceptable port", done => { + response.port.path = "/dev/tty.Bluetooth-Incoming-Port"; + assert.equal(Board.isAcceptablePort(response.port), false); + + response.port.path = "/dev/someotherthing"; + assert.equal(Board.isAcceptablePort(response.port), false); + + done(); + }); + + it("invokes callback with an acceptable port: usb", done => { + response.port.path = "/dev/usb.whatever"; + + Board.requestPort((error, port) => { + assert.equal(port, response.port); + done(); + }); + }); + + it("invokes callback with an acceptable port: acm", done => { + response.port.path = "/dev/ttyACM0"; + + Board.requestPort((error, port) => { + assert.equal(port, response.port); + done(); + }); + }); + + it("invokes callback with an acceptable port: com", done => { + response.port.path = "COM0"; + + Board.requestPort((error, port) => { + assert.equal(port, response.port); + done(); + }); + }); + + it("doesn't call callback with an unacceptable port: Bluetooth-Incoming-Port", done => { + response.port.path = "/dev/tty.Bluetooth-Incoming-Port"; + + Board.requestPort((error, port) => { + assert.equal(port, null); + assert.equal(error.message, "No Acceptable Port Found"); + done(); + }); + }); + + it("produces an error when there is no Transfer.list method", done => { + com.list = null; + + Board.requestPort((error, port) => { + assert.equal(port, null); + assert.equal(error.message, "No Transport provided"); + done(); + }); + }); + + it("produces an error when there is no Transfer", done => { + Board.test.transport = null; + + Board.requestPort((error, port) => { + assert.equal(port, null); + assert.equal(error.message, "No Transport provided"); + Board.test.restoreTransport(); + done(); + }); + }); +}); + + +describe("Board: data handling", () => { + + let SerialPort; + let transportWrite; + let transport; + let initCallback; + let board; + + beforeEach(() => { + initCallback = sandbox.spy(); + SerialPort = sandbox.spy(com, "SerialPort"); + transportWrite = sandbox.spy(SerialPort.prototype, "write"); + transport = new SerialPort("/path/to/fake/usb"); + board = new Board(transport, initCallback); + }); + + afterEach(() => { + Board.test.i2cActive.clear(); + sandbox.restore(); + }); + + describe("MIDI_RESPONSE", () => { + + it("must discard a bad response that meets 3 byte MIDI_RESPONSE criteria", done => { + transport.emit("data", [NaN, NaN, NaN]); + assert.equal(board.buffer.length, 0); + done(); + }); + + describe("REPORT_VERSION", () => { + + it("must ignore unexpected adc data until REPORT_VERSION", done => { + + const parts = [ + fixtures.unexpected.adc.slice(0, 200), + fixtures.unexpected.adc.slice(200, 400), + fixtures.unexpected.adc.slice(400, 513), + ]; + + const am = sandbox.spy(Board.MIDI_RESPONSE, ANALOG_MESSAGE); + const rv = sandbox.spy(Board.MIDI_RESPONSE, REPORT_VERSION); + + assert.equal(am.callCount, 0); + assert.equal(rv.callCount, 0); + assert.equal(board.buffer.length, 0); + + for (let i = 0; i < parts[0].length; i++) { + transport.emit("data", [parts[0][i]]); + } + + // There are several I2C_REPLY messages in this data, + // none should trigger the I2C_REPLY handler. + assert.equal(am.callCount, 0); + assert.equal(rv.callCount, 0); + + + // The REPORT_VERSION byte is at index 38 + const reportVersionAtByteIndex = 38; + // We won't know it's been seen until all three + // bytes have been read and processed. + let reportVersionCalledAtIndex = -1; + let isVersioned = false; + // This contains a valid REPORT_VERSION message + // + for (let j = 0; j < parts[1].length; j++) { + transport.emit("data", [parts[1][j]]); + + if (rv.callCount === 1 && !isVersioned) { + isVersioned = true; + reportVersionCalledAtIndex = j; + } + } + + // There are several I2C_REPLY messages in this data, + // none should trigger the I2C_REPLY handler. + assert.equal(am.callCount, 0); + + // The REPORT_VERSION was received near the end (index 38) + assert.equal(rv.callCount, 1); + assert.equal(reportVersionCalledAtIndex - 2, reportVersionAtByteIndex); + + + for (let k = 0; k < parts[2].length; k++) { + transport.emit("data", [parts[2][k]]); + + if (rv.callCount === 1 && !isVersioned) { + isVersioned = true; + reportVersionCalledAtIndex = k; + } + } + + // A single I2C_REPLY exists in the third data set + assert.equal(am.callCount, 1); + // No more REPORT_VERSION calls arrived + assert.equal(rv.callCount, 1); + // The buffer is empty + assert.equal(board.buffer.length, 0); + + // Another complete I2C_REPLY arrives... + transport.emit("data", [0xe0, 0x7f, 0x03]); + + assert.equal(am.callCount, 2); + assert.equal(board.buffer.length, 0); + + done(); + }); + + it("must ignore unexpected i2c data until REPORT_VERSION", done => { + + const parts = [ + fixtures.unexpected.i2c.slice(0, 200), + fixtures.unexpected.i2c.slice(200, 400), + fixtures.unexpected.i2c.slice(400, 697), + ]; + + const ir = sandbox.spy(Board.SYSEX_RESPONSE, I2C_REPLY); + const rv = sandbox.spy(Board.MIDI_RESPONSE, REPORT_VERSION); + + assert.equal(ir.callCount, 0); + assert.equal(rv.callCount, 0); + assert.equal(board.buffer.length, 0); + + for (let i = 0; i < parts[0].length; i++) { + transport.emit("data", [parts[0][i]]); + } + + // There are several I2C_REPLY messages in this data, + // none should trigger the I2C_REPLY handler. + assert.equal(ir.callCount, 0); + assert.equal(rv.callCount, 0); + + + // The REPORT_VERSION byte is at index 194 + const reportVersionAtByteIndex = 194; + // We won't know it's been seen until all three + // bytes have been read and processed. + let reportVersionCalledAtIndex = -1; + let isVersioned = false; + // This contains a valid REPORT_VERSION message + // + for (let j = 0; j < parts[1].length; j++) { + transport.emit("data", [parts[1][j]]); + + if (rv.callCount === 1 && !isVersioned) { + isVersioned = true; + reportVersionCalledAtIndex = j; + } + } + + // There are several I2C_REPLY messages in this data, + // none should trigger the I2C_REPLY handler. + assert.equal(ir.callCount, 0); + + // The REPORT_VERSION was received near the end (index 194) + assert.equal(rv.callCount, 1); + assert.equal(reportVersionCalledAtIndex - 2, reportVersionAtByteIndex); + + + for (let k = 0; k < parts[2].length; k++) { + transport.emit("data", [parts[2][k]]); + + if (rv.callCount === 1 && !isVersioned) { + isVersioned = true; + reportVersionCalledAtIndex = k; + } + } + + // A single I2C_REPLY exists in the third data set + assert.equal(ir.callCount, 1); + // No more REPORT_VERSION calls arrived + assert.equal(rv.callCount, 1); + // The buffer is empty + assert.equal(board.buffer.length, 0); + + // Another complete I2C_REPLY arrives... + transport.emit("data", [0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7]); + + assert.equal(ir.callCount, 2); + assert.equal(board.buffer.length, 0); + + done(); + }); + + it("must ignore unexpected serial data until REPORT_VERSION", done => { + + const parts = [ + fixtures.unexpected.serial.slice(0, 200), + fixtures.unexpected.serial.slice(200, 400), + fixtures.unexpected.serial.slice(400, 697), + ]; + + const sr = sandbox.spy(Board.SYSEX_RESPONSE, SERIAL_MESSAGE); + const rv = sandbox.spy(Board.MIDI_RESPONSE, REPORT_VERSION); + + assert.equal(sr.callCount, 0); + assert.equal(rv.callCount, 0); + assert.equal(board.buffer.length, 0); + + for (let i = 0; i < parts[0].length; i++) { + transport.emit("data", [parts[0][i]]); + } + + // There are several SERIAL_MESSAGE messages in this data, + // none should trigger the SERIAL_MESSAGE handler. + assert.equal(sr.callCount, 0); + assert.equal(rv.callCount, 0); + + + // The REPORT_VERSION byte is at index 86 + const reportVersionAtByteIndex = 86; + // We won't know it's been seen until all three + // bytes have been read and processed. + let reportVersionCalledAtIndex = -1; + let isVersioned = false; + // This contains a valid REPORT_VERSION message + // + for (let j = 0; j < parts[1].length; j++) { + transport.emit("data", [parts[1][j]]); + + if (rv.callCount === 1 && !isVersioned) { + isVersioned = true; + reportVersionCalledAtIndex = j; + } + } + + // There are several SERIAL_MESSAGE messages in this data, + // none should trigger the SERIAL_MESSAGE handler. + assert.equal(sr.callCount, 0); + + // The REPORT_VERSION was received near the end (index 86) + assert.equal(rv.callCount, 1); + assert.equal(reportVersionCalledAtIndex - 2, reportVersionAtByteIndex); + + + for (let k = 0; k < parts[2].length; k++) { + transport.emit("data", [parts[2][k]]); + + if (rv.callCount === 1 && !isVersioned) { + isVersioned = true; + reportVersionCalledAtIndex = k; + } + } + + // A single SERIAL_MESSAGE exists in the third data set + assert.equal(sr.callCount, 1); + // No more REPORT_VERSION calls arrived + assert.equal(rv.callCount, 1); + // The buffer is empty + assert.equal(board.buffer.length, 0); + + // Another complete SERIAL_MESSAGE arrives... + transport.emit("data", [0xf0, 0x60, 0x48, 0x19, 0x01, 0xf7]); + + assert.equal(sr.callCount, 2); + assert.equal(board.buffer.length, 0); + + done(); + }); + }); + }); + + describe("SYSEX_RESPONSE", () => { + it("QUERY_FIRMWARE", done => { + const qf = sandbox.spy(Board.SYSEX_RESPONSE, QUERY_FIRMWARE); + + board.versionReceived = true; + + transport.emit("data", [ + START_SYSEX, + QUERY_FIRMWARE, + // Version + 2, + 3, + // Firmware name + // "StandardFirmata" + 83, 0, + 116, 0, + 97, 0, + 110, 0, + 100, 0, + 97, 0, + 114, 0, + 100, 0, + 70, 0, + 105, 0, + 114, 0, + 109, 0, + 97, 0, + 116, 0, + 97, 0, + END_SYSEX + ]); + + assert.equal(qf.callCount, 1); + assert.deepEqual(board.firmware, { + name: "StandardFirmata", + version: { + major: 2, + minor: 3 + } + }); + done(); + }); + + it("CAPABILITY_RESPONSE", done => { + const cr = sandbox.spy(Board.SYSEX_RESPONSE, CAPABILITY_RESPONSE); + + board.versionReceived = true; + + // Received over multiple data events + transport.emit("data", [ + START_SYSEX, + CAPABILITY_RESPONSE, + 0, 1, 1, 1, 4, 14, 127, + 0, 1, 1, 1, 3, 8, 4, 14, 127, + ]); + transport.emit("data", [ + 0, 1, 1, 1, 3, 8, 4, 14, 127, + 0, 1, 1, 1, 4, 14, 127, + END_SYSEX + ]); + + assert.equal(cr.callCount, 1); + done(); + }); + + it("ONEWIRE_DATA", done => { + board.versionReceived = true; + const handler = sandbox.spy(Board.SYSEX_RESPONSE, ONEWIRE_SEARCH_REPLY); + const emit = sandbox.spy(); + const bogusSubCommand = 0xE7; + + // No such sub command exists. This will hit the early return condition + Board.SYSEX_RESPONSE[ONEWIRE_DATA]({ + buffer: [0, 0, bogusSubCommand], + emit, + }); + + Board.SYSEX_RESPONSE[ONEWIRE_DATA]({ + buffer: [0, 0, ONEWIRE_SEARCH_REPLY], + emit, + }); + + assert.equal(handler.callCount, 1); + assert.equal(emit.callCount, 1); + done(); + }); + + it("PIN_STATE_RESPONSE", done => { + const cr = sandbox.spy(Board.SYSEX_RESPONSE, PIN_STATE_RESPONSE); + + board.versionReceived = true; + + transport.emit("data", [ + START_SYSEX, + CAPABILITY_RESPONSE, + 0, 1, 1, 1, 4, 14, 127, + 0, 1, 1, 1, 3, 8, 4, 14, 127, + 0, 1, 1, 1, 3, 8, 4, 14, 127, + 0, 1, 1, 1, 4, 14, 127, + END_SYSEX + ]); + + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + 0, 1, + END_SYSEX + ]); + + // Garbage data... + transport.emit("data", [ + 1, 1, 1, 1, + ]); + + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + 1, 1, + END_SYSEX + ]); + + // Garbage data followed by valid data + transport.emit("data", [ + 1, 1, 1, 1, + START_SYSEX, + PIN_STATE_RESPONSE, + 2, 1, + END_SYSEX + ]); + + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + 3, 1, + END_SYSEX + ]); + + // minimum state response to set pin 0 + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + // pin, mode, state + 0, 1, 1, + END_SYSEX + ]); + + assert.equal(board.pins[0].mode, 1); + assert.equal(board.pins[0].state, 1); + + // minimum state response to change pin 0 + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + // pin, mode, state + 0, 2, 2, + END_SYSEX + ]); + + assert.equal(board.pins[0].mode, 2); + assert.equal(board.pins[0].state, 2); + + // > 6 bytes of data: + // + // the 5 byte will be shifted 7 bits to the right + // and or'ed with state. + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + // pin, mode, state, state 2 + 0, 2, 2, 1, + END_SYSEX + ]); + + assert.equal(board.pins[0].mode, 2); + assert.equal(board.pins[0].state, 130); // 2 | (1 << 7) + + // > 7 bytes of data: + // + // the 6 byte will be shifted 14 bits to the right + // and or'ed with state. + transport.emit("data", [ + START_SYSEX, + PIN_STATE_RESPONSE, + // pin, mode, state, state 2 + 0, 2, 2, 1, 1, + END_SYSEX + ]); + + assert.equal(board.pins[0].mode, 2); + assert.equal(board.pins[0].state, 16514); // 130 | (1 << 14) + + assert.equal(cr.callCount, 8); + done(); + }); + + it("ANALOG_MAPPING_RESPONSE", done => { + const amr = sandbox.spy(Board.SYSEX_RESPONSE, ANALOG_MAPPING_RESPONSE); + + board.versionReceived = true; + + transport.emit("data", [ + START_SYSEX, + CAPABILITY_RESPONSE, + 0, 1, 1, 1, 4, 14, 127, + 0, 1, 1, 1, 3, 8, 4, 14, 127, + 0, 1, 1, 1, 3, 8, 4, 14, 127, + 0, 1, 1, 1, 4, 14, 127, + END_SYSEX + ]); + + transport.emit("data", [ + START_SYSEX, + ANALOG_MAPPING_RESPONSE, + 127, 127, 0, 1, + END_SYSEX + ]); + + // Garbage data... + transport.emit("data", [ + 1, 1, 1, 1, + ]); + + transport.emit("data", [ + START_SYSEX, + ANALOG_MAPPING_RESPONSE, + 0, 1, + ]); + + transport.emit("data", [ + 2, 3, + END_SYSEX + ]); + + // Garbage data followed by valid data + transport.emit("data", [ + 1, 1, 1, 1, + START_SYSEX, + ANALOG_MAPPING_RESPONSE, + 2, 1, + END_SYSEX + ]); + + assert.equal(amr.callCount, 3); + done(); + }); + }); +}); + +describe("Board: initialization", () => { + it("Always returns a Board instance", done => { + assert.equal(new Board("/path/to/fake1") instanceof Board, true); + done(); + }); + + it("Is a subclass of EventEmitter", done => { + assert.equal(new Board("/path/to/fake1") instanceof Emitter, true); + done(); + }); + + it("Default RESOLUTION.* values are null", done => { + const board = new Board("/path/to/fake1"); + + assert.equal(typeof board.RESOLUTION, "object"); + assert.notEqual(board.RESOLUTION, null); + assert.equal(board.RESOLUTION.ADC, null); + assert.equal(board.RESOLUTION.PWM, null); + assert.equal(board.RESOLUTION.DAC, null); + done(); + }); +}); + +describe("Board: lifecycle", function() { + + let SerialPort = sandbox.spy(com, "SerialPort"); + let transportWrite = sandbox.spy(SerialPort.prototype, "write"); + let initCallback = sandbox.spy(error => { + assert.equal(typeof error, "undefined"); + }); + let initNoop = sandbox.spy(); + + let transport = new SerialPort("/path/to/fake/usb"); + let board = new Board(transport, initCallback); + + const context = this; + + beforeEach(() => { + Board.test.i2cActive.clear(); + + transport.spy = sandbox.spy(com, "SerialPort"); + + board._events.length = 0; + }); + + afterEach(() => { + Board.SYSEX_RESPONSE[NON_STANDARD_REPLY] = undefined; + sandbox.restore(); + }); + + describe("Writing To Transport", () => { + + beforeEach(() => { + board.pending = 0; + }); + + afterEach(() => { + board.pending = 0; + }); + + it("increments pending on writeToTransport", done => { + sandbox.spy(board.transport, "write"); + + assert.equal(board.pending, 0); + Board.test.writeToTransport(board, [1, 2, 3, 4]); + assert.equal(board.pending, 1); + + const args = board.transport.write.lastCall.args; + + assert.ok(args[0].equals(Buffer.from([1, 2, 3, 4]))); + + args[1](); + assert.equal(board.pending, 0); + done(); + }); + }); + + + it("has a name", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + + assert.equal(board.name, "Firmata"); + done(); + }); + + // Legacy + it("emits 'connect' event when transport emits 'open'.", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + + board.on("connect", () => done()); + + transport.emit("open"); + }); + + it("forwards 'open' events from transport.", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + + board.on("open", () => done()); + + transport.emit("open"); + }); + + it("emits 'ready' after handshakes complete (skipCapabilities)", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, {skipCapabilities: true}, initNoop); + let oc = 0; + + board.on("open", () => { + assert.ok(true); + oc++; + }); + + board.on("connect", () => { + assert.ok(true); + oc++; + }); + + board.on("ready", function() { + assert.equal(oc, 2); + assert.equal(this.isReady, true); + done(); + }); + + transport.emit("open"); + board.emit("reportversion"); + board.emit("queryfirmware"); + }); + + it("emits 'ready' after handshakes complete", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + let oc = 0; + + board.on("open", () => { + assert.ok(true); + oc++; + }); + + board.on("connect", () => { + assert.ok(true); + oc++; + }); + + board.on("ready", function() { + assert.equal(oc, 2); + assert.equal(this.isReady, true); + done(); + }); + + transport.emit("open"); + board.emit("reportversion"); + board.emit("queryfirmware"); + board.emit("capability-query"); + board.emit("analog-mapping-query"); + }); + + it("reports errors during connect/ready", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, err => { + assert.equal("test error", err); + done(); + }); + + transport.emit("error", "test error"); + }); + + it("forwards 'close' events from transport", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + + board.on("close", done); + + transport.emit("close"); + }); + + it("forwards 'disconnect' events from transport", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + + board.on("disconnect", done); + + // https://github.com/node-serialport/node-serialport/blob/5.0.0/UPGRADE_GUIDE.md#opening-and-closing + transport.emit("close", { + disconnect: true, + disconnected: true, + }); + }); + + it("forwards 'error' event from transport", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, initNoop); + + board.on("error", done); + + board.isReady = true; + transport.emit("error"); + }); + + it("When reportVersion and queryFirmware timeout, call noop", done => { + context.timeout(50); + sandbox.stub(Board.prototype, "reportVersion"); + sandbox.stub(Board.prototype, "queryFirmware"); + const clock = sandbox.useFakeTimers(); + const transport = new SerialPort("/path/to/fake/usb"); + const opt = { + reportVersionTimeout: 1 + }; + const board = new Board(transport, opt, initNoop); + board.versionReceived = false; + + clock.tick(2); + + assert.equal(board.reportVersion.callCount, 1); + assert.equal(board.queryFirmware.callCount, 1); + + assert.equal(board.reportVersion.getCall(0).args[0](), undefined); + assert.equal(board.queryFirmware.getCall(0).args[0](), undefined); + + done(); + }); + + it("sends 'REPORT_VERSION' and 'QUERY_FIRMWARE' if it hasnt received the version within the timeout", done => { + context.timeout(50000); + const transport = new SerialPort("/path/to/fake/usb"); + const opt = { + reportVersionTimeout: 1 + }; + const board = new Board(transport, opt, initNoop); + + // rcheck for report version + transport.once("write", data => { + assert.deepEqual(data, [REPORT_VERSION]); + // check for query firmware + transport.once("write", data => { + assert.deepEqual(data, [240, 121, 247]); + done(); + }); + }); + }); + + it("receives the version on startup", done => { + //"send" report version command back from arduino + transport.emit("data", [REPORT_VERSION]); + transport.emit("data", [0x02]); + + //subscribe to the "data" event to capture the event + transport.once("data", buffer => { + assert.equal(board.version.major, 2); + assert.equal(board.version.minor, 3); + done(); + }); + + //send the last byte of command to get "data" event to fire when the report version function is called + transport.emit("data", [0x03]); + }); + + it("receives the firmware after the version", done => { + board.once("queryfirmware", () => { + assert.equal(board.firmware.version.major, 2); + assert.equal(board.firmware.version.minor, 3); + assert.equal(board.firmware.name, "StandardFirmata"); + done(); + }); + transport.emit("data", [240]); + transport.emit("data", [121]); + transport.emit("data", [2]); + transport.emit("data", [3]); + transport.emit("data", [83]); + transport.emit("data", [0]); + transport.emit("data", [116]); + transport.emit("data", [0]); + transport.emit("data", [97]); + transport.emit("data", [0]); + transport.emit("data", [110]); + transport.emit("data", [0]); + transport.emit("data", [100]); + transport.emit("data", [0]); + transport.emit("data", [97]); + transport.emit("data", [0]); + transport.emit("data", [114]); + transport.emit("data", [0]); + transport.emit("data", [100]); + transport.emit("data", [0]); + transport.emit("data", [70]); + transport.emit("data", [0]); + transport.emit("data", [105]); + transport.emit("data", [0]); + transport.emit("data", [114]); + transport.emit("data", [0]); + transport.emit("data", [109]); + transport.emit("data", [0]); + transport.emit("data", [97]); + transport.emit("data", [0]); + transport.emit("data", [116]); + transport.emit("data", [0]); + transport.emit("data", [97]); + transport.emit("data", [0]); + transport.emit("data", [247]); + }); + + it("Optionally call setSamplingInterval after queryfirmware", done => { + sandbox.spy(Board.prototype, "setSamplingInterval"); + sandbox.spy(SerialPort.prototype, "write"); + + const transport = new SerialPort("/path/to/fake/usb"); + const options = { + skipCapabilities: true, + samplingInterval: 100 + }; + const board = new Board(transport, options, error => { + assert.deepEqual(Array.from(transport.write.lastCall.args[0]), [ + 0xf0, 0x7a, 0x64, 0x00, 0xf7 + ]); + assert.equal(board.setSamplingInterval.callCount, 1); + assert.ok(board.setSamplingInterval.calledWith(100)); + done(); + }); + + // Trigger fake "reportversion" + transport.emit("data", [REPORT_VERSION, 0x02, 0x03]); + + // Trigger fake "queryfirmware" + transport.emit("data", [ + 240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0, + 97, 0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, + 97, 0, 116, 0, 97, 0, 247 + ]); + }); + + it("Does not call setSamplingInterval after queryfirmware by default", done => { + sandbox.spy(Board.prototype, "setSamplingInterval"); + sandbox.spy(SerialPort.prototype, "write"); + + const transport = new SerialPort("/path/to/fake/usb"); + const options = { + skipCapabilities: true, + }; + + const board = new Board(transport, options, () => { + assert.equal(board.setSamplingInterval.callCount, 0); + assert.equal(transport.write.callCount, 0); + done(); + }); + + // Trigger fake "reportversion" + transport.emit("data", [REPORT_VERSION, 0x02, 0x03]); + + // Trigger fake "queryfirmware" + transport.emit("data", [ + 240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0, + 97, 0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, + 97, 0, 116, 0, 97, 0, 247 + ]); + }); + + it("Returns the present samplingInterval", done => { + board.settings.samplingInterval = Infinity; + + assert.equal(board.getSamplingInterval(), Infinity); + done(); + }); + + it("gets the capabilities after the firmware", done => { + //[START_SYSEX, CAPABILITY_QUERY, END_SYSEX] + assert.deepEqual(transport.lastWrite, [START_SYSEX, CAPABILITY_QUERY, END_SYSEX]); + + //report back mock capabilities + //taken from boards.h for arduino uno + transport.emit("data", [START_SYSEX]); + transport.emit("data", [CAPABILITY_RESPONSE]); + + for (let i = 0; i < 20; i++) { + // if "pin" is digital it can be input and output + if (i >= 2 && i <= 19) { + //input is on + transport.emit("data", [0]); + transport.emit("data", [1]); + //output is on + transport.emit("data", [1]); + transport.emit("data", [1]); + } + //if pin is analog + if (i >= 14 && i <= 19) { + transport.emit("data", [0x02]); + transport.emit("data", [10]); + } + //if pin is PWM + if ([3, 5, 6, 10, 11].includes(i)) { + transport.emit("data", [0x03]); + transport.emit("data", [8]); + } + //all pins are servo + if (i >= 2) { + transport.emit("data", [0x04]); + transport.emit("data", [14]); + } + //signal end of command for pin + transport.emit("data", [127]); + } + + //capture the event once to make all pin modes are set correctly + transport.once("data", () => { + assert.equal(board.pins.length, 20); + board.pins.forEach((pin, index) => { + if (index >= 2 && index <= 19) { + + assert.notEqual(pin.supportedModes.indexOf(0), -1); + assert.notEqual(pin.supportedModes.indexOf(1), -1); + } else { + assert.equal(pin.supportedModes.length, 0); + } + if (index >= 14 && index <= 19) { + assert.notEqual(pin.supportedModes.indexOf(0x02), -1); + } else { + assert.equal(pin.supportedModes.indexOf(0x02), -1); + } + if ([3, 5, 6, 10, 11].includes(index)) { + assert.notEqual(pin.supportedModes.indexOf(0x03), -1); + } else { + assert.equal(pin.supportedModes.indexOf(0x03), -1); + } + if (index >= 2) { + assert.notEqual(pin.supportedModes.indexOf(0x04), -1); + } + }); + done(); + }); + //end the sysex message + transport.emit("data", [END_SYSEX]); + }); + + it("capabilities response is an idempotent operation", done => { + + let count = 0; + let i = 0; + + transport.on("data", function data() { + count++; + + // Should be 20 after both responses. + assert.equal(board.pins.length, 20); + + if (count === 2) { + transport.removeListener("data", data); + done(); + } + }); + + // Fake two capabilities responses... + // 1 + transport.emit("data", [START_SYSEX, CAPABILITY_RESPONSE]); + for (i = 0; i < 20; i++) { + transport.emit("data", [0, 1, 1, 1, 127]); + } + transport.emit("data", [END_SYSEX]); + // 2 + transport.emit("data", [START_SYSEX, CAPABILITY_RESPONSE]); + for (i = 0; i < 20; i++) { + transport.emit("data", [0, 1, 1, 1, 127]); + } + transport.emit("data", [END_SYSEX]); + }); + + it("board.RESOLUTION.* properties recieve values via CAPABILITY_RESPONSE", done => { + assert.equal(board.RESOLUTION.ADC, 0x3FF); + assert.equal(board.RESOLUTION.PWM, 0x0FF); + done(); + }); + + + it("querys analog mappings after capabilities", done => { + //[START_SYSEX, ANALOG_MAPPING_QUERY, END_SYSEX] + assert.deepEqual(transport.lastWrite, [START_SYSEX, ANALOG_MAPPING_QUERY, END_SYSEX]); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ANALOG_MAPPING_RESPONSE]); + for (let i = 0; i < 20; i++) { + if (i >= 14 && i < 20) { + transport.emit("data", [i - 14]); + } else { + transport.emit("data", [127]); + } + } + + transport.once("data", () => { + assert.equal(board.pins[14].analogChannel, 0); + assert.equal(board.pins[15].analogChannel, 1); + assert.equal(board.pins[16].analogChannel, 2); + assert.equal(board.pins[17].analogChannel, 3); + assert.equal(board.pins[18].analogChannel, 4); + assert.equal(board.pins[19].analogChannel, 5); + assert.equal(board.analogPins.length, 6); + assert.equal(board.analogPins[0], 14); + assert.equal(board.analogPins[1], 15); + assert.equal(board.analogPins[2], 16); + assert.equal(board.analogPins[3], 17); + assert.equal(board.analogPins[4], 18); + assert.equal(board.analogPins[5], 19); + done(); + }); + transport.emit("data", [END_SYSEX]); + }); + + it("must be ready", done => { + assert.equal(board.isReady, true); + assert.equal(initCallback.callCount, 1); + done(); + }); + + it("allows setting a valid sampling interval", done => { + const spy = sandbox.spy(board.transport, "write"); + + // Valid sampling interval + board.setSamplingInterval(20); + assert.ok(Buffer.from([0xf0, 0x7a, 0x14, 0x00, 0xf7]).equals(spy.lastCall.args[0])); + + // Invalid sampling interval is constrained to a valid interval + // > 65535 => 65535 + board.setSamplingInterval(65540); + assert.ok(Buffer.from([0xf0, 0x7a, 0x7f, 0x7f, 0xf7]).equals(spy.lastCall.args[0])); + + // Invalid sampling interval is constrained to a valid interval + // < 10 => 10 + board.setSamplingInterval(0); + assert.ok(Buffer.from([0xf0, 0x7a, 0x0a, 0x00, 0xf7]).equals(spy.lastCall.args[0])); + + spy.restore(); + done(); + }); + + it("must be able to reset (SYSTEM_RESET)", done => { + board.reset(); + assert.equal(transport.lastWrite[0], SYSTEM_RESET); + done(); + }); + + it("must be able to set pin mode on digital pin (INPUT)", done => { + board.pinMode(2, board.MODES.INPUT); + assert.equal(transport.lastWrite[0], PIN_MODE); + assert.equal(transport.lastWrite[1], 2); + assert.equal(transport.lastWrite[2], board.MODES.INPUT); + assert.equal(board.pins[2].mode, board.MODES.INPUT); + done(); + }); + + it("must be able to read value of digital pin (INPUT)", done => { + let counter = 0; + const order = [1, 0, 1, 0]; + board.digitalRead(2, value => { + if (value === 1) { + counter++; + } + if (value === 0) { + counter++; + } + if (order[0] === value) { + order.shift(); + } + if (counter === 4) { + assert.equal(order.length, 0); + done(); + } + }); + + // Digital reporting turned on... + assert.deepEqual(transport.lastWrite, [208, 1]); + + // Single Byte + transport.emit("data", [DIGITAL_MESSAGE]); + transport.emit("data", [4 % 128]); + transport.emit("data", [4 >> 7]); + + transport.emit("data", [DIGITAL_MESSAGE]); + transport.emit("data", [0x00]); + transport.emit("data", [0x00]); + + // Multi Byte + transport.emit("data", [DIGITAL_MESSAGE, 4 % 128, 4 >> 7]); + transport.emit("data", [DIGITAL_MESSAGE, 0x00, 0x00]); + }); + + it("must be able to set pin mode on digital pin (PULLUP)", done => { + board.pinMode(3, board.MODES.PULLUP); + assert.equal(transport.lastWrite[0], PIN_MODE); + assert.equal(transport.lastWrite[1], 3); + assert.equal(transport.lastWrite[2], board.MODES.PULLUP); + assert.equal(board.pins[3].mode, board.MODES.PULLUP); + done(); + }); + + it("must be able to read value of digital pin (PULLUP)", done => { + let counter = 0; + const order = [1, 0, 1, 0]; + board.pinMode(2, board.MODES.PULLUP); + board.digitalRead(2, value => { + if (value === 1) { + counter++; + } + if (value === 0) { + counter++; + } + if (order[0] === value) { + order.shift(); + } + if (counter === 4) { + assert.equal(order.length, 0); + done(); + } + }); + + // Digital reporting turned on... + assert.deepEqual(transport.lastWrite, [208, 1]); + + // Single Byte + transport.emit("data", [DIGITAL_MESSAGE]); + transport.emit("data", [4 % 128]); + transport.emit("data", [4 >> 7]); + + transport.emit("data", [DIGITAL_MESSAGE]); + transport.emit("data", [0x00]); + transport.emit("data", [0x00]); + + // Multi Byte + transport.emit("data", [DIGITAL_MESSAGE, 4 % 128, 4 >> 7]); + transport.emit("data", [DIGITAL_MESSAGE, 0x00, 0x00]); + }); + + it("must be able to set mode on analog pins", done => { + board.pinMode(board.analogPins[0], board.MODES.INPUT); + assert.equal(transport.lastWrite[0], PIN_MODE); + assert.equal(transport.lastWrite[1], board.analogPins[0]); + assert.equal(transport.lastWrite[2], board.MODES.INPUT); + done(); + }); + + it("must be able to read value of analog pin", done => { + let counter = 0; + const order = [1023, 0, 1023, 0]; + board.analogRead(1, value => { + if (value === 1023) { + counter++; + } + if (value === 0) { + counter++; + } + if (order[0] === value) { + order.shift(); + } + if (counter === 4) { + assert.equal(order.length, 0); + done(); + } + }); + + // Analog reporting turned on... + assert.deepEqual(transport.lastWrite, [193, 1]); + + // Single Byte + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF)]); + transport.emit("data", [1023 % 128]); + transport.emit("data", [1023 >> 7]); + + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF)]); + transport.emit("data", [0 % 128]); + transport.emit("data", [0 >> 7]); + + // Multi Byte + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF), 1023 % 128, 1023 >> 7]); + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF), 0 % 128, 0 >> 7]); + }); + + + it("must be able to read value of analog pin on a board that skipped capabilities check", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, {skipCapabilities: true, analogPins: [14,15,16,17,18,19]}, initNoop); + + board.on("ready", () => { + let counter = 0; + let order = [1023, 0, 1023, 0]; + board.analogRead(1, value => { + if (value === 1023) { + counter++; + } + if (value === 0) { + counter++; + } + if (order[0] === value) { + order.shift(); + } + if (counter === 4) { + assert.equal(order.length, 0); + done(); + } + }); + + // Analog reporting turned on... + assert.deepEqual(transport.lastWrite, [193, 1]); + + // Single Byte + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF)]); + transport.emit("data", [1023 % 128]); + transport.emit("data", [1023 >> 7]); + + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF)]); + transport.emit("data", [0 % 128]); + transport.emit("data", [0 >> 7]); + + // Multi Byte + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF), 1023 % 128, 1023 >> 7]); + transport.emit("data", [ANALOG_MESSAGE | (1 & 0xF), 0 % 128, 0 >> 7]); + }); + + transport.emit("open"); + board.emit("reportversion"); + board.emit("queryfirmware"); + }); + + it("must be able to write a value to a digital output", done => { + + const write = sandbox.stub(SerialPort.prototype, "write"); + const expect = [ + [ 144, 1, 0 ], + [ 144, 2, 0 ], + [ 144, 4, 0 ], + [ 144, 8, 0 ], + [ 144, 16, 0 ], + [ 144, 32, 0 ], + [ 144, 64, 0 ], + [ 144, 0, 1 ], + [ 145, 1, 0 ], + [ 145, 2, 0 ], + [ 145, 4, 0 ], + [ 145, 8, 0 ], + [ 145, 16, 0 ], + [ 145, 32, 0 ], + [ 145, 64, 0 ], + [ 145, 0, 1 ], + [ 146, 1, 0 ], + [ 146, 2, 0 ], + [ 146, 4, 0 ], + [ 146, 8, 0 ], + ]; + + for (let i = 0; i < board.pins.length; i++) { + board.digitalWrite(i, board.HIGH); + assert.deepEqual(Array.from(write.lastCall.args[0]), expect[i]); + + board.digitalWrite(i, board.LOW); + } + done(); + }); + + it("must be able to enqueue a series of digital writes and then update the ports on demand", done => { + + const write = sandbox.stub(SerialPort.prototype, "write"); + const expect = [ + [ 144, 20, 0 ], + [ 145, 5, 0 ] + ]; + + board.digitalWrite(2, board.HIGH, true); + board.digitalWrite(3, board.LOW, true); + board.digitalWrite(4, board.HIGH, true); + board.digitalWrite(5, board.LOW, true); + + // Should not call write yet + assert.equal(write.callCount, 0); + + board.digitalWrite(8, board.HIGH, true); + board.digitalWrite(9, board.LOW, true); + board.digitalWrite(10, board.HIGH, true); + board.digitalWrite(11, board.LOW, true); + + // Should not call write yet + assert.equal(write.callCount, 0); + + // Write the ports + board.flushDigitalPorts(); + + // We are updating both ports 0 and 1 + assert.equal(write.callCount, 2); + + assert.deepEqual(Array.from(write.getCall(0).args[0]), expect[0]); + assert.deepEqual(Array.from(write.getCall(1).args[0]), expect[1]); + + // Reset pins to low + [2, 4, 8, 10].forEach(pin => { + board.digitalWrite(pin, board.LOW); + }); + + done(); + }); + + it("must be able to track digital writes via ports property", done => { + for (let i = 0; i < board.pins.length; i++) { + board.pins[i].mode = board.MODES.UNKNOWN; + } + + const write = sandbox.stub(SerialPort.prototype, "write"); + const expecting = [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 1, + 2, + 4, + 8, + ]; + + for (let j = 0; j < board.pins.length; j++) { + const port = j >> 3; + const expect = expecting[j]; + + board.digitalWrite(j, board.HIGH); + + assert.equal(board.ports[port], expect); + + board.digitalWrite(j, board.LOW); + } + done(); + }); + + it("must be able to write and read to a digital port without garbling state", done => { + /* This test will change the value of port 1 as follows: + + 0b00000001 + 0b00000000 + 0b00000001 + 0b00000101 + 0b00000000 + 0b00000101 + 0b00000001 + */ + + const write = sandbox.stub(SerialPort.prototype, "write"); + const state = 0; + let calls = 0; + const expecting = [ + // 10 is high, 9 is low, 8 is high + "101", + // 10 is low, 9 is low, 8 is low + "0", + // 10 is high, 9 is low, 8 is (still) low + "100", + // 10 is low, 9 is low, 8 is high + "1" + ]; + + for (let i = 0; i < board.pins.length; i++) { + board.pins[i].mode = board.MODES.UNKNOWN; + } + + for (let j = 0; j < board.ports.length; j++) { + board.ports[j] = 0; + } + + // No Pins are high on this port + assert.equal(board.ports[1].toString(2), "0"); + + + board.pinMode(8, board.MODES.OUTPUT); + board.pinMode(10, board.MODES.INPUT); + board.digitalRead(10, data => { + assert.equal(board.ports[1].toString(2), expecting[calls++]); + + if (calls === 4) { + done(); + } + }); + /* + Pin Byte high Value + 8 0b00000001 1 + 9 0b00000010 2 + 10 0b00000100 4 + 11 0b00001000 8 + 12 0b00010000 16 + 13 0b00100000 32 + 14 0b01000000 64 + 15 0b10000000 128 + */ + + // Pin 8 is bit 0 of port byte 1, it should now be ON + board.digitalWrite(8, 1); + assert.equal(board.ports[1].toString(2), "1"); + + + // Pin 8 is bit 0 of port byte 1, it should now be OFF + board.digitalWrite(8, 0); + assert.equal(board.ports[1].toString(2), "0"); + + + // Pin 8 is bit 0 of port byte 1, it should now be ON + board.digitalWrite(8, 1); + assert.equal(board.ports[1].toString(2), "1"); + + + transport.emit("data", [DIGITAL_MESSAGE | 1, 4, 0]); + board.digitalWrite(8, 0); + // Pin 10 is bit 2 (value = 4) of port byte 1, it should now be ON + // Pin 8 is bit 0 (value = 1) of port byte 1, it should now be OFF + assert.equal(board.ports[1].toString(2), "100"); + + + transport.emit("data", [DIGITAL_MESSAGE | 1, 0, 0]); + // Pin 10 is bit 2 (value = 4) of port byte 1, it should now be OFF + // Pin 8 is bit 0 (value = 1) of port byte 1, it should now be OFF + assert.equal(board.ports[1].toString(2), "0"); + + + // Pin 10 is bit 2 (value = 4) of port byte 1, it should now be ON + // Pin 8 is bit 0 (value = 1) of port byte 1, it should now be ON + transport.emit("data", [DIGITAL_MESSAGE | 1, 4, 0]); + board.digitalWrite(8, 1); + assert.equal(board.ports[1].toString(2), "101"); + + + // Pin 10 is bit 2 (value = 4) of port byte 1, it should now be OFF + // Pin 8 is bit 0 (value = 1) of port byte 1, it should now be ON + transport.emit("data", [DIGITAL_MESSAGE | 1, 0, 0]); + board.digitalWrite(8, 1); + assert.equal(board.ports[1].toString(2), "1"); + }); + + it("must be able to write a value to a digital output to a board that skipped capabilities check", done => { + const transport = new SerialPort("/path/to/fake/usb"); + const board = new Board(transport, {skipCapabilities: true}, initNoop); + + board.on("ready", () => { + board.digitalWrite(3, board.HIGH); + assert.deepEqual(transport.lastWrite, [DIGITAL_MESSAGE, 8, 0]); + + board.digitalWrite(3, board.LOW); + assert.deepEqual(transport.lastWrite, [DIGITAL_MESSAGE, 0, 0]); + done(); + }); + + transport.emit("open"); + board.emit("reportversion"); + board.emit("queryfirmware"); + + }); + + it("must be able to write a value to an analog pin being used as a digital output", done => { + board.ports[2] = 0; + + // `DIGITAL_MESSAGE | 2` => Digital Message on Port 2 + // + board.digitalWrite(19, board.HIGH); + assert.deepEqual(transport.lastWrite, [DIGITAL_MESSAGE | 2, 8, 0]); + + board.digitalWrite(19, board.LOW); + assert.deepEqual(transport.lastWrite, [DIGITAL_MESSAGE | 2, 0, 0]); + + done(); + }); + + it("analogWrite is an alias of pwmWrite (for backward compatibility)", done => { + assert.ok(board.pwmWrite === board.analogWrite); + done(); + }); + + it("must be able to write a PWM value to a capable output", done => { + board.pwmWrite(board.analogPins[1], 1023); + assert.deepEqual(transport.lastWrite, [ANALOG_MESSAGE | board.analogPins[1], 127, 7]); + + board.pwmWrite(board.analogPins[1], 0); + assert.deepEqual(transport.lastWrite, [ANALOG_MESSAGE | board.analogPins[1], 0, 0]); + done(); + }); + + it("must be able to write a value to an extended analog output", done => { + const length = board.pins.length; + + board.pins[46] = { + supportedModes: [0, 1, 4], + mode: 4, + value: 0, + report: 1, + analogChannel: 127 + }; + + board.pwmWrite(46, 180); + assert.deepEqual(transport.lastWrite, [ + START_SYSEX, + EXTENDED_ANALOG, + 46, 52, 1, + END_SYSEX, + ]); + + board.pwmWrite(46, 0); + assert.deepEqual(transport.lastWrite, [ + START_SYSEX, + EXTENDED_ANALOG, + 46, 0, 0, + END_SYSEX, + ]); + + board.pwmWrite(46, 0x00004001); + assert.deepEqual(transport.lastWrite, [ + START_SYSEX, + EXTENDED_ANALOG, + 46, 1, 0, 1, + END_SYSEX, + ]); + + board.pwmWrite(46, 0x00200001); + assert.deepEqual(transport.lastWrite, [ + START_SYSEX, + EXTENDED_ANALOG, + 46, 1, 0, 0, 1, + END_SYSEX, + ]); + + board.pwmWrite(46, 0x10000001); + assert.deepEqual(transport.lastWrite, [ + START_SYSEX, + EXTENDED_ANALOG, + 46, 1, 0, 0, 0, 1, + END_SYSEX, + ]); + + // Restore to original length + board.pins.length = length; + + done(); + }); + + + it("must be able to send a string", done => { + const bytes = Buffer.from("test string", "utf8"); + const length = bytes.length; + board.sendString(bytes); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], STRING_DATA); + for (let i = 0; i < length; i++) { + assert.equal(transport.lastWrite[i * 2 + 2], bytes[i] & 0x7F); + assert.equal(transport.lastWrite[i * 2 + 3], (bytes[i + 1] >> 7) & 0x7F); + } + assert.equal(transport.lastWrite[length * 2 + 2], 0); + assert.equal(transport.lastWrite[length * 2 + 3], 0); + assert.equal(transport.lastWrite[length * 2 + 4], END_SYSEX); + done(); + }); + it("must emit a string event", done => { + board.on("string", string => { + assert.equal(string, "test string"); + done(); + }); + transport.emit("data", [START_SYSEX]); + transport.emit("data", [STRING_DATA]); + const bytes = Buffer.from("test string", "utf8"); + Array.prototype.forEach.call(bytes, (value, index) => { + transport.emit("data", [value]); + }); + transport.emit("data", [END_SYSEX]); + }); + + it("can query pin state", done => { + board.queryPinState(2, () => { + assert.equal(board.pins[2].state, 1024); + done(); + }); + assert.deepEqual(transport.lastWrite, [START_SYSEX, PIN_STATE_QUERY, 2, END_SYSEX]); + transport.emit("data", [START_SYSEX]); + transport.emit("data", [PIN_STATE_RESPONSE]); + transport.emit("data", [2]); + transport.emit("data", [board.MODES.INPUT]); + transport.emit("data", [0x00]); + transport.emit("data", [0x08]); + transport.emit("data", [END_SYSEX]); + }); + + it("must ignore invalid query firmware data", done => { + board.once("queryfirmware", () => { + assert.equal(board.firmware.version.major, 2); + assert.equal(board.firmware.version.minor, 5); + assert.equal(board.firmware.name.substring(0, 3), "Sta"); + done(); + }); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [QUERY_FIRMWARE]); + transport.emit("data", [0x02]); + transport.emit("data", [0x05]); + transport.emit("data", [0x53]); + transport.emit("data", [0x00]); + transport.emit("data", [0x74]); + transport.emit("data", [0x00]); + transport.emit("data", [0x61]); + transport.emit("data", [0x00]); + transport.emit("data", [0x6e]); //<<< + transport.emit("data", [0x61]); //<<< + transport.emit("data", [0x00]); + transport.emit("data", [0x2e]); + transport.emit("data", [0x00]); + transport.emit("data", [0x69]); + transport.emit("data", [0x00]); + transport.emit("data", [0x6e]); + transport.emit("data", [0x00]); + transport.emit("data", [0x6f]); + transport.emit("data", [0x1]); + transport.emit("data", [0x00]); + transport.emit("data", [END_SYSEX]); + }); + + it("cannot pingRead without PingFirmata", done => { + assert.throws(() => { + board.pingRead({ + pin: 3 + }); + }); + + done(); + }); + + it("can send a pingRead without a timeout and without a pulse out", done => { + board.pins[3].supportedModes.push(PING_READ); + board.pingRead({ + pin: 3, + value: board.HIGH, + timeout: 1000000 + }, duration => { + assert.equal(duration, 0); + done(); + }); + assert.deepEqual(transport.lastWrite, [START_SYSEX, PING_READ, 3, board.HIGH, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 66, 0, 64, 0, END_SYSEX]); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [PING_READ]); + transport.emit("data", [3]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a pingRead with a timeout and a pulse out", done => { + board.pingRead({ + pin: 3, + value: board.HIGH, + pulseOut: 5, + timeout: 1000000 + }, duration => { + assert.equal(duration, 1000000); + done(); + }); + assert.deepEqual(transport.lastWrite, [START_SYSEX, PING_READ, 3, board.HIGH, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 15, 0, 66, 0, 64, 0, END_SYSEX]); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [PING_READ]); + transport.emit("data", [3]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [15]); + transport.emit("data", [0]); + transport.emit("data", [66]); + transport.emit("data", [0]); + transport.emit("data", [64]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + it("can send a pingRead with a pulse out and without a timeout ", done => { + board.pingRead({ + pin: 3, + value: board.HIGH, + pulseOut: 5 + }, duration => { + assert.equal(duration, 1000000); + done(); + }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PING_READ); + assert.equal(transport.lastWrite[2], 3); + assert.equal(transport.lastWrite[3], board.HIGH); + assert.equal(transport.lastWrite[4], 0); + assert.equal(transport.lastWrite[5], 0); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], 5); + assert.equal(transport.lastWrite[11], 0); + assert.equal(transport.lastWrite[12], 0); + assert.equal(transport.lastWrite[13], 0); + assert.equal(transport.lastWrite[14], 15); + assert.equal(transport.lastWrite[15], 0); + assert.equal(transport.lastWrite[16], 66); + assert.equal(transport.lastWrite[17], 0); + assert.equal(transport.lastWrite[18], 64); + assert.equal(transport.lastWrite[19], 0); + assert.equal(transport.lastWrite[20], END_SYSEX); + transport.emit("data", [START_SYSEX]); + transport.emit("data", [PING_READ]); + transport.emit("data", [3]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [15]); + transport.emit("data", [0]); + transport.emit("data", [66]); + transport.emit("data", [0]); + transport.emit("data", [64]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a stepper config for a driver configuration", done => { + board.stepperConfig(0, board.STEPPER.TYPE.DRIVER, 200, 2, 3); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], STEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], board.STEPPER.TYPE.DRIVER); + assert.equal(transport.lastWrite[5], 200 & 0x7F); + assert.equal(transport.lastWrite[6], (200 >> 7) & 0x7F); + assert.equal(transport.lastWrite[7], 2); + assert.equal(transport.lastWrite[8], 3); + assert.equal(transport.lastWrite[9], END_SYSEX); + done(); + }); + + it("can send a stepper config for a two wire configuration", done => { + board.stepperConfig(0, board.STEPPER.TYPE.TWO_WIRE, 200, 2, 3); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], STEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], board.STEPPER.TYPE.TWO_WIRE); + assert.equal(transport.lastWrite[5], 200 & 0x7F); + assert.equal(transport.lastWrite[6], (200 >> 7) & 0x7F); + assert.equal(transport.lastWrite[7], 2); + assert.equal(transport.lastWrite[8], 3); + assert.equal(transport.lastWrite[9], END_SYSEX); + done(); + }); + + it("can send a stepper config for a four wire configuration", done => { + board.stepperConfig(0, board.STEPPER.TYPE.FOUR_WIRE, 200, 2, 3, 4, 5); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], STEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], board.STEPPER.TYPE.FOUR_WIRE); + assert.equal(transport.lastWrite[5], 200 & 0x7F); + assert.equal(transport.lastWrite[6], (200 >> 7) & 0x7F); + assert.equal(transport.lastWrite[7], 2); + assert.equal(transport.lastWrite[8], 3); + assert.equal(transport.lastWrite[9], 4); + assert.equal(transport.lastWrite[10], 5); + assert.equal(transport.lastWrite[11], END_SYSEX); + done(); + }); + + it("can send a stepper move without acceleration or deceleration", done => { + board.stepperStep(2, board.STEPPER.DIRECTION.CCW, 10000, 2000, complete => { + assert.equal(complete, true); + done(); + }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], STEPPER); + assert.equal(transport.lastWrite[2], 1); + assert.equal(transport.lastWrite[3], 2); + assert.equal(transport.lastWrite[4], board.STEPPER.DIRECTION.CCW); + assert.equal(transport.lastWrite[5], 10000 & 0x7F); + assert.equal(transport.lastWrite[6], (10000 >> 7) & 0x7F); + assert.equal(transport.lastWrite[7], (10000 >> 14) & 0x7F); + assert.equal(transport.lastWrite[8], 2000 & 0x7F); + assert.equal(transport.lastWrite[9], (2000 >> 7) & 0x7F); + assert.equal(transport.lastWrite[9], (2000 >> 7) & 0x7F); + assert.equal(transport.lastWrite[10], END_SYSEX); + transport.emit("data", [START_SYSEX]); + transport.emit("data", [STEPPER]); + transport.emit("data", [2]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a stepper move with acceleration and deceleration", done => { + board.stepperStep(3, board.STEPPER.DIRECTION.CCW, 10000, 2000, 3000, 8000, complete => { + assert.equal(complete, true); + done(); + }); + + const message = [START_SYSEX, STEPPER, 1, 3, board.STEPPER.DIRECTION.CCW, 10000 & 0x7F, (10000 >> 7) & 0x7F, (10000 >> 14) & 0x7F, 2000 & 0x7F, (2000 >> 7) & 0x7F, 3000 & 0x7F, (3000 >> 7) & 0x7F, 8000 & 0x7F, (8000 >> 7) & 0x7F, END_SYSEX]; + assert.deepEqual(transport.lastWrite, message); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [STEPPER]); + transport.emit("data", [3]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a accelStepper config for a driver configuration with enable and invert", done => { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.DRIVER, stepPin: 5, directionPin: 6, enablePin: 2, invertPins: [2] }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x11); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 2); + assert.equal(transport.lastWrite[8], 16); + assert.equal(transport.lastWrite[9], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a two wire configuration", done => { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.TWO_WIRE, motorPin1: 5, motorPin2: 6, invertPins: [5, 6] }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x20); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a four wire configuration", done => { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.FOUR_WIRE, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4 }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x40); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a four wire, half step configuration", done => { + board.accelStepperConfig({ deviceNum: 0, type: board.STEPPER.TYPE.FOUR_WIRE, stepSize: board.STEPPER.STEP_SIZE.HALF, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4 }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x42); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], END_SYSEX); + done(); + }); + + it("can send a accelStepper config with four wire and whole step as defaults", done => { + board.accelStepperConfig({ deviceNum: 0, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4 }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x40); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 0); + assert.equal(transport.lastWrite[10], END_SYSEX); + done(); + }); + + it("can send a accelStepper config for a default four wire configuration with inverted motor and enable pins", done => { + board.accelStepperConfig({ deviceNum: 0, motorPin1: 5, motorPin2: 6, motorPin3: 3, motorPin4: 4, enablePin: 2, invertPins: [2, 3, 4, 5, 6] }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0x41); + assert.equal(transport.lastWrite[5], 5); + assert.equal(transport.lastWrite[6], 6); + assert.equal(transport.lastWrite[7], 3); + assert.equal(transport.lastWrite[8], 4); + assert.equal(transport.lastWrite[9], 2); + assert.equal(transport.lastWrite[10], 31); + assert.equal(transport.lastWrite[11], END_SYSEX); + done(); + }); + + it("can send a accelStepper step", done => { + board.accelStepperStep(0, 12345, value => { + assert.equal(value, 12345); + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 2); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 57); + assert.equal(transport.lastWrite[5], 96); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x0A]); + transport.emit("data", [0]); + transport.emit("data", [57]); + transport.emit("data", [96]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a accelStepper step w/o a callback", done => { + board.accelStepperStep(0, 12345); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 2); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 57); + assert.equal(transport.lastWrite[5], 96); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + done(); + }); + + it("can send a accelStepper zero", done => { + board.accelStepperZero(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 1); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("can send a accelStepper enable", done => { + board.accelStepperEnable(0, true); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 4); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 1); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can send a accelStepper enable using default value", done => { + board.accelStepperEnable(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 4); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 1); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can can send a accelStepper disable", done => { + board.accelStepperEnable(0, false); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 4); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can send a accelStepper move to as specified position", done => { + board.accelStepperTo(0, 2000, value => { + assert.equal(value, 2000); + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 3); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 80); + assert.equal(transport.lastWrite[5], 15); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x0A]); + transport.emit("data", [0]); + transport.emit("data", [80]); + transport.emit("data", [15]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + }); + + it("can send a accelStepper move to as specified position w/o a callback", done => { + board.accelStepperTo(0, 2000); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 3); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 80); + assert.equal(transport.lastWrite[5], 15); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], END_SYSEX); + + done(); + }); + + it("can send an accelStepper stop", done => { + + board.accelStepperStop(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 5); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + + done(); + + }); + + it("can send a accelStepper reportPosition", done => { + board.accelStepperReportPosition(0, () => done()); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 6); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x06]); + transport.emit("data", [0]); + transport.emit("data", [80]); + transport.emit("data", [15]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + + it("can send a accelStepper set speed", done => { + board.accelStepperSpeed(0, 123.4); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 9); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 82); + assert.equal(transport.lastWrite[5], 9); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 40); + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can send a accelStepper set acceleration", done => { + board.accelStepperAcceleration(0, 199.9); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 8); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 24); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 122); + assert.equal(transport.lastWrite[7], 28); + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can configure a multiStepper", done => { + board.multiStepperConfig({ groupNum: 0, devices: [0, 1, 2] }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x20); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 0); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 2); + assert.equal(transport.lastWrite[7], END_SYSEX); + done(); + }); + + it("can send a multiStepper stop", done => { + board.multiStepperStop(0); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x23); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("multiStepperStop(-1)", done => { + assert.throws(() => { + board.multiStepperStop(-1); + }); + done(); + }); + + it("multiStepperStop(6)", done => { + assert.throws(() => { + board.multiStepperStop(6); + }); + done(); + }); + + it("can send a multiStepper to", done => { + board.multiStepperTo(0, [200, 400, 600], () => done()); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x21); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 72); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], 16); + assert.equal(transport.lastWrite[10], 3); + assert.equal(transport.lastWrite[11], 0); + assert.equal(transport.lastWrite[12], 0); + assert.equal(transport.lastWrite[13], 0); + assert.equal(transport.lastWrite[14], 88); + assert.equal(transport.lastWrite[15], 4); + assert.equal(transport.lastWrite[16], 0); + assert.equal(transport.lastWrite[17], 0); + assert.equal(transport.lastWrite[18], 0); + assert.equal(transport.lastWrite[19], END_SYSEX); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x24]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + + it("can send a multiStepper to w/o a callback", done => { + board.multiStepperTo(0, [200, 400, 600]); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], ACCELSTEPPER); + assert.equal(transport.lastWrite[2], 0x21); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], 72); + assert.equal(transport.lastWrite[5], 1); + assert.equal(transport.lastWrite[6], 0); + assert.equal(transport.lastWrite[7], 0); + assert.equal(transport.lastWrite[8], 0); + assert.equal(transport.lastWrite[9], 16); + assert.equal(transport.lastWrite[10], 3); + assert.equal(transport.lastWrite[11], 0); + assert.equal(transport.lastWrite[12], 0); + assert.equal(transport.lastWrite[13], 0); + assert.equal(transport.lastWrite[14], 88); + assert.equal(transport.lastWrite[15], 4); + assert.equal(transport.lastWrite[16], 0); + assert.equal(transport.lastWrite[17], 0); + assert.equal(transport.lastWrite[18], 0); + assert.equal(transport.lastWrite[19], END_SYSEX); + + done(); + + }); + + it("multiStepperTo(-1)", done => { + assert.throws(() => { + board.multiStepperTo(-1); + }); + done(); + }); + + it("multiStepperTo(6)", done => { + assert.throws(() => { + board.multiStepperStop(6); + }); + done(); + }); + + it("can receive a stepper position", done => { + board.once("stepper-position-0", value => { + assert.equal(value, 1234); + done(); + }); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x06]); + transport.emit("data", [0]); + transport.emit("data", [82]); + transport.emit("data", [9]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + + it("can receive a multStepper done", done => { + board.once("multi-stepper-done-0", () => done()); + + transport.emit("data", [START_SYSEX]); + transport.emit("data", [ACCELSTEPPER]); + transport.emit("data", [0x24]); + transport.emit("data", [0]); + transport.emit("data", [END_SYSEX]); + + }); + + it("must be able to send a 1-wire config with parasitic power enabled", done => { + board.sendOneWireConfig(1, true); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_CONFIG_REQUEST); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + assert.equal(transport.lastWrite[4], ONEWIRE_RESET_REQUEST_BIT); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + it("must be able to send a 1-wire config with parasitic power disabled", done => { + board.sendOneWireConfig(1, false); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_CONFIG_REQUEST); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + assert.equal(transport.lastWrite[4], 0x00); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + it("must be able to send a 1-wire search request and recieve a reply", done => { + board.sendOneWireSearch(1, (error, devices) => { + assert.equal(devices.length, 1); + done(); + }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_SEARCH_REQUEST); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + assert.equal(transport.lastWrite[4], END_SYSEX); + + transport.emit("data", [START_SYSEX, PULSE_OUT, ONEWIRE_SEARCH_REPLY, ONEWIRE_RESET_REQUEST_BIT, 0x28, 0x36, 0x3F, 0x0F, 0x52, 0x00, 0x00, 0x00, 0x5D, 0x00, END_SYSEX]); + }); + it("must be able to send a 1-wire search alarm request and recieve a reply", done => { + board.sendOneWireAlarmsSearch(1, (error, devices) => { + assert.equal(devices.length, 1); + done(); + }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_SEARCH_ALARMS_REQUEST); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + assert.equal(transport.lastWrite[4], END_SYSEX); + + transport.emit("data", [START_SYSEX, PULSE_OUT, ONEWIRE_SEARCH_ALARMS_REPLY, ONEWIRE_RESET_REQUEST_BIT, 0x28, 0x36, 0x3F, 0x0F, 0x52, 0x00, 0x00, 0x00, 0x5D, 0x00, END_SYSEX]); + }); + it("must be able to send a 1-wire write read", done => { + sandbox.spy(board, Board.test.symbols.SYM_sendOneWireRequest); + board.sendOneWireRead(1, 1, 1, () => {}); + + board[Board.test.symbols.SYM_sendOneWireRequest].lastCall.args[8](); + done(); + }); + it("must be able to send a 1-wire reset request", done => { + board.sendOneWireReset(1); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_RESET_REQUEST_BIT); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + + done(); + }); + it("must be able to send a 1-wire delay request", done => { + const delay = 1000; + + board.sendOneWireDelay(1, delay); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_WITHDATA_REQUEST_BITS); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + + // decode delay from request + const request = Encoder7Bit.from7BitArray(transport.lastWrite.slice(4, transport.lastWrite.length - 1)); + const sentDelay = request[12] | (request[13] << 8) | (request[14] << 12) | request[15] << 24; + assert.equal(sentDelay, delay); + + done(); + }); + + it("must be able to send a 1-wire write request", done => { + const device = [40, 219, 239, 33, 5, 0, 0, 93]; + const data = 0x33; + + board.sendOneWireWrite(1, device, data); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_WITHDATA_REQUEST_BITS); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + + // decode delay from request + const request = Encoder7Bit.from7BitArray(transport.lastWrite.slice(4, transport.lastWrite.length - 1)); + + // should select the passed device + assert.equal(request[0], device[0]); + assert.equal(request[1], device[1]); + assert.equal(request[2], device[2]); + assert.equal(request[3], device[3]); + assert.equal(request[4], device[4]); + assert.equal(request[5], device[5]); + assert.equal(request[6], device[6]); + assert.equal(request[7], device[7]); + + // and send the passed data + assert.equal(request[16], data); + + done(); + }); + + it("must be able to send a 1-wire write request (Array)", done => { + const device = [40, 219, 239, 33, 5, 0, 0, 93]; + const data = 0x33; + + board.sendOneWireWrite(1, device, [data]); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_WITHDATA_REQUEST_BITS); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + + // decode delay from request + const request = Encoder7Bit.from7BitArray(transport.lastWrite.slice(4, transport.lastWrite.length - 1)); + + // should select the passed device + assert.equal(request[0], device[0]); + assert.equal(request[1], device[1]); + assert.equal(request[2], device[2]); + assert.equal(request[3], device[3]); + assert.equal(request[4], device[4]); + assert.equal(request[5], device[5]); + assert.equal(request[6], device[6]); + assert.equal(request[7], device[7]); + + // and send the passed data + assert.equal(request[16], data); + + done(); + }); + it("must be able to send a 1-wire write and read request and recieve a reply", done => { + const device = [40, 219, 239, 33, 5, 0, 0, 93]; + const data = 0x33; + const output = [ONEWIRE_RESET_REQUEST_BIT, 0x02]; + + board.sendOneWireWriteAndRead(1, device, data, 2, (error, received) => { + + assert.deepEqual(received, output); + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_WITHDATA_REQUEST_BITS); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + + // decode delay from request + const request = Encoder7Bit.from7BitArray(transport.lastWrite.slice(4, transport.lastWrite.length - 1)); + + // should select the passed device + assert.equal(request[0], device[0]); + assert.equal(request[1], device[1]); + assert.equal(request[2], device[2]); + assert.equal(request[3], device[3]); + assert.equal(request[4], device[4]); + assert.equal(request[5], device[5]); + assert.equal(request[6], device[6]); + assert.equal(request[7], device[7]); + + // and send the passed data + assert.equal(request[16], data); + + const dataSentFromBoard = []; + + // respond with the same correlation id + dataSentFromBoard[0] = request[10]; + dataSentFromBoard[1] = request[11]; + + // data "read" from the 1-wire device + dataSentFromBoard[2] = output[0]; + dataSentFromBoard[3] = output[1]; + + transport.emit("data", [START_SYSEX, PULSE_OUT, ONEWIRE_READ_REPLY, ONEWIRE_RESET_REQUEST_BIT].concat(Encoder7Bit.to7BitArray(dataSentFromBoard)).concat([END_SYSEX])); + }); + + it("must be able to send a 1-wire write and read request and recieve a reply (array)", done => { + const device = [40, 219, 239, 33, 5, 0, 0, 93]; + const data = 0x33; + const output = [ONEWIRE_RESET_REQUEST_BIT, 0x02]; + + board.sendOneWireWriteAndRead(1, device, [data], 2, (error, received) => { + assert.deepEqual(received, output); + done(); + }); + + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], PULSE_OUT); + assert.equal(transport.lastWrite[2], ONEWIRE_WITHDATA_REQUEST_BITS); + assert.equal(transport.lastWrite[3], ONEWIRE_RESET_REQUEST_BIT); + + // decode delay from request + const request = Encoder7Bit.from7BitArray(transport.lastWrite.slice(4, transport.lastWrite.length - 1)); + + // should select the passed device + assert.equal(request[0], device[0]); + assert.equal(request[1], device[1]); + assert.equal(request[2], device[2]); + assert.equal(request[3], device[3]); + assert.equal(request[4], device[4]); + assert.equal(request[5], device[5]); + assert.equal(request[6], device[6]); + assert.equal(request[7], device[7]); + + // and send the passed data + assert.equal(request[16], data); + + const dataSentFromBoard = []; + + // respond with the same correlation id + dataSentFromBoard[0] = request[10]; + dataSentFromBoard[1] = request[11]; + + // data "read" from the 1-wire device + dataSentFromBoard[2] = output[0]; + dataSentFromBoard[3] = output[1]; + + transport.emit("data", [START_SYSEX, PULSE_OUT, ONEWIRE_READ_REPLY, ONEWIRE_RESET_REQUEST_BIT].concat(Encoder7Bit.to7BitArray(dataSentFromBoard)).concat([END_SYSEX])); + }); + + describe("Servo", () => { + it("can configure a servo pwm range", done => { + board.servoConfig(3, 1000, 2000); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], SERVO_CONFIG); + assert.equal(transport.lastWrite[2], 0x03); + + assert.equal(transport.lastWrite[3], 1000 & 0x7F); + assert.equal(transport.lastWrite[4], (1000 >> 7) & 0x7F); + + assert.equal(transport.lastWrite[5], 2000 & 0x7F); + assert.equal(transport.lastWrite[6], (2000 >> 7) & 0x7F); + done(); + }); + + it("can configure a servo pwm range, with object", done => { + board.servoConfig({ + pin: 3, + min: 1000, + max: 2000, + }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[1], SERVO_CONFIG); + assert.equal(transport.lastWrite[2], 0x03); + + assert.equal(transport.lastWrite[3], 1000 & 0x7F); + assert.equal(transport.lastWrite[4], (1000 >> 7) & 0x7F); + + assert.equal(transport.lastWrite[5], 2000 & 0x7F); + assert.equal(transport.lastWrite[6], (2000 >> 7) & 0x7F); + done(); + }); + + it("will throw if servoConfig is missing any parameters", done => { + + assert.throws(() => { + board.servoConfig(); + }); + + assert.throws(() => { + board.servoConfig(3, 1000); + }); + + assert.throws(() => { + board.servoConfig({ + min: 1000, + max: 2000, + }); + }); + + assert.throws(() => { + board.servoConfig({ + pin: 3, + max: 2000, + }); + }); + + assert.throws(() => { + board.servoConfig({ + pin: 3, + min: 1000, + }); + }); + + assert.throws(() => { + board.servoConfig({}); + }); + done(); + }); + + it("calls analogWrite with arguments", done => { + const aw = sandbox.stub(board, "analogWrite"); + + board.servoWrite(9, 180); + assert.deepEqual(aw.lastCall.args, [9, 180]); + + board.servoWrite(9, 600); + assert.deepEqual(aw.lastCall.args, [9, 600]); + done(); + }); + + }); + + describe("I2C", () => { + + it("throws if i2c not enabled", done => { + + assert.throws(() => { + board.i2cRead(1, 1, initNoop); + }); + assert.throws(() => { + board.i2cReadOnce(1, 1, initNoop); + }); + assert.throws(() => { + board.i2cWrite(1, [1, 2, 3]); + }); + assert.throws(() => { + board.i2cWriteReg(1, 1, 1); + }); + done(); + }); + + it("must be able to send an i2c config (empty)", done => { + board.i2cConfig(); + assert.deepEqual(transport.lastWrite, [START_SYSEX, I2C_CONFIG, 0, 0, END_SYSEX]); + done(); + }); + + it("calls i2cConfig (sendI2CConfig)", done => { + + const ic = sandbox.stub(board, "i2cConfig"); + + board.sendI2CConfig(); + assert.equal(ic.callCount, 1); + done(); + }); + + it("must be able to send an i2c config (number)", done => { + board.i2cConfig(1); + assert.deepEqual(transport.lastWrite, [START_SYSEX, I2C_CONFIG, 1 & 0xFF, (1 >> 8) & 0xFF, END_SYSEX]); + done(); + }); + + it("must be able to send an i2c config (object with delay property)", done => { + board.i2cConfig({ delay: 1 }); + assert.deepEqual(transport.lastWrite, [START_SYSEX, I2C_CONFIG, 1 & 0xFF, (1 >> 8) & 0xFF, END_SYSEX]); + done(); + }); + + it("must be able to send an i2c request", done => { + board.i2cConfig(1); + board.sendI2CWriteRequest(0x68, [1, 2, 3]); + const request = [START_SYSEX, I2C_REQUEST, 0x68, 0 << 3, 1 & 0x7F, (1 >> 7) & 0x7F, 2 & 0x7F, (2 >> 7) & 0x7F, 3 & 0x7F, (3 >> 7) & 0x7F, END_SYSEX]; + assert.deepEqual(transport.lastWrite, request); + done(); + }); + + it("must be able to receive an i2c reply", done => { + const handler = sandbox.spy(() => {}); + board.i2cConfig(1); + board.sendI2CReadRequest(0x68, 4, handler); + assert.deepEqual(transport.lastWrite, [START_SYSEX, I2C_REQUEST, 0x68, 1 << 3, 4 & 0x7F, (4 >> 7) & 0x7F, END_SYSEX]); + + // Start + transport.emit("data", [START_SYSEX]); + // Reply + transport.emit("data", [I2C_REPLY]); + // Address + transport.emit("data", [0x68 % 128]); + transport.emit("data", [0x68 >> 7]); + // Register + transport.emit("data", [0]); + transport.emit("data", [0]); + // Data 0 + transport.emit("data", [1 & 0x7F]); + transport.emit("data", [(1 >> 7) & 0x7F]); + // Data 1 + transport.emit("data", [2 & 0x7F]); + transport.emit("data", [(2 >> 7) & 0x7F]); + // Data 2 + transport.emit("data", [3 & 0x7F]); + transport.emit("data", [(3 >> 7) & 0x7F]); + // Data 3 + transport.emit("data", [4 & 0x7F]); + transport.emit("data", [(4 >> 7) & 0x7F]); + // End + transport.emit("data", [END_SYSEX]); + + assert.equal(handler.callCount, 1); + assert.deepEqual(handler.getCall(0).args[0], [1, 2, 3, 4]); + done(); + }); + + it("does not create default settings for an i2c peripheral, when call to i2cConfig does not include address", done => { + board.i2cConfig(); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { delay: 0 }); + done(); + }); + + it("creates default settings for an i2c peripheral, with call to i2cConfig that includes address", done => { + board.i2cConfig({ + address: 0x00 + }); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("creates default settings for an i2c peripheral, without call to i2cConfig for that peripheral (i2cWrite)", done => { + // This has to be called no matter what, + // but it might be the case that it's called once in a program, + // where there are several different i2c peripherals. + board.i2cConfig(); + + board.i2cWrite(0x00, [0x01, 0x02]); + board.i2cWrite(0x05, [0x06, 0x07]); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + 5: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("creates default settings for an i2c peripheral, without call to i2cConfig for that peripheral (i2cWriteReg)", done => { + // This has to be called no matter what, + // but it might be the case that it's called once in a program, + // where there are several different i2c peripherals. + board.i2cConfig(); + + board.i2cWriteReg(0x00, 0x01, 0x02); + board.i2cWriteReg(0x05, 0x06, 0x07); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + 5: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("creates default settings for an i2c peripheral, without call to i2cConfig for that peripheral (i2cRead w/ Register)", done => { + // This has to be called no matter what, + // but it might be the case that it's called once in a program, + // where there are several different i2c peripherals. + board.i2cConfig(); + + board.i2cRead(0x00, 0x01, 1, initNoop); + board.i2cRead(0x05, 0x06, 1, initNoop); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + 5: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("creates default settings for an i2c peripheral, without call to i2cConfig for that peripheral (i2cRead w/o Register)", done => { + // This has to be called no matter what, + // but it might be the case that it's called once in a program, + // where there are several different i2c peripherals. + board.i2cConfig(); + + board.i2cRead(0x00, 1, initNoop); + board.i2cRead(0x05, 1, initNoop); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + 5: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("does nothing when i2cStop()", done => { + board.i2cConfig(); + + assert.equal(transportWrite.callCount, 0); + + board.i2cRead(0x00, 1, initNoop); + board.i2cStop(); + assert.equal(transportWrite.callCount, 0); + done(); + }); + + it("can stop a continuous read with i2cStop(address)", done => { + board.i2cConfig(); + + Object.keys(board._events).forEach(event => { + if (event.startsWith("I2C-reply-")) { + board.removeAllListeners(event); + } + }); + const removeAllListeners = sandbox.spy(board, "removeAllListeners"); + + + board.i2cRead(0x00, 1, initNoop); + board.i2cStop(0x00); + + assert.equal(transport.lastWrite[2], 0x00); + assert.equal(transport.lastWrite[3], board.I2C_MODES.STOP_READING << 3); + assert.equal(transport.lastWrite[4], END_SYSEX); + assert.equal(removeAllListeners.callCount, 1); + done(); + }); + + it("can stop a continuous read with i2cStop({address})", done => { + board.i2cConfig(); + + Object.keys(board._events).forEach(event => { + if (event.startsWith("I2C-reply-")) { + board.removeAllListeners(event); + } + }); + const removeAllListeners = sandbox.spy(board, "removeAllListeners"); + + board.i2cRead(0x00, 1, initNoop); + board.i2cStop({ address: 0x00 }); + + assert.equal(transport.lastWrite[2], 0x00); + assert.equal(transport.lastWrite[3], board.I2C_MODES.STOP_READING << 3); + assert.equal(transport.lastWrite[4], END_SYSEX); + assert.equal(removeAllListeners.callCount, 1); + done(); + }); + + + it("creates default settings for an i2c peripheral, without call to i2cConfig for that peripheral (i2cReadOnce w/ Register)", done => { + // This has to be called no matter what, + // but it might be the case that it's called once in a program, + // where there are several different i2c peripherals. + board.i2cConfig(); + + board.i2cReadOnce(0x00, 0x01, 1, initNoop); + board.i2cReadOnce(0x05, 0x06, 1, initNoop); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + 5: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("creates default settings for an i2c peripheral, without call to i2cConfig for that peripheral (i2cReadOnce w/o Register)", done => { + // This has to be called no matter what, + // but it might be the case that it's called once in a program, + // where there are several different i2c peripherals. + board.i2cConfig(); + + board.i2cReadOnce(0x00, 1, initNoop); + board.i2cReadOnce(0x05, 1, initNoop); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: true, + }, + 5: { + stopTX: true, + }, + delay: 0, + }); + done(); + }); + + it("can store arbitrary settings for an i2c peripheral via i2cConfig", done => { + board.i2cConfig({ + address: 0x00, + settings: { + whatever: true, + } + }); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + delay: 0, + 0: { + stopTX: true, + whatever: true, + } + }); + done(); + }); + + it("allows stored i2c peripheral settings to be reconfigured via i2cConfig", done => { + board.i2cConfig({ + address: 0x00, + settings: { + stopTX: false, + } + }); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: false, + }, + delay: 0, + }); + + board.i2cConfig({ + address: 0x00, + settings: { + stopTX: true, + } + }); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + delay: 0, + 0: { + stopTX: true, + } + }); + done(); + }); + + it("allows an i2c peripheral's stopTX to be overridden", done => { + // var spy = sandbox.spy(board.transport, "write"); + const mask = 0x48; // 01001000 + + board.i2cConfig({ + address: 0x00, + settings: { + stopTX: true, + } + }); + + board.i2cReadOnce(0x00, 0x01, 1, initNoop); + + assert.deepEqual(transport.lastWrite, [240, 118, 0, 8, 1, 0, 1, 0, 247]); + + board.i2cConfig({ + address: 0x00, + settings: { + stopTX: false, + } + }); + + assert.deepEqual(Board.test.i2cPeripheralSettings(board), { + 0: { + stopTX: false, + }, + delay: 0, + }); + + board.i2cReadOnce(0x00, 0x01, 1, initNoop); + + assert.deepEqual(transport.lastWrite, [240, 118, 0, 72, 1, 0, 1, 0, 247]); + + board.i2cRead(0x00, 0x01, 1, initNoop); + + assert.deepEqual(transport.lastWrite, [240, 118, 0, 80, 1, 0, 1, 0, 247]); + done(); + }); + + it("has an i2cWrite method, that writes a data array", done => { + const spy = sandbox.spy(transport, "write"); + + board.i2cConfig(0); + board.i2cWrite(0x53, [1, 2]); + + assert.deepEqual(transport.lastWrite, [240, 118, 83, 0, 1, 0, 2, 0, 247]); + assert.equal(spy.callCount, 2); + spy.restore(); + done(); + }); + + it("has an i2cWrite method, that writes a byte", done => { + const spy = sandbox.spy(transport, "write"); + + board.i2cConfig(0); + board.i2cWrite(0x53, 1); + + assert.deepEqual(transport.lastWrite, [240, 118, 83, 0, 1, 0, 247]); + assert.equal(spy.callCount, 2); + spy.restore(); + done(); + }); + + it("has an i2cWrite method, that writes a data array to a register", done => { + const spy = sandbox.spy(transport, "write"); + + board.i2cConfig(0); + board.i2cWrite(0x53, 0xB2, [1, 2]); + + assert.deepEqual(transport.lastWrite, [240, 118, 83, 0, 50, 1, 1, 0, 2, 0, 247]); + assert.equal(spy.callCount, 2); + spy.restore(); + done(); + }); + + it("has an i2cWrite method, that writes a data byte to a register", done => { + const spy = sandbox.spy(transport, "write"); + + board.i2cConfig(0); + board.i2cWrite(0x53, 0xB2, 1); + + assert.deepEqual(transport.lastWrite, [240, 118, 83, 0, 50, 1, 1, 0, 247]); + assert.equal(spy.callCount, 2); + spy.restore(); + done(); + }); + + it("has an i2cWriteReg method, that writes a data byte to a register", done => { + const spy = sandbox.spy(transport, "write"); + + board.i2cConfig(0); + board.i2cWrite(0x53, 0xB2, 1); + + assert.deepEqual(transport.lastWrite, [240, 118, 83, 0, 50, 1, 1, 0, 247]); + assert.equal(spy.callCount, 2); + spy.restore(); + done(); + }); + + it("has an i2cRead method that reads continuously", done => { + const handler = sandbox.spy(() => {}); + + board.i2cConfig(0); + board.i2cRead(0x53, 0x04, handler); + + for (let i = 0; i < 5; i++) { + transport.emit("data", [ + START_SYSEX, I2C_REPLY, 83, 0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX + ]); + } + + assert.equal(handler.callCount, 5); + assert.equal(handler.getCall(0).args[0].length, 4); + assert.equal(handler.getCall(1).args[0].length, 4); + assert.equal(handler.getCall(2).args[0].length, 4); + assert.equal(handler.getCall(3).args[0].length, 4); + assert.equal(handler.getCall(4).args[0].length, 4); + done(); + }); + + it("has an i2cRead method that reads a register continuously", done => { + const handler = sandbox.spy(() => {}); + + board.i2cConfig(0); + board.i2cRead(0x53, 0xB2, 0x04, handler); + + for (let i = 0; i < 5; i++) { + transport.emit("data", [ + START_SYSEX, I2C_REPLY, 83, 0, 50, 1, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX + ]); + } + + assert.equal(handler.callCount, 5); + assert.equal(handler.getCall(0).args[0].length, 4); + assert.equal(handler.getCall(1).args[0].length, 4); + assert.equal(handler.getCall(2).args[0].length, 4); + assert.equal(handler.getCall(3).args[0].length, 4); + assert.equal(handler.getCall(4).args[0].length, 4); + done(); + }); + + + it("has an i2cRead method that reads continuously", done => { + const handler = sandbox.spy(() => {}); + + board.i2cConfig(0); + board.i2cRead(0x53, 0x04, handler); + + for (let i = 0; i < 5; i++) { + transport.emit("data", [ + START_SYSEX, I2C_REPLY, 83, 0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX + ]); + } + + assert.equal(handler.callCount, 5); + assert.equal(handler.getCall(0).args[0].length, 4); + assert.equal(handler.getCall(1).args[0].length, 4); + assert.equal(handler.getCall(2).args[0].length, 4); + assert.equal(handler.getCall(3).args[0].length, 4); + assert.equal(handler.getCall(4).args[0].length, 4); + done(); + }); + + it("has an i2cReadOnce method that reads a register once", done => { + const handler = sandbox.spy(() => {}); + + board.i2cConfig(0); + board.i2cReadOnce(0x53, 0xB2, 0x04, handler); + + // Emit data enough times to potentially break it. + for (let i = 0; i < 5; i++) { + transport.emit("data", [ + START_SYSEX, I2C_REPLY, 83, 0, 50, 1, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX + ]); + } + + assert.equal(handler.callCount, 1); + assert.equal(handler.getCall(0).args[0].length, 4); + done(); + }); + + it("has an i2cReadOnce method that reads a register once", done => { + const handler = sandbox.spy(() => {}); + + board.i2cConfig(0); + board.i2cReadOnce(0x53, 0xB2, 0x04, handler); + + // Emit data enough times to potentially break it. + for (let i = 0; i < 5; i++) { + transport.emit("data", [ + START_SYSEX, I2C_REPLY, 83, 0, 50, 1, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX + ]); + } + + assert.equal(handler.callCount, 1); + assert.equal(handler.getCall(0).args[0].length, 4); + done(); + }); + }); + + describe("Serial", () => { + + it("has a SERIAL_MODES property", done => { + + assert.deepEqual(board.SERIAL_MODES, { + CONTINUOUS_READ: 0x00, + STOP_READING: 0x01, + }); + done(); + }); + + it("has a SERIAL_PORT_IDs property", done => { + + assert.deepEqual(board.SERIAL_PORT_IDs, { + HW_SERIAL0: 0x00, + HW_SERIAL1: 0x01, + HW_SERIAL2: 0x02, + HW_SERIAL3: 0x03, + SW_SERIAL0: 0x08, + SW_SERIAL1: 0x09, + SW_SERIAL2: 0x10, + SW_SERIAL3: 0x11, + DEFAULT: 0x08, + }); + done(); + }); + + // SERIAL_PIN_TYPES is currently unused. + // it("has a SERIAL_PIN_TYPES property", done => { + + // assert.deepEqual(board.SERIAL_PORT_IDs, { + // RES_RX0: 0x00, + // RES_TX0: 0x01, + // RES_RX1: 0x02, + // RES_TX1: 0x03, + // RES_RX2: 0x04, + // RES_TX2: 0x05, + // RES_RX3: 0x06, + // RES_TX3: 0x07, + // }); + + // done(); + // }); + + it("can configure a software serial port", done => { + board.serialConfig({ + portId: 0x08, + baud: 9600, + rxPin: 10, + txPin: 11 + }); + assert.equal(transport.lastWrite[0], START_SYSEX); + assert.equal(transport.lastWrite[2], SERIAL_CONFIG | 0x08); + + assert.equal(transport.lastWrite[3], 9600 & 0x007F); + assert.equal(transport.lastWrite[4], (9600 >> 7) & 0x007F); + assert.equal(transport.lastWrite[5], (9600 >> 14) & 0x007F); + + assert.equal(transport.lastWrite[6], 10); + assert.equal(transport.lastWrite[7], 11); + + assert.equal(transport.lastWrite[8], END_SYSEX); + done(); + }); + + it("can configure a hardware serial port", done => { + board.serialConfig({ + portId: 0x01, + buad: 57600 + }); + assert.equal(transport.lastWrite[2], SERIAL_CONFIG | 0x01); + + assert.equal(transport.lastWrite[3], 57600 & 0x007F); + assert.equal(transport.lastWrite[4], (57600 >> 7) & 0x007F); + assert.equal(transport.lastWrite[5], (57600 >> 14) & 0x007F); + + assert.equal(transport.lastWrite[6], END_SYSEX); + done(); + }); + + it("throws an error if no serial port id is passed", done => { + assert.throws(() => { + board.serialConfig({ + buad: 57600 + }); + }); + done(); + }); + + it("throws an error if both RX and TX pins are not defined when using Software Serial", done => { + // throw error if both pins are not specified + assert.throws(() => { + board.serialConfig({ + portId: 8, + buad: 57600 + }); + }); + + // throw error if only one serial pin is specified + assert.throws(() => { + board.serialConfig({ + portId: 8, + buad: 57600, + txPin: 0 + }); + }); + done(); + }); + + it("can write a single byte to a serial port", done => { + board.serialWrite(0x08, [1]); + assert.equal(transport.lastWrite[2], SERIAL_WRITE | 0x08); + assert.equal(transport.lastWrite[3], 1 & 0x7F); + assert.equal(transport.lastWrite[4], (1 >> 7) & 0x7F); + assert.equal(transport.lastWrite[5], END_SYSEX); + done(); + }); + + it("can write a byte array to a serial port", done => { + board.serialWrite(0x08, [252, 253, 254]); + assert.equal(transport.lastWrite[2], SERIAL_WRITE | 0x08); + assert.equal(transport.lastWrite[3], 252 & 0x7F); + assert.equal(transport.lastWrite[4], (252 >> 7) & 0x7F); + assert.equal(transport.lastWrite[7], 254 & 0x7F); + assert.equal(transport.lastWrite[8], (254 >> 7) & 0x7F); + assert.equal(transport.lastWrite[9], END_SYSEX); + done(); + }); + + it("has a serialRead method that sets READ_CONTINUOUS mode", done => { + const handler = sandbox.spy(() => {}); + board.serialRead(0x08, handler); + + assert.equal(transport.lastWrite[2], SERIAL_READ | 0x08); + assert.equal(transport.lastWrite[3], 0); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("has a serialRead method that reads continuously", done => { + const inBytes = [ + 242 & 0x7F, + (242 >> 7) & 0x7F, + 243 & 0x7F, + (243 >> 7) & 0x7F, + 244 & 0x7F, + (244 >> 7) & 0x7F, + ]; + + const handler = sandbox.spy(() => {}); + board.serialRead(0x08, handler); + + for (let i = 0; i < 5; i++) { + transport.emit("data", [ + START_SYSEX, + SERIAL_MESSAGE, + SERIAL_REPLY | 0x08, + inBytes[0], + inBytes[1], + inBytes[2], + inBytes[3], + inBytes[4], + inBytes[5], + END_SYSEX + ]); + } + + assert.equal(handler.callCount, 5); + assert.equal(handler.getCall(0).args[0].length, 3); + assert.equal(handler.getCall(0).args[0][0], 242); + assert.equal(handler.getCall(0).args[0][2], 244); + assert.equal(handler.getCall(1).args[0].length, 3); + assert.equal(handler.getCall(2).args[0].length, 3); + assert.equal(handler.getCall(3).args[0].length, 3); + assert.equal(handler.getCall(4).args[0].length, 3); + done(); + }); + + it("serialRead accepts an optional maxBytesToRead parameter", done => { + const maxBytesToRead = 4; + const handler = sandbox.spy(() => {}); + board.serialRead(0x08, maxBytesToRead, handler); + + assert.equal(transport.lastWrite[4], 4); + assert.equal(transport.lastWrite[5], 0); + assert.equal(transport.lastWrite[6], END_SYSEX); + done(); + }); + + it("has a serialStop method that sets STOP_READING mode", done => { + board.serialStop(0x08); + assert.equal(transport.lastWrite[2], SERIAL_READ | 0x08); + assert.equal(transport.lastWrite[3], 1); + assert.equal(transport.lastWrite[4], END_SYSEX); + done(); + }); + + it("has a serialClose method", done => { + board.serialClose(0x09); + assert.equal(transport.lastWrite[2], SERIAL_CLOSE | 0x09); + done(); + }); + + it("has a serialFlush method", done => { + board.serialFlush(0x02); + assert.equal(transport.lastWrite[2], SERIAL_FLUSH | 0x02); + done(); + }); + + it("has a serialListen method that switches software serial port", done => { + const spy = sandbox.spy(transport, "write"); + board.serialListen(0x08); + assert.equal(transport.lastWrite[2], SERIAL_LISTEN | 0x08); + assert.equal(transport.lastWrite[3], END_SYSEX); + assert.equal(spy.callCount, 1); + spy.restore(); + done(); + }); + + it("must not send a SERIAL_LISTEN message for a hardware serial port", done => { + const spy = sandbox.spy(transport, "write"); + board.serialListen(0x01); + assert.equal(spy.callCount, 0); + spy.restore(); + done(); + }); + + }); + + describe("sysex: custom messages and response handlers", () => { + + it("must allow custom SYSEX_RESPONSE handlers", done => { + + assert.equal(Board.SYSEX_RESPONSE[NON_STANDARD_REPLY], undefined); + + Board.SYSEX_RESPONSE[NON_STANDARD_REPLY] = board => { + const payload = []; + const sub = board.buffer[2]; + + for (let i = 3, length = board.buffer.length - 1; i < length; i += 2) { + payload.push(board.buffer[i] | (board.buffer[i + 1] << 7)); + } + + board.emit(`non-standard-reply-${sub}`, payload); + }; + + // User code may add this emitter + board.on("non-standard-reply-4", payload => { + assert.deepEqual(payload, [0, 1, 2, 3, 4]); + done(); + }); + + // Emit mock data to trigger the NON_STANDARD_REPLY handler + transport.emit("data", [ + // SUB Data... + START_SYSEX, NON_STANDARD_REPLY, 0x04, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX + ]); + }); + + it("must provide a SAFE API to define custom SYSEX_RESPONSE handlers", done => { + + const incoming = [START_SYSEX, NON_STANDARD_REPLY, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX]; + + board.sysexResponse(NON_STANDARD_REPLY, data => { + // Data does NOT include: + // [0] START_SYSEX + // [1] NON_STANDARD_REPLY + // [n] END_SYSEX + + assert.deepEqual(data, [0, 0, 1, 0, 2, 0, 3, 0, 4, 0]); + assert.deepEqual(Board.decode(data), [0, 1, 2, 3, 4]); + + done(); + }); + + transport.emit("data", incoming); + }); + + it("SYSEX_RESPONSE handler context is board", done => { + + const incoming = [START_SYSEX, NON_STANDARD_REPLY, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, END_SYSEX]; + + board.sysexResponse(NON_STANDARD_REPLY, function(data) { + assert.equal(this, board); + done(); + }); + + transport.emit("data", incoming); + }); + + it("fail when overwriting SYSEX_RESPONSE command byte", done => { + Board.SYSEX_RESPONSE[0xFF] = () => {}; + + assert.throws(() => { + board.sysexResponse(0xFF); + }); + done(); + }); + + it("fail when calling sysexCommand with empty array", done => { + assert.throws(() => { + board.sysexCommand(); + }); + assert.throws(() => { + board.sysexCommand([]); + }); + done(); + }); + + it("must allow sending arbitrary sysex commands", done => { + + const write = sandbox.stub(transport, "write"); + + board.sysexCommand([ + I2C_REQUEST, 0x68, 0 << 3, 1 & 0x7F, (1 >> 7) & 0x7F, + ]); + + const sent = write.lastCall.args[0]; + + assert.equal(sent[0], START_SYSEX); + assert.equal(sent[1], I2C_REQUEST); + assert.equal(sent[2], 0x68); + assert.equal(sent[3], 0 << 3); + assert.equal(sent[4], 1 & 0x7F); + assert.equal(sent[5], (1 >> 7) & 0x7F); + assert.equal(sent[6], END_SYSEX); + done(); + }); + + it("allows clearing handler for SYSEX_RESPONSE command byte", done => { + Board.SYSEX_RESPONSE[0xFF] = () => {}; + + board.clearSysexResponse(0xFF); + + assert.doesNotThrow(() => { + board.sysexResponse(0xFF); + }); + done(); + }); + }); + + describe("parser", () => { + + beforeEach(() => { + board.buffer = []; + }); + + it("must parse a command from the beginning of a data packet", done => { + const spy = sandbox.spy(); + const incoming = [REPORT_VERSION, 0x02, 0x03]; + board.versionReceived = false; + board.on("reportversion", spy); + transport.emit("data", incoming); + assert.equal(spy.callCount, 1); + done(); + }); + + it("must parse a command from the middle of a data packet", done => { + const spy = sandbox.spy(); + // includes: analog input, report version, query firmware (incomplete) + const incoming = [ + 0xe0, 0x07, 0x07, 0xf9, 0x02, 0x05, 0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, 0x00, 0x61, + 0x00, 0x6e, 0x00, 0x64, 0x00 + ]; + board.versionReceived = false; + board.on("reportversion", spy); + transport.emit("data", incoming); + assert.equal(spy.callCount, 1); + done(); + }); + + it("must not emit command events until REPORT_VERSION is received", done => { + const spyAnalog = sandbox.spy(); + const spyVersion = sandbox.spy(); + // includes: analog input, report version, query firmware (incomplete) and junk + // between analog input and report version + const incoming = [ + 0xe0, 0x00, 0x71, 0xf9, 0x02, 0x05, 0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, + 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00 + ]; + board.versionReceived = false; + board.on("analog-read-0", spyAnalog); + board.on("reportversion", spyVersion); + transport.emit("data", incoming); + assert.equal(spyAnalog.callCount, 0); + assert.equal(spyVersion.callCount, 1); + done(); + }); + + it("must parse multiple commands from a single packet", done => { + const spyAnalog = sandbox.spy(); + const spyVersion = sandbox.spy(); + // includes: report version, analog input, query firmware (incomplete) and junk + // between analog input and report version + const incoming = [ + 0xf9, 0x02, 0x05, 0xe0, 0x00, 0x71, 0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, + 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00 + ]; + board.versionReceived = false; + board.on("reportversion", spyVersion); + board.on("analog-read-0", spyAnalog); + transport.emit("data", incoming); + assert.equal(spyVersion.callCount, 1); + assert.equal(spyAnalog.callCount, 1); + done(); + }); + + it("must parse a complete sysex command after an incomplete sysex command", done => { + const spy = sandbox.spy(); + // includes: query firmware (incomplete sysex), pin state response (pin 2) + const incoming = [ + 0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, + 0xf0, 0x6e, 0x02, 0x01, 0x01, 0xf7 + ]; + board.versionReceived = true; + board.on("pin-state-2", spy); + transport.emit("data", incoming); + assert.equal(spy.callCount, 1); + done(); + }); + + it("must parse a non-sysex command after an incomplete sysex command", done => { + const spy = sandbox.spy(); + // includes: query firmware (incomplete sysex), analog input + const incoming = [ + 0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, + 0xe0, 0x00, 0x71 + ]; + board.versionReceived = true; + board.on("analog-read-0", spy); + transport.emit("data", incoming); + assert.equal(spy.callCount, 1); + done(); + }); + + it("must parse a command spread across multiple data packets", done => { + const spy = sandbox.spy(); + // query firmware split across 3 packets with first packet preceeded by junk + const incoming1 = [0x07, 0x04, 240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0]; + const incoming2 = [97, 0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0]; + const incoming3 = [97, 0, 116, 0, 97, 0, 247]; + + board.versionReceived = true; + board.on("queryfirmware", spy); + transport.emit("data", incoming1); + transport.emit("data", incoming2); + transport.emit("data", incoming3); + assert.equal(spy.callCount, 1); + done(); + }); + + it("must parse a command spread across multiple single byte transfers", done => { + const spy = sandbox.spy(); + const incoming = [REPORT_VERSION, 0x02, 0x03]; + + board.versionReceived = true; + board.on("reportversion", spy); + for (let i = 0; i < incoming.length; i++) { + transport.emit("data", [incoming[i]]); + } + assert.equal(spy.callCount, 1); + done(); + }); + }); +}); + +describe("Numeric encoding/decoding and formatting", () => { + + it("must encode 32 bit signed integers", done => { + assert.deepEqual(Board.test.encode32BitSignedInteger(5786), [26, 45, 0, 0, 0]); + done(); + }); + + it("must encode 32 bit signed integers when they are negative", done => { + assert.deepEqual(Board.test.encode32BitSignedInteger(-5786), [26, 45, 0, 0, 8]); + done(); + }); + + it("must decode 32 bit signed integers", done => { + assert.equal(Board.test.decode32BitSignedInteger([26, 45, 0, 0, 0]), 5786); + done(); + }); + + it("must decode 32 bit signed integers when they are negative", done => { + assert.equal(Board.test.decode32BitSignedInteger([26, 45, 0, 0, 8]), -5786); + done(); + }); + + it("must encode custom floats", done => { + assert.deepEqual(Board.test.encodeCustomFloat(123.456), [0, 45, 75, 28]); + done(); + }); + + it("must encode custom floats (even when they are integers)", done => { + assert.deepEqual(Board.test.encodeCustomFloat(100), [1, 0, 0, 52]); + done(); + }); + + it("must encode custom floats when they are negative", done => { + assert.deepEqual(Board.test.encodeCustomFloat(-7321.783), [54, 113, 62, 99]); + done(); + }); + + it("must encode custom floats when they are less than 1", done => { + assert.deepEqual(Board.test.encodeCustomFloat(0.000325), [79, 46, 70, 5]); + done(); + }); + + it("must decode custom floats", done => { + assert.equal(Board.test.decodeCustomFloat([110, 92, 44, 32]), 732.782); + done(); + }); + + it("must decode custom floats when they are negative", done => { + assert.equal(Board.test.decodeCustomFloat([110, 92, 44, 96]), -732.782); + done(); + }); +}); + +describe("Board.encode/Board.decode", () => { + + describe("Board.encode", () => { + it("must encode arbitrary data", done => { + assert.deepEqual(Board.encode([0, 1, 2, 3, 4]), [0, 0, 1, 0, 2, 0, 3, 0, 4, 0]); + done(); + }); + it("returns a fresh array", done => { + const data = []; + assert.notEqual(Board.encode(data), data); + done(); + }); + }); + + describe("Board.decode", () => { + it("must decode arbitrary data", done => { + assert.deepEqual(Board.decode([0, 0, 1, 0, 2, 0, 3, 0, 4, 0]), [0, 1, 2, 3, 4]); + done(); + }); + + it("must fail to decode uneven data", done => { + assert.throws(() => { + Board.decode([0, 0, 1, 0, 2, 0, 3, 0, 4]); + }); + done(); + }); + + it("returns a fresh array", done => { + const data = []; + assert.notEqual(Board.decode(data), data); + done(); + }); + }); +}); diff --git a/server/esp32_firmata/firmata/test/unit/fixtures/entry.js b/server/esp32_firmata/firmata/test/unit/fixtures/entry.js new file mode 100644 index 0000000..fdc391d --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/fixtures/entry.js @@ -0,0 +1 @@ +var Firmata = require("../../../packages/firmata.js/lib/firmata"); diff --git a/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-adc.js b/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-adc.js new file mode 100644 index 0000000..f78eb67 --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-adc.js @@ -0,0 +1,5 @@ +module.exports = [0x42, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x3f, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x3f, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x40, 0x02, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x44, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x3f, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x43, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x3f, 0x02, 0xe0, 0x40, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x42, 0x02, 0xe0, 0x41, 0x02, 0xe0, 0x40, 0x02, + +/* REPORT_VERSION start */ 0xf9, 0x02, 0x05, /* REPORT_VERSION end */ + +0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x46, 0x00, 0x69, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x61, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0xf7, 0xf0, 0x6c, 0x7f, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x06, 0x01, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x06, 0x01, 0x7f, 0xf7, 0xf0, 0x6a, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7, 0xe0, 0x7f, 0x03]; diff --git a/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-i2c.js b/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-i2c.js new file mode 100644 index 0000000..d644e8c --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-i2c.js @@ -0,0 +1,6 @@ +module.exports = [0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0x00, 0x40, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x66, 0x00, 0x05, 0x00, 0x39, 0x01, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x40, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x40, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0x00, 0x40, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x66, 0x00, 0x05, 0x00, 0x39, 0x01, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x40, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x40, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0x00, 0x40, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x66, 0x00, 0x05, 0x00, 0x39, 0x01, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x40, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x40, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5e, 0x00, 0x05, 0x00, 0x48, 0x01, 0x05, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x3f, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x42, 0x01, 0xf7, + +/* REPORT_VERSION start */ 0xf9, 0x02, 0x05, /* REPORT_VERSION end */ + +0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x46, 0x00, 0x69, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x61, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0xf7, 0xf0, 0x6c, 0x7f, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x06, 0x01, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x06, 0x01, 0x7f, 0xf7, 0xf0, 0x6a, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7, 0xf0, 0x77, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5d, 0x00, 0x05, 0x00, 0x40, 0x01, 0x05, 0x00, 0x41, 0x01, 0x05, 0x00, 0x41, 0x01, 0x04, 0x00, 0x16, 0x00, 0x05, 0x00, 0x40, 0x01, 0xf7, +]; diff --git a/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-serial.js b/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-serial.js new file mode 100644 index 0000000..8f2fa20 --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/fixtures/unexpected-data-serial.js @@ -0,0 +1,6 @@ +module.exports = [0x00, 0x2c, 0x00, 0x30, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x30, 0x00, 0x30, 0x00, 0x2c, 0x00, 0x2a, 0x00, 0x37, 0x00, 0x31, 0x00, 0x0d, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x0a, 0x00, 0x24, 0x00, 0x47, 0x00, 0x50, 0x00, 0x52, 0x00, 0x4d, 0x00, 0x43, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x2c, 0x00, 0x2c, 0x00, 0x56, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x2c, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x2c, 0x00, 0x4e, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x33, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x24, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x47, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x50, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x47, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x47, 0x00, 0xf7, 0xf0, 0x60, 0x48, 0x41, 0xfe, 0xf0, 0x79, 0x02, 0x05, 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x46, 0x00, 0x69, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x61, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0xf7, + +/* REPORT_VERSION start */ 0xf9, 0x02, 0x05, /* REPORT_VERSION end */ + + +0x05, 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x46, 0x00, 0x69, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x61, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0xf7, 0xf0, 0x6c, 0x7f, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x03, 0x08, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x06, 0x01, 0x7f, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x02, 0x0a, 0x04, 0x0e, 0x06, 0x01, 0x7f, 0xf7, 0xf0, 0x6a, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7, 0xf0, 0x60, 0x48, 0x19, 0x01, 0xf7]; diff --git a/server/esp32_firmata/firmata/test/unit/onewireutils.test.js b/server/esp32_firmata/firmata/test/unit/onewireutils.test.js new file mode 100644 index 0000000..485e278 --- /dev/null +++ b/server/esp32_firmata/firmata/test/unit/onewireutils.test.js @@ -0,0 +1,69 @@ +require("../common/bootstrap"); + +const sandbox = sinon.createSandbox(); + +describe("OneWire.crc8/OneWire.readDevices", () => { + + afterEach(() => { + sandbox.restore(); + }); + + describe("OneWire.crc8", () => { + it("must CRC check data read from firmata", done => { + const input = [0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D]; + const crcByte = OneWire.crc8(input.slice(0, input.length - 1)); + + assert.equal(crcByte, input[input.length - 1]); + + done(); + }); + it("must return an invalid CRC check for corrupt data", done => { + const input = [0x28, 0xDB, 0xEF, 0x22, 0x05, 0x00, 0x00, 0x5D]; + const crcByte = OneWire.crc8(input.slice(0, input.length - 1)); + + crcByte.should.not.equal(input[input.length - 1]); + + done(); + }); + }); + describe("OneWire.readDevices", () => { + it("must read device identifier", done => { + const input = Encoder7Bit.to7BitArray([0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D]); + const devices = OneWire.readDevices(input); + + assert.equal(devices.length, 1); + + done(); + }); + it("must read device identifiers", done => { + const input = Encoder7Bit.to7BitArray([0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D, 0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D]); + const devices = OneWire.readDevices(input); + + assert.equal(devices.length, 2); + + done(); + }); + it("must read only complete device identifiers", done => { + const input = Encoder7Bit.to7BitArray([0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D, 0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D, 0x00, 0x01, 0x02]); + const devices = OneWire.readDevices(input); + + assert.equal(devices.length, 2); + + done(); + }); + + it("detects and logs invalid ROM", done => { + + sandbox.stub(console, "error").callsFake(() => {}); + sandbox.stub(OneWire, "crc8").callsFake(() => null); + + const input = Encoder7Bit.to7BitArray([0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D, 0x28, 0xDB, 0xEF, 0x21, 0x05, 0x00, 0x00, 0x5D, 0x00, 0x01, 0x02]); + const devices = OneWire.readDevices(input); + + assert.equal(devices.length, 2); + assert.equal(console.error.lastCall.args[0], "ROM invalid!"); + + done(); + }); + }); +}); diff --git a/server/src/board.js b/server/src/board.js index e968a55..16c48f2 100644 --- a/server/src/board.js +++ b/server/src/board.js @@ -1,5 +1,6 @@ -const Firmata = require('firmata'); +const Arduino = require('firmata'); const Controller = require('node-pid-controller'); +const Esp32 = require('./esp32-io'); module.exports = class Board { /** @@ -18,7 +19,7 @@ module.exports = class Board { this.openLoopVoltage = 0; this.therms = []; this.isControlling = false; - this.board = new Firmata(boardPath); + this.board = new Arduino(boardPath); this.pidConsts = pidConsts || { pb: 30, // proportional band width ti: 1.27, // integral time diff --git a/server/src/esp32-io.js b/server/src/esp32-io.js new file mode 100644 index 0000000..dc0df30 --- /dev/null +++ b/server/src/esp32-io.js @@ -0,0 +1,113 @@ +const Firmata = require('../esp32_firmata/firmata/lib/firmata'); + +const DIGITAL_WRITE = 0x01; +const ADC_READ = 0x02; +const DIGITAL_READ = 0x03; +const PWM_OUTPUT = 0x04; +const PWM_CHANNEL = 0; + +/** + * A Class to handle with the ESP32 inputs and outputs + */ +class Esp32IO { + /** + * The default constructor of the Esp32IO class + * @param {string} usbPath The usb path for the board + * @param {Object} config The esp32 configuration + */ + constructor(usbPath, { pwmFreq = 10, pwmResolution = 8 }) { + this.firmata = new Firmata(usbPath); + this.HIGH = 1; + this.LOW = 0; + this.pwmFreq = pwmFreq; + this.pwmResolution = pwmResolution; + } + /** + * Execute something after the board is ready + * @param {Function} callback the callback to be called when the board is ready + */ + onReady(callback) { + if (!callback) { + throw new Error('Missing callback'); + } + this.firmata.on('ready', callback); + } + /** + Asks the ESP32 to read analog data. + @param {number} pin The pin to read analog data + @param {Function} callback A function to call when we have the analog data. + */ + analogRead(pin, callback) { + if (!callback) { + throw new Error('Missing callback'); + } + this.firmata.sysexCommand([ADC_READ, pin]); + this.firmata.sysexResponse(ADC_READ, (data) => { + const rawValue = Firmata.decode(data); + callback(rawValue.reduce((acc, cur) => acc + cur)); + }); + } + /** + Write a PWM value Asks the ESP32 to write an analog message. + @param {number} pin The pin to write analog data to. + @param {number} pwmValue The data to write to the pin between 0 and 255 + */ + analogWrite(pin, pwmValue) { + const maxValue = 2 ** this.pwmResolution - 1; + if (pwmValue > maxValue) { + throw new Error('Value to high for the pwm resolution'); + } + const slicedPwmValue = pwmValue > 255 ? slicePwmValue(pwmValue) : [pwmValue]; + this.firmata.sysexCommand([ + PWM_OUTPUT, + pin, + PWM_CHANNEL, + this.pwmFreq, + this.pwmResolution, + ...slicedPwmValue, + ]); + } + /** + Asks the ESP32 to read digital data. + @param {number} pin The pin to read data from + @param {Function} callback The function to call when data has been received + */ + digitalRead(pin, callback) { + if (callback) { + throw new Error('Missing callback'); + } + this.firmata.sysexCommand([DIGITAL_READ, pin]); + this.firmata.sysexResponse(DIGITAL_READ, (data) => { + callback(Firmata.decode(data)[0]); + }); + } + /** + Asks the Esp32 to write a value to a digital pin + @param {number} pin The pin you want to write a value to. + @param {number} value The value you want to write. Must be 0 or 1 + */ + digitalWrite(pin, value) { + if (value !== 0 && value !== 1) { + throw new Error('Invalid digital value. Must be 0 or 1'); + } + this.firmata.sysexCommand([DIGITAL_WRITE, pin, value]); //command, argc, argv = data + } +} +/** + * Slice the pwm value in 8 bits parts + * @param {number} pwmValue The pwmValue to be spliced + * @returns {number[]} An array containing the pwmValue + * spliced in 8 bit values + */ +function slicePwmValue(pwmValue) { + const quotient = Math.trunc(pwmValue / 255); + const rest = pwmValue % 255; + const splicedPwmValue = []; + for (let i = 0; i < quotient; i++) { + splicedPwmValue.push(255); + } + splicedPwmValue.push(rest); + return splicedPwmValue; +} + +module.exports = Esp32IO;