Skip to content

Commit

Permalink
fix(init): initialize state of pins one by one
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kurbatov committed Aug 26, 2019
1 parent 4ce2484 commit 8839902
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 16 deletions.
49 changes: 33 additions & 16 deletions src/main/java/org/firmata4j/firmata/FirmataDevice.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -60,6 +62,7 @@ public class FirmataDevice implements IODevice {
private FiniteStateMachine protocol;
private final Set<IODeviceEventListener> listeners = Collections.synchronizedSet(new LinkedHashSet<IODeviceEventListener>());
private final List<FirmataPin> pins = Collections.synchronizedList(new ArrayList<FirmataPin>());
private final Queue<Byte> pinStateRequestQueue = new ArrayDeque<>();
private final AtomicBoolean started = new AtomicBoolean(false);
private final AtomicBoolean ready = new AtomicBoolean(false);
private final AtomicInteger initializedPins = new AtomicInteger(0);
Expand Down Expand Up @@ -92,6 +95,7 @@ public FirmataDevice(TransportInterface transport) {
protocol.addHandler(PROTOCOL_MESSAGE, onProtocolReceive);
protocol.addHandler(FIRMWARE_MESSAGE, onFirmwareReceive);
protocol.addHandler(PIN_CAPABILITIES_MESSAGE, onCapabilitiesReceive);
protocol.addHandler(PIN_CAPABILITIES_FINISHED, onCapabilitiesFinished);
protocol.addHandler(PIN_STATE, onPinStateReceive);
protocol.addHandler(ANALOG_MAPPING_MESSAGE, onAnalogMappingReceive);
protocol.addHandler(ANALOG_MESSAGE_RESPONSE, onAnalogMessageReceive);
Expand Down Expand Up @@ -367,24 +371,29 @@ public void accept(Event event) {
initializedPins.incrementAndGet();
} else {
// if the pin supports some modes, we ask for its current mode and value
pinStateRequestQueue.add(pinId);
}
}
};

/**
* Called when capabilities for the all the pins are received.
*/
private final Consumer<Event> onCapabilitiesFinished = new Consumer<Event>() {
@Override
public void accept(Event t) {
if (initializedPins.get() == pins.size()) {
try {
sendMessage(FirmataMessageFactory.ANALOG_MAPPING_REQUEST);
} catch (IOException e) {
LOGGER.error("Error requesting of the analog mapping", e);
}
} else {
byte pinId = pinStateRequestQueue.poll();
try {
sendMessage(FirmataMessageFactory.pinStateRequest(pinId));
if (pinId > 0 && pinId % 14 == 0) { // 14 pins on Arduino UNO get initialized without delay
/* If the pin count is too high (i.e. Arduino Mega), then
* too many firmata requests in a row can overflow the
* device's serial input buffer.
* One solution is to yield a little time between
* requests to allow the device to respond. The response
* may then safely sit in the host's much larger serial
* input buffer until it is dealt with by onPinStateReceive
*/
Thread.sleep(100);
}
} catch (IOException ex) {
LOGGER.error(String.format("Error requesting state of pin %d", pin.getIndex()), ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
LOGGER.warn("Delay between capability requests was interrupted", ex);
LOGGER.error(String.format("Error requesting state of pin %d", pinId), ex);
}
}
}
Expand All @@ -404,11 +413,19 @@ public void accept(Event event) {
} else {
pin.updateValue((Long) event.getBodyItem(PIN_VALUE));
}
if (!pinStateRequestQueue.isEmpty()) {
byte pid = pinStateRequestQueue.poll();
try {
sendMessage(FirmataMessageFactory.pinStateRequest(pid)); // request the following pin state
} catch (IOException ex) {
LOGGER.error(String.format("Error requesting state of pin %d", pid), ex);
}
}
if (initializedPins.incrementAndGet() == pins.size()) {
try {
sendMessage(FirmataMessageFactory.ANALOG_MAPPING_REQUEST);
} catch (IOException e) {
LOGGER.error("Error on request analog mapping", e);
LOGGER.error("Error requesting of the analog mapping", e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public interface FirmataEventType {
String PIN_SUPPORTED_MODES = "supportedModes";
String PIN_MODE = "pinMode";
String PIN_VALUE = "pinValue";
String PIN_CAPABILITIES_FINISHED = "pinCapabilitiesFinished";

String SYSTEM_RESET_MESSAGE = "systemReset";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public ParsingCapabilityResponseState(FiniteStateMachine fsm) {
@Override
public void process(byte b) {
if (b == END_SYSEX) {
publish(new Event(PIN_CAPABILITIES_FINISHED));
transitTo(WaitingForMessageState.class);
} else if (b == 127) {
byte[] buffer = getBuffer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com)
*
* 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.
*/

package org.firmata4j.firmata.fsm;

import org.firmata4j.fsm.Event;
import org.firmata4j.fsm.FiniteStateMachine;
import org.junit.Before;
import org.junit.Test;

/**
*
* @author Oleg Kurbatov &lt;o.v.kurbatov@gmail.com&gt;
*/
public class FiniteStateMachineTest {

private FiniteStateMachine fsm;

@Before
public void setUp() {
fsm = new FiniteStateMachine();
}

@Test
public void testHandle() {
Event evt = new Event("error");
evt.setBodyItem("test", "test value");
fsm.handle(evt);
}

}

0 comments on commit 8839902

Please sign in to comment.