Skip to content

Complete base API #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions Firmata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ systemCallbackFunction FirmataClass::currentSystemResetCallback = (systemCallbac
*/
void FirmataClass::sendValueAsTwo7bitBytes(int value)
{
marshaller.sendValueAsTwo7bitBytes(value);
marshaller.transformByteStreamToMessageBytes(sizeof(value), reinterpret_cast<uint8_t *>(&value), sizeof(value));
}

/**
Expand Down Expand Up @@ -146,9 +146,7 @@ void FirmataClass::begin(Stream &s)
*/
void FirmataClass::printVersion(void)
{
FirmataStream->write(REPORT_VERSION);
FirmataStream->write(FIRMATA_PROTOCOL_MAJOR_VERSION);
FirmataStream->write(FIRMATA_PROTOCOL_MINOR_VERSION);
marshaller.sendVersion(FIRMATA_PROTOCOL_MAJOR_VERSION, FIRMATA_PROTOCOL_MINOR_VERSION);
}

/**
Expand Down Expand Up @@ -188,17 +186,8 @@ void FirmataClass::disableBlinkVersion()
*/
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) {
marshaller.sendValueAsTwo7bitBytes(firmwareVersionVector[i]);
}
endSysex();
marshaller.sendFirmwareVersion(static_cast<uint8_t>(firmwareVersionVector[0]), static_cast<uint8_t>(firmwareVersionVector[1]), (firmwareVersionCount - 2), reinterpret_cast<uint8_t *>(&firmwareVersionVector[2]));
}
}

Expand Down
2 changes: 1 addition & 1 deletion Firmata.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class FirmataClass

/* private methods ------------------------------ */
void strobeBlinkPin(byte pin, int count, int onInterval, int offInterval);
friend void FirmataMarshaller::sendValueAsTwo7bitBytes(uint16_t value) const;
friend void FirmataMarshaller::transformByteStreamToMessageBytes (size_t bytec, uint8_t * bytev, size_t max_bytes = 0) const;

/* callback functions */
static callbackFunction currentAnalogCallback;
Expand Down
172 changes: 158 additions & 14 deletions FirmataMarshaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,57 @@ const
}

/**
* Split a 16-bit integer into two 7-bit values and write each value.
* @param value The 16-bit value to be split and written separately.
* An alternative to the normal analog message, this extended version allows addressing beyond
* pin 15 and supports sending analog values with any number of bits.
* @param pin The analog pin to which the value is sent.
* @param bytec The size of the storage for the analog value
* @param bytev The pointer to the location of the analog value
*/
void FirmataMarshaller::sendValueAsTwo7bitBytes(uint16_t value)
void FirmataMarshaller::sendExtendedAnalog(uint8_t pin, size_t bytec, uint8_t * bytev)
const
{
FirmataStream->write(value & 0x7F); // LSB
FirmataStream->write(value >> 7 & 0x7F); // MSB
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(START_SYSEX);
FirmataStream->write(EXTENDED_ANALOG);
FirmataStream->write(pin);
transformByteStreamToMessageBytes(bytec, bytev, bytec);
FirmataStream->write(END_SYSEX);
}

/**
* Transform 8-bit stream into 7-bit message
* @param bytec The number of data bytes in the message.
* @param bytev A pointer to the array of data bytes to send in the message.
* @param max_bytes Force message to be n bytes, regardless of data bits.
*/
void FirmataMarshaller::transformByteStreamToMessageBytes (size_t bytec, uint8_t * bytev, size_t max_bytes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a similar Encoder7Bit class in ConfigurableFirmata that both reads and writes: https://github.com/firmata/ConfigurableFirmata/blob/master/src/Encoder7Bit.cpp

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and example use to read and write

Copy link
Contributor Author

@zfields zfields Feb 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I wish I had seen it before! ;-) However, just looking at it (and mulling it over) briefly, it looks like another dependency that will need to be managed.

Since this is a self-contained, private function, we will be able to refactor it at any time, without impacting the external API. For now, I would like to move forward using the current implementation, at least until ConfigurableFirmata becomes a little more mainstream (or Encoder7Bit is refactored to consume/extend the FirmataMarshaller API) and gets pulled under the firmata namespace.

const
{
static const size_t transmit_bits = 7;
static const uint8_t transmit_mask = ((1 << transmit_bits) - 1);

size_t bytes_sent = 0;
size_t outstanding_bits = 0;
uint8_t outstanding_bit_cache = *bytev;

if ( !max_bytes ) { max_bytes = static_cast<size_t>(-1); }
for (size_t i = 0 ; (i < bytec) && (bytes_sent < max_bytes) ; ++i) {
uint8_t transmit_byte = (outstanding_bit_cache|(bytev[i] << outstanding_bits));
FirmataStream->write(transmit_mask & transmit_byte);
++bytes_sent;
outstanding_bit_cache = (bytev[i] >> (transmit_bits - outstanding_bits));
outstanding_bits = (outstanding_bits + (8 - transmit_bits));
for ( ; (outstanding_bits >= transmit_bits) && (bytes_sent < max_bytes) ; ) {
transmit_byte = outstanding_bit_cache;
FirmataStream->write(transmit_mask & transmit_byte);
++bytes_sent;
outstanding_bit_cache >>= transmit_bits;
outstanding_bits -= transmit_bits;
}
}
if ( outstanding_bits && (bytes_sent < max_bytes) ) {
FirmataStream->write(static_cast<uint8_t>((1 << outstanding_bits) - 1) & outstanding_bit_cache);
}
}

//******************************************************************************
Expand Down Expand Up @@ -116,6 +159,28 @@ void FirmataMarshaller::end(void)
//* Output Stream Handling
//******************************************************************************

/**
* Query the target's firmware name and version
*/
void FirmataMarshaller::queryFirmwareVersion(void)
const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(START_SYSEX);
FirmataStream->write(REPORT_FIRMWARE);
FirmataStream->write(END_SYSEX);
}

/**
* Query the target's Firmata protocol version
*/
void FirmataMarshaller::queryVersion(void)
const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this back in with the updated max. I assume it's 32 bits. However different firmata client libraries may interpret this in different ways (I think some only expect a max of 16 bits), but that shouldn't break anything unless a library looks for a specific number of bytes rather than accepting all bytes up to END_SYSEX (but that would be the fault of the client library).

Copy link
Contributor Author

@zfields zfields Feb 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, in fact, still 14-bits. I removed it when I thought it would be changing. I have replaced the note. I amended the last commit because it was aptly named.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's limited to 14 bits when using ANALOG_MESSAGE, but sendAnalog now calls sendExtendedAnalog so larger values should be allowed. No cap is defined in the protocol documentation but I think 32-bit is probably reasonable (although not sure I've ever seen hardware that supports that resolution).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's the equivalent in firmata.js for example: https://github.com/firmata/firmata.js/blob/master/lib/firmata.js#L776-L806

{
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(REPORT_VERSION);
}

/**
* Halt the stream of analog readings from the Firmata host application. The range of pins is
* limited to [0..15] when using the REPORT_ANALOG. The maximum result of the REPORT_ANALOG is limited to 14 bits
Expand Down Expand Up @@ -173,17 +238,20 @@ const
* 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 pin The analog pin to which the value is sent.
* @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).
* @note The maximum value is 14-bits (16384).
*/
void FirmataMarshaller::sendAnalog(uint8_t pin, uint16_t value)
const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
// pin can only be 0-15, so chop higher bits
FirmataStream->write(ANALOG_MESSAGE | (pin & 0xF));
sendValueAsTwo7bitBytes(value);
if ( (0xF >= pin) && (0x3FFF >= value) ) {
FirmataStream->write(ANALOG_MESSAGE|pin);
transformByteStreamToMessageBytes(sizeof(value), reinterpret_cast<uint8_t *>(&value), sizeof(value));
} else {
sendExtendedAnalog(pin, sizeof(value), reinterpret_cast<uint8_t *>(&value));
}
}

/**
Expand Down Expand Up @@ -236,8 +304,45 @@ const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(DIGITAL_MESSAGE | (portNumber & 0xF));
FirmataStream->write((uint8_t)portData % 128); // Tx bits 0-6 (protocol v1 and higher)
FirmataStream->write(portData >> 7); // Tx bits 7-13 (bit 7 only for protocol v2 and higher)
// Tx bits 0-6 (protocol v1 and higher)
// Tx bits 7-13 (bit 7 only for protocol v2 and higher)
transformByteStreamToMessageBytes(sizeof(portData), reinterpret_cast<uint8_t *>(&portData), sizeof(portData));
}

/**
* Sends the firmware name and version to the Firmata host application.
* @param major The major verison number
* @param minor The minor version number
* @param bytec The length of the firmware name
* @param bytev The firmware name array
*/
void FirmataMarshaller::sendFirmwareVersion(uint8_t major, uint8_t minor, size_t bytec, uint8_t *bytev)
const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
size_t i;
FirmataStream->write(START_SYSEX);
FirmataStream->write(REPORT_FIRMWARE);
FirmataStream->write(major);
FirmataStream->write(minor);
for (i = 0; i < bytec; ++i) {
transformByteStreamToMessageBytes(sizeof(bytev[i]), reinterpret_cast<uint8_t *>(&bytev[i]), sizeof(bytev[i]));
}
FirmataStream->write(END_SYSEX);
}

/**
* Send the Firmata protocol version to the Firmata host application.
* @param major The major verison number
* @param minor The minor version number
*/
void FirmataMarshaller::sendVersion(uint8_t major, uint8_t minor)
const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(REPORT_VERSION);
FirmataStream->write(major);
FirmataStream->write(minor);
}

/**
Expand All @@ -256,6 +361,23 @@ const
FirmataStream->write(config);
}

/**
* Send a pin state query to the Firmata host application. The resulting sysex message will have
* a PIN_STATE_RESPONSE command byte, followed by the pin number, the pin mode and a stream of
* bits to indicate any *data* written to the pin (pin state).
* @param pin The pin to query
* @note The pin state is any data written to the pin (i.e. pin state != pin value)
*/
void FirmataMarshaller::sendPinStateQuery(uint8_t pin)
const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(START_SYSEX);
FirmataStream->write(PIN_STATE_QUERY);
FirmataStream->write(pin);
FirmataStream->write(END_SYSEX);
}

/**
* 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).
Expand All @@ -271,7 +393,7 @@ const
FirmataStream->write(START_SYSEX);
FirmataStream->write(command);
for (i = 0; i < bytec; ++i) {
sendValueAsTwo7bitBytes(bytev[i]);
transformByteStreamToMessageBytes(sizeof(bytev[i]), reinterpret_cast<uint8_t *>(&bytev[i]), sizeof(bytev[i]));
}
FirmataStream->write(END_SYSEX);
}
Expand All @@ -283,5 +405,27 @@ const
void FirmataMarshaller::sendString(const char *string)
const
{
sendSysex(STRING_DATA, strlen(string), (uint8_t *)string);
sendSysex(STRING_DATA, strlen(string), reinterpret_cast<uint8_t *>(const_cast<char *>(string)));
}

/**
* The sampling interval sets how often analog data and i2c data is reported to the client.
* @param interval_ms The interval (in milliseconds) at which to sample
* @note The default sampling interval is 19ms
*/
void FirmataMarshaller::setSamplingInterval(uint16_t interval_ms)
const
{
sendSysex(SAMPLING_INTERVAL, sizeof(interval_ms), reinterpret_cast<uint8_t *>(&interval_ms));
}

/**
* Perform a software reset on the target. For example, StandardFirmata.ino will initialize
* everything to a known state and reset the parsing buffer.
*/
void FirmataMarshaller::systemReset(void)
const
{
if ( (Stream *)NULL == FirmataStream ) { return; }
FirmataStream->write(SYSTEM_RESET);
}
10 changes: 9 additions & 1 deletion FirmataMarshaller.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class FirmataMarshaller
void end();

/* serial send handling */
void queryFirmwareVersion(void) const;
void queryVersion(void) const;
void reportAnalogDisable(uint8_t pin) const;
void reportAnalogEnable(uint8_t pin) const;
void reportDigitalPortDisable(uint8_t portNumber) const;
Expand All @@ -48,15 +50,21 @@ class FirmataMarshaller
void sendCapabilityQuery(void) const;
void sendDigital(uint8_t pin, uint8_t value) const;
void sendDigitalPort(uint8_t portNumber, uint16_t portData) const;
void sendFirmwareVersion(uint8_t major, uint8_t minor, size_t bytec, uint8_t *bytev) const;
void sendVersion(uint8_t major, uint8_t minor) const;
void sendPinMode(uint8_t pin, uint8_t config) const;
void sendPinStateQuery(uint8_t pin) const;
void sendString(const char *string) const;
void sendSysex(uint8_t command, size_t bytec, uint8_t *bytev) const;
void setSamplingInterval(uint16_t interval_ms) const;
void systemReset(void) const;

private:
/* utility methods */
void reportAnalog(uint8_t pin, bool stream_enable) const;
void reportDigitalPort(uint8_t portNumber, bool stream_enable) const;
void sendValueAsTwo7bitBytes(uint16_t value) const;
void sendExtendedAnalog(uint8_t pin, size_t bytec, uint8_t * bytev) const;
void transformByteStreamToMessageBytes (size_t bytec, uint8_t * bytev, size_t max_bytes = 0) const;

Stream * FirmataStream;
};
Expand Down