Skip to content

Commit 8839902

Browse files
committed
fix(init): initialize state of pins one by one
If the pin count is too high (i.e. Arduino Mega), then too many firmata requests in a row may overflow the device's serial input buffer. The solution is to request states of the pins one by one, i.e. request the folowing pin's state after the previous responce is received. Fixes #33
1 parent 4ce2484 commit 8839902

File tree

4 files changed

+87
-16
lines changed

4 files changed

+87
-16
lines changed

src/main/java/org/firmata4j/firmata/FirmataDevice.java

+33-16
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@
3434
import org.slf4j.LoggerFactory;
3535
import java.io.IOException;
3636
import java.text.MessageFormat;
37+
import java.util.ArrayDeque;
3738
import java.util.ArrayList;
3839
import java.util.Collections;
3940
import java.util.HashMap;
4041
import java.util.HashSet;
4142
import java.util.LinkedHashSet;
4243
import java.util.List;
4344
import java.util.Map;
45+
import java.util.Queue;
4446
import java.util.Set;
4547
import java.util.concurrent.Executors;
4648
import java.util.concurrent.atomic.AtomicBoolean;
@@ -60,6 +62,7 @@ public class FirmataDevice implements IODevice {
6062
private FiniteStateMachine protocol;
6163
private final Set<IODeviceEventListener> listeners = Collections.synchronizedSet(new LinkedHashSet<IODeviceEventListener>());
6264
private final List<FirmataPin> pins = Collections.synchronizedList(new ArrayList<FirmataPin>());
65+
private final Queue<Byte> pinStateRequestQueue = new ArrayDeque<>();
6366
private final AtomicBoolean started = new AtomicBoolean(false);
6467
private final AtomicBoolean ready = new AtomicBoolean(false);
6568
private final AtomicInteger initializedPins = new AtomicInteger(0);
@@ -92,6 +95,7 @@ public FirmataDevice(TransportInterface transport) {
9295
protocol.addHandler(PROTOCOL_MESSAGE, onProtocolReceive);
9396
protocol.addHandler(FIRMWARE_MESSAGE, onFirmwareReceive);
9497
protocol.addHandler(PIN_CAPABILITIES_MESSAGE, onCapabilitiesReceive);
98+
protocol.addHandler(PIN_CAPABILITIES_FINISHED, onCapabilitiesFinished);
9599
protocol.addHandler(PIN_STATE, onPinStateReceive);
96100
protocol.addHandler(ANALOG_MAPPING_MESSAGE, onAnalogMappingReceive);
97101
protocol.addHandler(ANALOG_MESSAGE_RESPONSE, onAnalogMessageReceive);
@@ -367,24 +371,29 @@ public void accept(Event event) {
367371
initializedPins.incrementAndGet();
368372
} else {
369373
// if the pin supports some modes, we ask for its current mode and value
374+
pinStateRequestQueue.add(pinId);
375+
}
376+
}
377+
};
378+
379+
/**
380+
* Called when capabilities for the all the pins are received.
381+
*/
382+
private final Consumer<Event> onCapabilitiesFinished = new Consumer<Event>() {
383+
@Override
384+
public void accept(Event t) {
385+
if (initializedPins.get() == pins.size()) {
386+
try {
387+
sendMessage(FirmataMessageFactory.ANALOG_MAPPING_REQUEST);
388+
} catch (IOException e) {
389+
LOGGER.error("Error requesting of the analog mapping", e);
390+
}
391+
} else {
392+
byte pinId = pinStateRequestQueue.poll();
370393
try {
371394
sendMessage(FirmataMessageFactory.pinStateRequest(pinId));
372-
if (pinId > 0 && pinId % 14 == 0) { // 14 pins on Arduino UNO get initialized without delay
373-
/* If the pin count is too high (i.e. Arduino Mega), then
374-
* too many firmata requests in a row can overflow the
375-
* device's serial input buffer.
376-
* One solution is to yield a little time between
377-
* requests to allow the device to respond. The response
378-
* may then safely sit in the host's much larger serial
379-
* input buffer until it is dealt with by onPinStateReceive
380-
*/
381-
Thread.sleep(100);
382-
}
383395
} catch (IOException ex) {
384-
LOGGER.error(String.format("Error requesting state of pin %d", pin.getIndex()), ex);
385-
} catch (InterruptedException ex) {
386-
Thread.currentThread().interrupt();
387-
LOGGER.warn("Delay between capability requests was interrupted", ex);
396+
LOGGER.error(String.format("Error requesting state of pin %d", pinId), ex);
388397
}
389398
}
390399
}
@@ -404,11 +413,19 @@ public void accept(Event event) {
404413
} else {
405414
pin.updateValue((Long) event.getBodyItem(PIN_VALUE));
406415
}
416+
if (!pinStateRequestQueue.isEmpty()) {
417+
byte pid = pinStateRequestQueue.poll();
418+
try {
419+
sendMessage(FirmataMessageFactory.pinStateRequest(pid)); // request the following pin state
420+
} catch (IOException ex) {
421+
LOGGER.error(String.format("Error requesting state of pin %d", pid), ex);
422+
}
423+
}
407424
if (initializedPins.incrementAndGet() == pins.size()) {
408425
try {
409426
sendMessage(FirmataMessageFactory.ANALOG_MAPPING_REQUEST);
410427
} catch (IOException e) {
411-
LOGGER.error("Error on request analog mapping", e);
428+
LOGGER.error("Error requesting of the analog mapping", e);
412429
}
413430
}
414431
}

src/main/java/org/firmata4j/firmata/parser/FirmataEventType.java

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public interface FirmataEventType {
5656
String PIN_SUPPORTED_MODES = "supportedModes";
5757
String PIN_MODE = "pinMode";
5858
String PIN_VALUE = "pinValue";
59+
String PIN_CAPABILITIES_FINISHED = "pinCapabilitiesFinished";
5960

6061
String SYSTEM_RESET_MESSAGE = "systemReset";
6162

src/main/java/org/firmata4j/firmata/parser/ParsingCapabilityResponseState.java

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public ParsingCapabilityResponseState(FiniteStateMachine fsm) {
5050
@Override
5151
public void process(byte b) {
5252
if (b == END_SYSEX) {
53+
publish(new Event(PIN_CAPABILITIES_FINISHED));
5354
transitTo(WaitingForMessageState.class);
5455
} else if (b == 127) {
5556
byte[] buffer = getBuffer();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2014-2019 Oleg Kurbatov ([email protected])
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.firmata4j.firmata.fsm;
26+
27+
import org.firmata4j.fsm.Event;
28+
import org.firmata4j.fsm.FiniteStateMachine;
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
32+
/**
33+
*
34+
* @author Oleg Kurbatov &lt;[email protected]&gt;
35+
*/
36+
public class FiniteStateMachineTest {
37+
38+
private FiniteStateMachine fsm;
39+
40+
@Before
41+
public void setUp() {
42+
fsm = new FiniteStateMachine();
43+
}
44+
45+
@Test
46+
public void testHandle() {
47+
Event evt = new Event("error");
48+
evt.setBodyItem("test", "test value");
49+
fsm.handle(evt);
50+
}
51+
52+
}

0 commit comments

Comments
 (0)