firmata/protocol

provide a query to report pin values in bulk

Opened this issue · 19 comments

There already exists the Pin State Query which reports the output state (which is also the value) of a specified pin. There is also a use case for querying input values - especially when using non-serial transports. The Pin State Query is also inefficient when reporting several pins since the query is on a per pin basis.

The following proposal adds the ability to query the values of a range of pins including all pins, all input, all output, all digital input, all analog input, all digital output and all analog/pwm output.

Reporting of input pins would simply report the input pin value via the existing DIGITAL_MESSAGE or ANALOG_MESSAGE commands immediately upon receipt of the query. This is already done per pin (or port) when analog or digital pin reporting is enabled, but this proposal extends the ability to report at any time the user requests it - especially useful in the event that the transport connection was reestablished.

Reporting of output pins could use the Pin State Query Response, iterating over the output pins and sending the response for each pin.

0  START_SYSEX        (0xF0)
1  PIN_VALUE_QUERY    (0x68)
2  type                0 = all pins, 1 = all input, 2 = digital input, 3 = analog input,
                       4 = all output, 5 = digital output, 6 = analog output / pwm
3  END_SYSEX

Some additional types could be pin (input & output), pin_input, pin_output and then the following byte would be the pin number. Not sure if this would actually be useful, but it could be added later if a use case arises.

I support this proposal. The type parameter give the host all necessary control depending on the application. The pin type should be included from the beginning.

0  START_SYSEX        (0xF0)
1  PIN_VALUE_QUERY    (0x68)
2  type                0 = all pins (input & output),
                       1 = all input, 2 = digital input, 3 = analog input,
                       4 = all output, 5 = digital output, 6 = analog output / pwm
                       7 = pin (input & output), 8 = pin input, 9 = pin output
3  pin                (0-127) // only use if type > 6
4  END_SYSEX

What is the difference between pin X, pin input X and pin output X? Will there be no reply if X is configured as output and the query is for pin input?

Good point. pin alone is probably sufficient then. There are also cases when a pin is neither configured as input or output (I2C for example) and in that case if the user submitted a query for that pin, nothing would be returned.

0  START_SYSEX        (0xF0)
1  PIN_VALUE_QUERY    (0x68)
2  type                0 = all pins (input & output),
                       1 = all input, 2 = digital input, 3 = analog input,
                       4 = all output, 5 = digital output, 6 = analog output / pwm,
                       7 = pin
3  pin                (0-127) // only use if type > 6
4  END_SYSEX

Here are some scenarios that I think are obvious and that I've already answered and understood, but I would appreciate your confirmation :)

When I send...

  • [START_SYSEX, PIN_VALUE_QUERY, 0b10000000, 2, END_SYSEX]... I will receive a DIGITAL_MESSAGE response with the state of port 0
  • [START_SYSEX, PIN_VALUE_QUERY, 0b00000100, END_SYSEX]... I will receive x DIGITAL_MESSAGE responses, where n is the number of ports that contain pins of which I've previously sent [PIN_MODE, p, 0x00]
  • [START_SYSEX, PIN_VALUE_QUERY, 0b00001000, END_SYSEX]... I will receive n ANALOG_MESSAGE responses, where n is the number of A* input pins of which I've previously sent [PIN_MODE, p, 0x02]

If all of those are true statements, then I feel confident in my understanding of the proposal.

With regard to 6 = analog output / pwm,, this is entirely new, so there is no existing response type for it. Reusing ANALOG_MESSAGE (which I'm not recommending), then you will have two value ranges: 10-bit and 8-bit (eg. on Arduino UNO, that will likely varying further in future). I'm not sure what this means or if it's even important, I'm just thinking out loud

My first impression of the proposal was that the "pin" parameter is always part of the message but zero is not an invalid pin number on all platforms so it cannot be used as a wildcard. Either the pin parameter is defined as optional or a value like 0x7F is defined as wildcard for "all pins".

With [START_SYSEX, PIN_VALUE_QUERY, type=2, pin=7, END_SYSEX] you should get a DIGITAL_MESSAGE response with the state of port 0 if pin 7 is configured as digital input.

Looking at the specs I think using ANALOG_MESSAGE to report type 6 is not possible because the pin range is limited to 16. This is enough for analog inputs but there is no room for mapping additional analog/pwm inputs (at least on some boards). For type 6 the pin state response 0x6E could be used and it can handle any kind of pwm resolution because of its extensible definition.

That's correct, pin state response (0x6E) would be used to report analog out / pwm value. It would also be used to report the digital out value since you would not want to receive a DIGITAL_MESSAGE on the host for an output pin.

I see no conflict using DIGITAL_MESSAGE to report digital output pins. The message is port oriented anyway and a port may contain an arbitrary mix of inputs and outputs pins. Using this message would reduce the overhead in transmission and decoding. Even existing host applications know which pin of a port is input or output. Existing host code for updating digital inputs could be reused for the outputs.

W/r to my notes re: analog/pwm output values—I completely forgot that 0x6E messages would include that information—sorry for the confusing comment.

There is probably no conflict as long as the host is properly filtering the port values. I'm just saying the pin state query response is already set up to report the digital output pin values and if a host cares to know this value for an individual pin it already has a mechanism to do so rather than having to add new logic to try to get that value from an incoming DIGITAL_MESSAGE which is currently only used in host libraries (to my knowledge at least) to parse digital input data (not output).

sending a pin value query for a digital input pin on the other hand would return a DIGITAL_MESSAGE

Lets define the reply messages in a list for each request type:

  • type 2 -> digital message 0x90 (for all ports with digital input pins)
  • type 3 -> analog message 0xE0 (for each pin)
  • type 1 -> type 2 + type 3
  • type 5 -> digital_message 0x90 (for all ports with digital output pins) or pin state query response 0x6E pin mode 1 (for each pin)?
  • type 6 -> pin state query response 0x6E pin mode 3 (for each pin)
  • type 4 -> type 5 + type 6
  • type 0 -> type 1 + type 4
  • type 7 -> type 2, 3, 5 or 6 depending on pin type

I agree that hosts who have already implemented the pin state query response need no changes to process the replies for type 5 and 6. On the other hand port reporting for type 5 has slightly less overhead.

I agree that there is less overhead in using DIGITAL_MESSAGE to report output values, but there is also added overhead in parsing the result, especially in that 99% of the time you receive DIGITAL_MESSAGE you only care about the input state. If there is no loss of connection the host should always know the state of digital output (since the host set that pin state). If you need to ensure this state is correct after a RECONNECT event then you would have to assume the next DIGITAL_MESSAGE received, following a PIN_VALUE_QUERY is the message you need to parse the output values from and update the local state of that pin (if you use a pin object in the host for example). However when using PIN_STATE_RESPONSE, there is no guessing about when to update the output pin state in the host application.

OK, now we have:

0  START_SYSEX     (0xF0)
1  PIN_VALUE_QUERY (0x68)
2  type            0 = all pins (input & output) - response is type 1 + type 4,
                   1 = all input - response is type 2 + type 3  
                   2 = digital input - response is 0x90 for all ports with digital input pins, 
                   3 = analog input - response is 0xE0 for each analog input pin,
                   4 = all output - response is type 5 + type 6, 
                   5 = digital output - response is type 0x6E with pin mode 1 for each pin,
                   6 = analog output / pwm - response is type 0x6E with pin mode 3 for each pin,
                   7 = pin
3  pin             (0-127) // only use if type > 6
4  END_SYSEX

Implementation detail for Arduino:
When reporting digital input values, the 3rd parameter of outputPort should be set to true so that the value can be reported even if it has not changed:

// setting 3rd parameter to true forces reporting even if pin value has not changed
outputPort(port, readPort(port, portConfigInputs[port]), true);

The discussion here may alter this protocol to some extent. It may be beneficial to have a dedicated message for reporting individual pins once the ability to enable pin reporting for individual pins is implemented. I guess this proposal would probably still stand, but it would use the new digital pin message (if added) to report individual pins.

One problem with the above proposal is when a person wants to query an individual pin. As discussed in #68, different boards have different digital-to-analog pin mappings, such as the Leonardo. To get around this, we could change type 7 to target a digital pin and add a type 8 to target an analog pin:

0  START_SYSEX     (0xF0)
1  PIN_VALUE_QUERY (0x68)
2  type            0 = all pins (input & output) - response is type 1 + type 4,
                   1 = all input - response is type 2 + type 3,
                   2 = digital input - response is 0x90 for all ports with digital input pins, 
                   3 = analog input - response is 0xE0 for each analog input pin,
                   4 = all output - response is type 5 + type 6, 
                   5 = digital output - response is type 0x6E with pin mode 1 for each pin,
                   6 = analog output / pwm - response is type 0x6E with pin mode 3 for each pin,
                   7 = digital pin,
                   8 = analog pin
3  pin             (0-127) // only use if type > 6
4  END_SYSEX

Hello folks, i think it might help on readings, i put a capacitor in +5v and GND on arduino nano, helped a lot on analog readings,

btw, i trying to make an BMS, so, i plan use at least all analog inputs for reading XD
20191109_073629

Captura de tela de 2019-11-09 07-15-57

and without capacitor:
20191109_073651

Captura de tela de 2019-11-09 07-16-25

i hope it can help.