Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/gnu/io/RXTXCommDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ else if ( osName.equals("Solaris") || osName.equals("SunOS"))
"ttyUSB", // for USB frobs
"ttyAMA", // Raspberry Pi
"rfcomm", // bluetooth serial device
"hidraw",
"ttyircomm", // linux IrCommdevices (IrDA serial emu)
"ttyACM",// linux CDC ACM devices
"DyIO",// NRDyIO
Expand All @@ -677,6 +678,7 @@ else if(osName.equals("Linux-all-ports"))
"holter", // custom card for heart monitoring
"modem", // linux symbolic link to modem.
"rfcomm", // bluetooth serial device
"hidraw",
"ttyircomm", // linux IrCommdevices (IrDA serial emu)
"ttycosa0c", // linux COSA/SRP synchronous serial card
"ttycosa1c", // linux COSA/SRP synchronous serial card
Expand Down
90 changes: 90 additions & 0 deletions test/src/test/mpp/CRCUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package test.mpp;
// adapted from https://github.com/synogen/mpp/blob/master/src/main/java/org/mppsolartest/serial/CRCUtil.java

public class CRCUtil {
private static final char[] crc_tb = new char[] { '\u0000', 'အ', '⁂', 'っ', '䂄', '傥', '惆', '烧', '脈', '鄩', 'ꅊ', '녫',
'소', '톭', '\ue1ce', '\uf1ef', 'ሱ', 'Ȑ', '㉳', '≒', '劵', '䊔', '狷', '拖', '錹', '茘', '덻', 'ꍚ', '펽', '쎜',
'\uf3ff', '\ue3de', '③', '㑃', 'Р', 'ᐁ', '擦', '瓇', '䒤', '咅', 'ꕪ', '땋', '蔨', '锉', '\ue5ee', '\uf5cf', '얬',
'햍', '㙓', '♲', 'ᘑ', 'ذ', '盗', '曶', '嚕', '䚴', '띛', 'ꝺ', '霙', '蜸', '\uf7df', '\ue7fe', '힝', '잼', '䣄', '壥',
'梆', '碧', 'ࡀ', 'ᡡ', '⠂', '㠣', '짌', '\ud9ed', '\ue98e', '羚', '襈', '饩', 'ꤊ', '뤫', '嫵', '䫔', '窷', '檖', 'ᩱ',
'\u0a50', '㨳', '⨒', '\udbfd', '쯜', '﮿', '\ueb9e', '魹', '識', '묻', '\uab1a', '沦', '粇', '䳤', '峅', 'Ⱒ', '㰃',
'ౠ', '᱁', '\uedae', 'ﶏ', '췬', '\uddcd', '괪', '봋', '赨', '鵉', '纗', '溶', '廕', '仴', '㸓', '⸲', 'ṑ', '\u0e70',
'゚', '\uefbe', '\udfdd', '쿼', '뼛', '꼺', '齙', '轸', '醈', '膩', '뇊', 'ꇫ', '턌', '섭', '\uf14e', '\ue16f', 'ႀ',
'¡', 'ヂ', '⃣', '倄', '䀥', '灆', '恧', '莹', '鎘', 'ꏻ', '돚', '쌽', '팜', '\ue37f', '\uf35e', 'ʱ', 'ነ', '⋳', '㋒',
'䈵', '刔', '扷', '牖', '뗪', 'ꗋ', '閨', '薉', '\uf56e', '\ue54f', '픬', '씍', '㓢', 'Ⓝ', 'ᒠ', 'ҁ', '瑦', '摇', '吤',
'䐅', '\ua7db', '럺', '螙', '鞸', '\ue75f', '\uf77e', '윝', '휼', '⛓', '㛲', 'ڑ', 'ᚰ', '晗', '癶', '䘕', '嘴',
'\ud94c', '쥭', '癩', '\ue92f', '駈', '觩', '릊', 'ꦫ', '塄', '䡥', '砆', '栧', 'ᣀ', '࣡', '㢂', '⢣', '쭽', '\udb5c',
'\ueb3f', 'ﬞ', '诹', '鯘', 'ꮻ', '뮚', '䩵', '婔', '樷', '稖', '૱', '\u1ad0', '⪳', '㪒', 'ﴮ', '\ued0f', '\udd6c',
'쵍', '붪', '궋', '鷨', '跉', '簦', '氇', '層', '䱅', '㲢', 'ⲃ', '᳠', 'ು', '\uef1f', '^', '콝', '\udf7c', '꾛', '뾺',
'这', '鿸', '渗', '縶', '乕', '年', '⺓', '㺲', '໑', 'Ự' };

public static boolean checkCRC(String resultValue) {
if (resultValue.length() <= 2)
return false;
var firstValue = resultValue.substring(0, resultValue.length() - 2);
var lastValue = resultValue.substring(resultValue.length() - 2);
var pByte = firstValue.getBytes();
var returnV = calculateCRC(pByte);
var lastV = toHexString(lastValue);
var reV = Integer.parseInt(lastV, 16);
return reV == returnV;
}

public static byte[] getCRCByte(String command) {
var crcint = calculateCRC(command.getBytes());
var crclow = crcint & 255;
var crchigh = crcint >> 8 & 255;
return new byte[] { (byte) crchigh, (byte) crclow };
}

private static String toHexString(String s) {
StringBuilder result = new StringBuilder();

for (int i = 0; i < s.length(); ++i) {
var ch = (short) s.charAt(i);
if (ch < 0)
ch = (short) (ch + 256);

var chString = Integer.toHexString(ch);
if (chString.length() < 2)
chString = "0" + chString;

result.append(chString);
}

return result.toString();
}

private static int calculateCRC(byte[] pByte) {
try {
int len = pByte.length;
int i = 0;

int crc;
for (crc = 0; len-- != 0; ++i) {
int da = 255 & (255 & crc >> 8) >> 4;
crc <<= 4;
crc ^= crc_tb[255 & (da ^ pByte[i] >> 4)];
da = 255 & (255 & crc >> 8) >> 4;
crc <<= 4;
int temp = 255 & (da ^ pByte[i] & 15);
crc ^= crc_tb[temp];
}

int bCRCLow = 255 & crc;
int bCRCHign = 255 & crc >> 8;
if (bCRCLow == 40 || bCRCLow == 13 || bCRCLow == 10)
++bCRCLow;

if (bCRCHign == 40 || bCRCHign == 13 || bCRCHign == 10)
++bCRCHign;

crc = (255 & bCRCHign) << 8;
crc += bCRCLow;
return crc;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
71 changes: 71 additions & 0 deletions test/src/test/mpp/MPP.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package test.mpp;

import java.io.IOException;

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.RXTXPort;

public class MPP implements AutoCloseable {
private RXTXPort r;
private SerialHandler serialHandler;

public MPP(final String port) throws NoSuchPortException, PortInUseException {
r = CommPortIdentifier.getPortIdentifier(port).open(MPP.class.getSimpleName(), 1000); // calls nativeavailable
try {
/*
* read(1) on hidraw devices does not work, so read(byte[]) must be done. But
* ioctl(…FIORDCHK, 0) also does not work, so to avoid calling RXTXPort.nativeavailable
* threshold must be set. It turns out that the received data arrives in batches of 8.
*/
r.enableReceiveThreshold(8);
} catch (Exception e) {
/*
* IOException: Invalid argument in TimeoutThreshold, likewise for below. The reason
* is that RXTXPort.NativeEnableReceiveTimeoutThreshold aborts when tcgetattr() fails.
* Why Excepiton and not IOException? Because the declaration in RXTXPort.java for
* native void NativeEnableReceiveTimeoutThreshold does not say it can throw an
* exception, so it throws only RuntimeExceptions in theory and IOException in practice.
*/
}
try { // useful when the cable is unplugged or device is off
r.enableReceiveTimeout(1000);
} catch (Exception e) {} // as above
serialHandler = new SerialHandler(r.getInputStream(), r.getOutputStream());
}

public static void main(String[] args) {
final String port = args.length == 1 ? args[0] : "/dev/hidraw0";

try (MPP d = new MPP(port)) {
System.out.println("Main CPU Firmware: " + d.command("QVFW"));
System.out.println("Another CPU Firmware: " + d.command("QVFW2"));
System.out.println("Device Protocol ID: " + d.command("QPI"));
System.out.println("Device Serial Number: " + d.command("QID"));
} catch (NoSuchPortException e) {
System.err.println("No such port " + port + " " + e);
} catch (PortInUseException e) {
System.err.println("Port in use " + e);
}
}

public String command(String command) {
try {
return serialHandler.executeCommand(command);
} catch (IOException e) {
e.printStackTrace();
return "";
}
}

@Override
public void close() {
final RXTXPort localR = r;
if (localR != null) {
localR.close();
r = null;
}
serialHandler = null;
}
}
30 changes: 30 additions & 0 deletions test/src/test/mpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Introduction

This shows how to use a /dev/hidraw device in the example of idVendor=0665 (Cypress Semiconductor), idProduct=5161 (USB to Serial), bcdDevice= 0.02, USB device string Mfr=3, Product=1, SerialNumber=0.

The device is embedded in inverters known as Axpert, Effekta, MPP, Voltronic, or even having no name.

Communication with it can be done using a USB Type B cable, demonstrated herein. It supports also communication over LAN cable, bluetooth, RS232.

# Caveats imposed by the Linux Kernel

* reading one byte or writing one byte does not work.
* tcgetattr() and tcsetattr() also do not work. tcgetattr() sets errno to EINVAL, but should set it to ENOTTY, cf. https://lore.kernel.org/linux-input/[email protected]/.
* ioctl(,FIONREAD,) also does not work.
* reading data arrives in batches of 8 bytes.

# Implications for the implementation

* Do not fetch one byte with RXTXPort.getInputStream().read(), do not use RXTXPort.getOutputStream.write(byte).
* Avoid RXTXPort.nativeavailable() by utilizing enableReceiveThreshold(8). It calls tcgetattr(), which throws an exception, but nevertheless sets the threshold.
* To make progress, when the cable is unplugged, set enableReceiveTimeout(1000). It again throws an exception when invoking tcgetattr(), but nevertheless sets the timeout for select().

# Building

cd nrjavaserial/test/src
javac test/mpp/CRCUtil.java test/mpp/SerialHandler.java
java -classpath ../../build/libs/nrjavaserial-5.2.1.jar:. test/mpp/MPP.java

To utilizie /dev/hidraw1, instead of the default /dev/hidraw0, use

java -classpath ../../build/libs/nrjavaserial-5.2.1.jar:. test/mpp/MPP.java /dev/hidraw1
67 changes: 67 additions & 0 deletions test/src/test/mpp/SerialHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package test.mpp;
// adapted from https://github.com/synogen/mpp/blob/master/src/main/java/org/mppsolartest/serial/SerialHandler.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class SerialHandler {
private InputStream input;
private OutputStream output;
private int errorCount = 0;

public SerialHandler(InputStream input, OutputStream output) {
this.input = input;
this.output = output;
}

public synchronized String executeCommand(String command) throws IOException {
var result = true;
try {
output.write(command.getBytes());
var crc = CRCUtil.getCRCByte(command);
// on /dev/hidraw writing one byte fails
// write(8, "\r", 1) = -1 EINVAL (Invalid argument)
byte[] hack = new byte[crc.length + 1];
System.arraycopy(crc, 0, hack, 0, crc.length);
hack[crc.length] = '\r';
output.write(hack);
/* This could be useful, when using MPP over LAN cable, RS232 or Bluetooth
* try { // on hidraw devices ioctl(8, TCSBRK, 1) causes "Invalid argument in nativeDrain"
* output.flush();
* } catch (IOException e) { }
*/
var timeout = System.currentTimeMillis() + 3000L;
var sb = new StringBuilder();
var linebreak = false;
byte[] b = new byte[8];
outerloop:
while (System.currentTimeMillis() < timeout)
if (input.read(b) > 0) {
for (int by : b) {
if (by == 13) {
linebreak = true;
break outerloop;
}
sb.append((char) by);
}
}

if (!linebreak)
result = false;
var returnValue = sb.toString();
return CRCUtil.checkCRC(returnValue) ? returnValue.substring(1, returnValue.length() - 2) : "";
} catch (IOException e) {
result = false;
throw e;
} finally {
errorCount = result ? 0 : errorCount + 1;
if (errorCount >= 12)
System.err.println("[Serial] Communication failed " + Integer.toString(errorCount) + " times");
}
}

public int errorCount() {
return errorCount;
}
}