Skip to content

Commit

Permalink
OneWire support in configurable firmata #138
Browse files Browse the repository at this point in the history
  • Loading branch information
mattjlewis committed Feb 21, 2023
1 parent eea4b7a commit 09ddc86
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ where they simplify development and improve code readability.
Created by [Matt Lewis](https://github.com/mattjlewis) (email [deviceiozero@gmail.com](mailto:deviceiozero@gmail.com)) ([blog](https://diozero.blogspot.co.uk/)),
inspired by [GPIO Zero](https://gpiozero.readthedocs.org/) and [Johnny Five](http://johnny-five.io/).
If you have any issues please use the [GitHub issues page](https://github.com/mattjlewis/diozero/issues).
For any other comments or suggestions, please use the [diozero Google Group](https://groups.google.com/forum/#!forum/diozero).
For any other comments or suggestions, please use the [GitHub discussions page](https://github.com/mattjlewis/diozero/discussions).

Please refer to the [GitHub Pages site](https://www.diozero.com/) for further documentation.
2 changes: 2 additions & 0 deletions diozero-core/src/main/java/com/diozero/util/SleepUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ public static void busySleep(final long nanos) {
}

public static void parkNanos(final long nanos) {
// See:
// https://hazelcast.com/blog/locksupport-parknanos-under-the-hood-and-the-curious-case-of-parking/
LockSupport.parkNanos(nanos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -430,6 +434,123 @@ public void i2cWriteData(int slaveAddress, boolean autoRestart, boolean addressS
sendMessage(buffer);
}

public OneWireSearchResponse oneWireSearch(int gpio, boolean alarms) throws RuntimeIOException {
byte[] buffer = new byte[5];
int index = 0;
buffer[index++] = START_SYSEX;
buffer[index++] = ONEWIRE_COMMAND;
if (alarms) {
buffer[index++] = ONEWIRE_ALARMS_SEARCH;
} else {
buffer[index++] = ONEWIRE_NORMAL_SEARCH;
}
buffer[index++] = (byte) gpio;
buffer[index++] = END_SYSEX;

return sendMessage(buffer, OneWireSearchResponse.class);
}

public void oneWireConfig(int gpio, boolean parasiticPower) throws RuntimeIOException {
byte[] buffer = new byte[5];
int index = 0;
buffer[index++] = START_SYSEX;
buffer[index++] = ONEWIRE_COMMAND;
buffer[index++] = ONEWIRE_CONFIG;
if (parasiticPower) {
buffer[index++] = ONEWIRE_PARASITIC_POWER_ON;
} else {
buffer[index++] = ONEWIRE_PARASITIC_POWER_OFF;
}
buffer[index++] = (byte) gpio;
buffer[index++] = END_SYSEX;

sendMessage(buffer);
}

public void oneWireReset(int gpio) throws RuntimeIOException {
oneWireCommands(gpio, EnumSet.of(OneWireCommand.RESET), Optional.empty(), OptionalInt.empty(),
OptionalInt.empty(), OptionalInt.empty(), OptionalInt.empty());
}

public void oneWireSkip(int gpio) throws RuntimeIOException {
oneWireCommands(gpio, EnumSet.of(OneWireCommand.SKIP), Optional.empty(), OptionalInt.empty(),
OptionalInt.empty(), OptionalInt.empty(), OptionalInt.empty());
}

public void oneWireSelect(int gpio, byte[] address) throws RuntimeIOException {
oneWireCommands(gpio, EnumSet.of(OneWireCommand.SELECT), Optional.of(address), OptionalInt.empty(),
OptionalInt.empty(), OptionalInt.empty(), OptionalInt.empty());
}

public OneWireReadResponse oneWireRead(int gpio, int length, int correlationId) throws RuntimeIOException {
return oneWireCommands(gpio, EnumSet.of(OneWireCommand.READ), Optional.empty(), OptionalInt.of(length),
OptionalInt.of(correlationId), OptionalInt.empty(), OptionalInt.empty()).get();
}

public void oneWireDelay(int gpio, int delay) throws RuntimeIOException {
oneWireCommands(gpio, EnumSet.of(OneWireCommand.DELAY), Optional.empty(), OptionalInt.empty(),
OptionalInt.empty(), OptionalInt.of(delay), OptionalInt.empty());
}

public void oneWireWrite(int gpio, int data) throws RuntimeIOException {
oneWireCommands(gpio, EnumSet.of(OneWireCommand.WRITE), Optional.empty(), OptionalInt.empty(),
OptionalInt.empty(), OptionalInt.empty(), OptionalInt.of(data));
}

public Optional<OneWireReadResponse> oneWireCommands(int gpio, EnumSet<OneWireCommand> commands,
Optional<byte[]> address, OptionalInt bytesToRead, OptionalInt correlationId, OptionalInt delay,
OptionalInt data) throws RuntimeIOException {
byte[] data_bytes = new byte[19];
ByteBuffer data_buffer = ByteBuffer.wrap(data_bytes);
data_buffer.order(ByteOrder.LITTLE_ENDIAN);
byte command_mask = 0;
for (OneWireCommand command : commands) {
command_mask |= command.mask();
switch (command) {
case SELECT:
data_buffer.position(0);
data_buffer.put(address.get());
break;
case READ:
data_buffer.position(8);
data_buffer.putShort((short) bytesToRead.getAsInt());
data_buffer.putShort((short) correlationId.getAsInt());
break;
case DELAY:
data_buffer.position(12);
data_buffer.putInt(delay.getAsInt());
break;
case WRITE:
data_buffer.position(16);
data_buffer.putShort((short) data.getAsInt());
data_buffer.put((byte) (data.getAsInt() >> 16));
break;
default:
// Ignore
}
}
data_buffer.flip();

byte[] data_bytes_encoded = FirmataProtocol.to7BitArray(data_bytes);

byte[] buffer = new byte[5 + data_bytes_encoded.length];
int index = 0;
buffer[index++] = START_SYSEX;
buffer[index++] = ONEWIRE_COMMAND;
buffer[index++] = (byte) gpio;
buffer[index++] = command_mask;
System.arraycopy(data_bytes_encoded, 0, buffer, index, data_bytes_encoded.length);
index += data_bytes_encoded.length;
buffer[index++] = END_SYSEX;

if (commands.contains(OneWireCommand.READ)) {
return Optional.of(sendMessage(buffer, OneWireReadResponse.class));
}

sendMessage(buffer);
return Optional.empty();
}

public void createTask(int taskId, int length) {
if (taskId < 0 || taskId > 0x7f) {
throw new IllegalArgumentException("Invalid taskId (" + taskId + ") must be 0.." + 0x7f);
Expand Down Expand Up @@ -698,6 +819,9 @@ private SysExResponse readSysEx(Byte sysExCommand) {
// TODO Implementation
response = new ReportFeaturesResponse();
break;
case ONEWIRE_COMMAND:
response = unpackOneWireResponse(buffer);
break;
case SCHEDULER_DATA:
byte scheduler_reply_type = buffer.get();
switch (scheduler_reply_type) {
Expand Down Expand Up @@ -781,6 +905,20 @@ private SysExResponse readSysEx(Byte sysExCommand) {
return response;
}

private static SysExResponse unpackOneWireResponse(ByteBuffer buffer) {
byte onewire_response = buffer.get();
switch (onewire_response) {
case ONEWIRE_NORMAL_SEARCH_REPLY:
case ONEWIRE_ALARMS_SEARCH_REPLY:
return new OneWireSearchResponse(buffer);
case ONEWIRE_READ_REPLY:
return new OneWireReadResponse(buffer);
default:
Logger.warn("Unhandled OneWire response {}", Integer.valueOf(onewire_response));
}
return null;
}

private ProtocolVersionResponse readVersionResponse() {
return new ProtocolVersionResponse(transport.readByte(), transport.readByte());
}
Expand Down Expand Up @@ -894,22 +1032,22 @@ public String toString() {
}

/*-
0 START_SYSEX (0xF0)
1 REPORT_FEATURES (0x65)
2 REPORT_FEATURES_RESPONSE (0x01)
3 1st FEATURE_ID (1-127, eg: Serial = 0x60, Stepper = 0x62)
4 1st FEATURE_MAJOR_VERSION (0-127)
5 1st FEATURE_MINOR_VERSION (0-127)
6 2nd FEATURE_ID (1-127, eg: Serial = 0x60, Stepper = 0x62)
7 2nd FEATURE_MAJOR_VERSION (0-127)
8 2nd FEATURE_MINOR_VERSION (0-127)
9 3rd FEATURE_ID (0x00, Extended ID)
10 3rd FEATURE_ID (lsb)
11 3rd FEATURE_ID (msb)
12 3rd FEATURE_MAJOR_VERSION (0-127)
13 3rd FEATURE_MINOR_VERSION (0-127)
...for all supported features
n END_SYSEX (0xF7)
* 0 START_SYSEX (0xF0)
* 1 REPORT_FEATURES (0x65)
* 2 REPORT_FEATURES_RESPONSE (0x01)
* 3 1st FEATURE_ID (1-127, eg: Serial = 0x60, Stepper = 0x62)
* 4 1st FEATURE_MAJOR_VERSION (0-127)
* 5 1st FEATURE_MINOR_VERSION (0-127)
* 6 2nd FEATURE_ID (1-127, eg: Serial = 0x60, Stepper = 0x62)
* 7 2nd FEATURE_MAJOR_VERSION (0-127)
* 8 2nd FEATURE_MINOR_VERSION (0-127)
* 9 3rd FEATURE_ID (0x00, Extended ID)
* 10 3rd FEATURE_ID (lsb)
* 11 3rd FEATURE_ID (msb)
* 12 3rd FEATURE_MAJOR_VERSION (0-127)
* 13 3rd FEATURE_MINOR_VERSION (0-127)
* ...for all supported features
* n END_SYSEX (0xF7)
*/
static class ReportFeaturesResponse extends SysExResponse {
ReportFeaturesResponse() {
Expand Down Expand Up @@ -1075,6 +1213,87 @@ public String toString() {
}
}

public static class OneWireSearchResponse extends SysExResponse {
private int gpio;
private byte[][] addresses;

public OneWireSearchResponse(ByteBuffer buffer) {
/*-
* 3 pin (0-127)
* 4 bit 0-6 [optional] address bytes encoded using 8 times 7 bit for 7 bytes of 8 bit
* 5 bit 7-13 [optional] 1.address[0] = byte[0] + byte[1]<<7 & 0x7F
* 6 bit 14-20 [optional] 1.address[1] = byte[1]>>1 + byte[2]<<6 & 0x7F
* 7 .... ...
* 11 bit 49-55 1.address[6] = byte[6]>>6 + byte[7]<<1 & 0x7F
* 12 bit 56-63 1.address[7] = byte[8] + byte[9]<<7 & 0x7F
* 13 bit 64-69 2.address[0] = byte[9]>>1 + byte[10]<<6 &0x7F
* n ... as many bytes as needed (don't exceed MAX_DATA_BYTES though)
* n+1 END_SYSEX (0xF7)
*/
gpio = buffer.get() & 0xff;
byte[] enc = new byte[buffer.remaining()];
buffer.get(enc);
byte[] data = FirmataProtocol.from7BitArray(enc);
/*
* Each one-wire-device has a unique identifier which is 8 bytes long and comes
* factory-programmed into the the device.
*/
if (data.length % 8 != 0) {
// Error !
Logger.error("Expected response with multiple of 8 bytes, got {} bytes", Integer.valueOf(data.length));
}
addresses = new byte[data.length % 8][8];
for (int device = 0; device < data.length % 8; device++) {
buffer.get(addresses[device]);
}
}

public int getGpio() {
return gpio;
}

public byte[][] getAddresses() {
return addresses;
}
}

public static class OneWireReadResponse extends SysExResponse {
private int gpio;
private int correlationId;
private int value;

public OneWireReadResponse(ByteBuffer buffer) {
/*-
* 3 pin (0-127)
* 4 bit 0-6 [optional] data bytes encoded using 8 times 7 bit for 7 bytes of 8 bit
* 5 bit 7-13 [optional] correlationid[0] = byte[0] + byte[1]<<7 & 0x7F
* 6 bit 14-20 [optional] correlationid[1] = byte[1]>1 + byte[2]<<6 & 0x7F
* 7 bit 21-27 [optional] data[0] = byte[2]>2 + byte[3]<<5 & 0x7F
* 8 .... data[1] = byte[3]>3 + byte[4]<<4 & 0x7F
* n ... as many bytes as needed (don't exceed MAX_DATA_BYTES though)
* n+1 END_SYSEX (0xF7)
*/
gpio = buffer.get() & 0xff;
byte[] enc = new byte[buffer.remaining()];
buffer.get(enc);
ByteBuffer data = ByteBuffer.wrap(FirmataProtocol.from7BitArray(enc));
correlationId = data.getShort() & 0xffff;
value = data.getShort() & 0xffff;
}

public int getGpio() {
return gpio;
}

public int getCorrelationId() {
return correlationId;
}

public int getValue() {
return value;
}
}

static abstract class SchedulerDataResponse extends SysExResponse {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public interface FirmataProtocol {
byte SERVO_CONFIG = 0x70; // set max angle, minPulse, maxPulse, freq
byte STRING_DATA = 0x71; // a string message with 14-bits per char
byte STEPPER_DATA = 0x72; // ** control a stepper motor
byte ONEWIRE_DATA = 0x73; // ** send an OneWire read/write/reset/select/skip/search request
byte ONEWIRE_COMMAND = 0x73; // ** send an OneWire read/write/reset/select/skip/search request
byte DHTSENSOR_DATA = 0x74; // Used by DhtFirmata
byte SHIFT_DATA = 0x75; // ** a bitstream to/from a shift register
byte I2C_REQUEST = (byte) 0x76; // send an I2C read/write request
Expand All @@ -92,6 +92,16 @@ public interface FirmataProtocol {
byte SYSEX_NON_REALTIME = 0x7E; // MIDI Reserved for non-realtime messages
byte SYSEX_REALTIME = 0X7F; // MIDI Reserved for realtime messages

// OneWire
byte ONEWIRE_NORMAL_SEARCH = 0x40;
byte ONEWIRE_NORMAL_SEARCH_REPLY = 0x42;
byte ONEWIRE_READ_REPLY = 0x43;
byte ONEWIRE_ALARMS_SEARCH = 0x44;
byte ONEWIRE_ALARMS_SEARCH_REPLY = 0x45;
byte ONEWIRE_CONFIG = 0x41;
byte ONEWIRE_PARASITIC_POWER_ON = 0x00;
byte ONEWIRE_PARASITIC_POWER_OFF = 0x01;

// Scheduler
byte MAX_TASK_ID = 0x7f;
// Scheduler instructions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.diozero.internal.provider.firmata.adapter;

public enum OneWireCommand {
RESET(0), SKIP(1), SELECT(2), READ(3), DELAY(4), WRITE(5);

private byte mask;

OneWireCommand(int bit) {
this.mask = (byte) (1 << bit);
}

public byte mask() {
return mask;
}
}
2 changes: 1 addition & 1 deletion docs/docs/8_boards/5_TinkerBoardNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Header: DEFAULT
+-----+----------+------+---+--------+----------+--------+---+------+----------+-----+
```

## I/O File Permissions
## GPIO File Permissions

File `/etc/udev/rules.d/70-gpio.rules`:

Expand Down

0 comments on commit 09ddc86

Please sign in to comment.