firmata / ConfigurableFirmata

A plugin-based version of Firmata
GNU Lesser General Public License v2.1
152 stars 72 forks source link

Packet overflow on WiFi network / fix #75

Open ttww opened 6 years ago

ttww commented 6 years ago

I am implementing an Network layer for the firmata4j project. Everything is implemented and works "well" except that the current transport layer sends every byte as a single WiFi packet over the air... This leads to an unreliable connection because of packet overflows somewhere...

I have forked the ConfigurableFirmata project also and added some patches which collects the bytes and flush it after an END_SYSEX, buffer full or end of reporting.

With this changes everything is fast and nice, but I don't oversee the side-effects to the normal serial communication and the procedure to update the other places (like in Arduino IDE packet manager...)

Can someone help me and do a review with the changes in https://github.com/ttww/ConfigurableFirmata/tree/Packet-Buffer? I'm also unsure about the version number... Thanks in advance

soundanalogous commented 6 years ago

One side effect is that this sort of buffering is already implemented in BLEStream, so this would result in double buffering for BLE. For this reason, it may be better to implement buffered write in WiFiStream.

soundanalogous commented 6 years ago

Also FirmataReporting is optional, a user may configure a sketch that does not include analog input or i2c or other features that use reporting and therefore FirmataReporting will not be included.

As for how to ensure the buffer is flushed appropriately, one option is to add a poll method to WiFiStream in a similar manner to how it is used in BLEStream. Also note the use of the timer in the BLEStream implementation. Something like this may be required to ensure that digitalWrite data if flushed in a timely manner since digital messages don't use Sysex and would otherwise only be sent when the buffer if full, which could lead to noticeable latency.

soundanalogous commented 6 years ago

And a configureable flush interval helps the user fine tune according to an amount of latency that is acceptable to the user: https://github.com/firmata/ConfigurableFirmata/blob/master/src/utility/BLEStream.h#L217.

ttww commented 6 years ago

Thanks for your answer.

The buffering is completely independent from FirmataReporting. BLEStream is the wrong layer and an overkill for this problem (count the lines), it introduce more unclear timing situations (my point of view :-)). Maybe putting a Firmata.flush() in the main loop is a more general and simple solution (and we didn't need the FirmataReporting.done() or SYSEX handling anymore). I fully agree with you that sending when the buffer is full is not an option, it's a sign that something went wrong :-) What's about removing the buffering from BLEStream and doing there only the Bluetooth things? This would leave an out of the box buffering solution for all transport channels (serial, bluetooth, WiFi). Putting the buffering inside WiFiStream is not a good solution. The problem is in the layer above and it would WiFiStream left to guess when to send. The buffering in ConfigurableFirmata is a benefit for all transport layers. Please don't misunderstand me, I try to find a general out of the box solution without the need for special Streams :-)

soundanalogous commented 6 years ago

I agree, my thought for adding buffering to WiFiStream was a short term solution. It should be done at the higher layer though. It's just going to be a much bigger project. All of the transport streams were written by different authors so it's probably a good time to refactor for consistency.

soundanalogous commented 6 years ago

There is a complication here however, writes on the Serial transport are already buffered in the HardwareSerial implementation. However they are not in the WiFi and Ethernet implementations as you pointed out. This is just for AVR, I'd have to look at how the other Arduino-compatible architectures handle write as well.

ttww commented 6 years ago

Additionally, the Serial*-Buffers can't be reused right after write() call (don't know if the copy the data). Would it make sense to make a pull request with the current state? How should we go on?

soundanalogous commented 6 years ago

Serial.write returns immediately without transmitting any data because transmission is actually handled by an ISR, at least on some MCUs. Serial.flush waits until the TX buffer is eventually emptied by the ISR so Serial.write followed by Serial.flush is blocking while Serial.write alone is not.

As far as how to proceed, you can submit a PR but there is a lot of work remaining. First remove the changes to FirmataReporting.

soundanalogous commented 6 years ago

TODO:

soundanalogous commented 6 years ago

The more I think about this, the more I think it actually may be better to handle this in the individual transport streams. Another reason is the TX buffer size varies by transport, even with Serial, the buffer may be as small as 16 or as large as 256 bytes. The BLE TX buffer is only 20 bytes for the Arduino 101 and for the general purpose BLEPeripheral library.

Otherwise ConfigurableFirmata would need to be littered with architecture dependent #ifdefs. I'd rather push this complexity out to the individual stream transport wrappers.

One thing we could try is combining the WiFi and Ethernet stream wrappers into a single NetworkStream wrapper, that would at least remove some duplication.

I'm open to other ideas as well.

soundanalogous commented 6 years ago

@zfields I'm curious if you have any thoughts on a good approach here. The issue is that Arduino buffers serial/UART data in the various architecture-dependent HW serial implementations (you simply use stream.write(someData) and data is buffered in an ISR behind the scenes). However there is no such buffering behind the scenes when using a network (WiFi or Ethernet) stream, data is sent one byte at a time based on the way writes are currently implemented (example from WiFiClient implementation in WiFi101.cpp). Additionally, various streams have different TX buffer sizes. BLE for example uses a 20 byte buffer, serial can range from 16 to 256 bytes depending on the board architecture (but 64 bytes is probably the most common). A network TX buffer could be fixed (say 64 bytes), but perhaps give the user the ability to increase up to 256 bytes using a define.

I'm leaning towards adding buffering to the Firmata WiFi and Ethernet stream wrappers. A more ideal solution would be to combine these 2 stream wrappers into a single NetworkStream class. I'm curious if you have any other ideas. Ideally, as @ttww points out this could be handled at a lower level (a single buffering scheme to cover all streams), but due to the different buffer sizes and the Serial issue, I'm not exactly sure how to proceed in that direction, thus my suggestion to handle this in the network stream separately for now.

ttww commented 6 years ago

Hi, finally, I have done some more changes which are not directly correlated to this packet buffering context. Please tell me if you want to discuss this on a different place.

With all those changes you can completely control a small driving roboter with the Firmata protocol and develop on the host :-)

soundanalogous commented 6 years ago

I think that sounds like it would be a few separate pull requests. You should also look at the newer architecture being implemented in the firmata/arduino repo (note in particular the new classes FirmataParser, FirmataMarshaller, FirmataConstants, FirmataDefines). These changes will be migrated over to firmata/ConfigurableFirmata at some point.

soundanalogous commented 6 years ago

@ttww I've looked over your latest changes. You have several different changes that would need to be broken up as follows:

  1. Proposal for a way to inquire about which pins on a board support interrupts.
  2. Proposal to add a new PIN_COUNTER feature (the place to start here is firmata/protocol and submit a PR to add a new markdown file with your proposal.
  3. Proposal to add a heartbeat feature.
  4. Proposal to add buffering when using a network transport (the original request in this thread).

1 & 2 are related and could be submitted together, but I'd prefer if 3 and 4 were separate changes (and separate threads).

The other thing to consider is if there was a more general way to access pin change interrupts through firmata if you'd still be able to accomplish the counter part on the client side, or if the timing is too intricate and the only way to do it is on the firmware side.