Fazecast / jSerialComm

Platform-independent serial port access for Java
GNU Lesser General Public License v3.0
1.35k stars 287 forks source link

sometimes send message to serial port got no response. #443

Closed mike1023 closed 2 years ago

mike1023 commented 2 years ago

i buy a bluetooth device with HID module, i use it to simulate a bluetooth keyboard, now i want to use it to control my iPhone with VoiceOver. it needs to send some key message. such as "->" to select next item, "<-" to select preview item, "CapsLock + SpaceBar" to activate an item.

now, if i send a single key, it works well, such as "<-" or "->", but if i send two or more key, it is unstable, sometimes work, sometimes not work.

here is some useful link: https://support.apple.com/en-au/guide/iphone/iph6c494dc6/ios

OS system: macOS Monterey 12.4(Intel) Java SDK: 1.8 jSerialComm:2.9.2

package com.mc;
import com.fazecast.jSerialComm.*;

import java.util.Scanner;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class Main {

    private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
//    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    public static String bytesToHex(byte[] bytes) {
        byte[] hexChars = new byte[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars, StandardCharsets.UTF_8);
    }

//    public static String bytesToHex(byte[] bytes) {
//        char[] hexChars = new char[bytes.length * 2];
//        for (int j = 0; j < bytes.length; j++) {
//            int v = bytes[j] & 0xFF;
//            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
//            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
//        }
//        return new String(hexChars);
//    }

    public static void main(String[] args) {
        SerialPort[] ports = SerialPort.getCommPorts();
        for (SerialPort port:ports) {
            System.out.println(port.getSystemPortName());
        }
        SerialPort port = SerialPort.getCommPort("tty.usbserial-14430");
        port.flushIOBuffers();
        System.out.println(port.isOpen());
        if (!port.isOpen()) {
            if (!port.openPort()) {
                System.out.println("open port failed");
                return;
            }
        }

        boolean res = port.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
        if (res) {
            System.out.println("port init success");
        } else {
            System.out.println("port init fail");
        }
        port.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 0, 3);

        OutputStream out = port.getOutputStream();

        port.addDataListener(new SerialPortDataListener() {
            @Override
            public int getListeningEvents() {
                return SerialPort.LISTENING_EVENT_DATA_WRITTEN;
            }

            @Override
            public void serialEvent(SerialPortEvent serialPortEvent) {
                System.out.println(serialPortEvent.getEventType());
                byte[] newData = serialPortEvent.getReceivedData();
                System.out.println("Received data of size: " + newData.length);
                for (int i = 0; i < newData.length; ++i)
                    System.out.print((char)newData[i]);
                System.out.println("\n");
            }
        });

        String s = "0C00A10100001A00000000000C00A1010000000000000000";  // w

        Scanner sc = new Scanner(System.in);
        String action = "";
        String command = "";
        while (!action.equals("q")) {
            System.out.print("please select action:(1: next item. 2: preview item. 3: activate item. 4: back to home. 5: output 'w'. q: quit.):");
            action = sc.nextLine();
            switch (action) {
                case "1":
                    command = "0C00A10100004F00000000000C00A1010000000000000000";
                    break;
                case "2":
                    command = "0C00A10100005000000000000C00A1010000000000000000";
                    break;
                case "3":
                    command = "0C00A10100003900000000000C00A1010000392C000000000C00A10100003900000000000C00A1010000000000000000";
                    break;
                case "4":
                    command = "0C00A10100003900000000000C00A1010000390B000000000C00A10100003900000000000C00A1010000000000000000";
                    break;
                case "5":
                    command = "0C00A10100001A00000000000C00A1010000000000000000";
                    break;
                default:
            }
            try {
                byte[] data = hexStringToByteArray(command);
                System.out.println("data length: " + data.length);
                out.write(data);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //while quit, close port.
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        port.closePort();
    }
}
hedgecrw commented 2 years ago

I'm not sure if this has anything to do with the underlying library, but from a quick glance at the code you posted:

1) You're adding an event listener port.addDataListener() but not really doing anything with it. Additionally, the serialEvent() callback you have written has nothing to do with the event type you're listening for (LISTENING_EVENT_DATA_WRITTEN). I would just remove this entire data listener. It probably doesn't make sense for your application. 2) You're setting a blocking write timeout which might be problematic when the underlying device is Bluetooth-based since the transmission time may be non-deterministic. Again, I don't think that this is necessary for your application, so I would remove it. 3) If you're sending a bunch of keys in quick succession, perhaps the underlying driver is combining them into a single Bluetooth packet and only the first key is getting recognized by the iPhone? If that's the case, you may need to wait for a confirmation status message from the first keypress (if your device sends such messages). 4) Barring all of the above, you may need to write some manual test cases to see if you can reproducibly figure out what works and what doesn't (for example, write one long message string at one time that contains 2-3 commands inside of it, write 2-3 commands in a row in quick succession, etc.).

mike1023 commented 2 years ago

hello, thanks for your response, i have removed addDataListener() and blocking write timeout. for third point you mentioned, if i send a bunch of keys in quick succession, it is worked sometimes, if the driver only recognized the first key, it should never worked. and, how can i get the status from my device whether it have received all message?

i write a loop to send a single key press, it works well.

//0C00A10100005000000000000C00A1010000000000000000 means press down "right arrow"  and then lift up it.
for (int i = 0; i < 20; i++) {
      byte[] data = hexStringToByteArray("0C00A10100005000000000000C00A1010000000000000000");
      out.write(data);
      out.flush();
}
mike1023 commented 2 years ago

@hedgecrw i found the root cause, i separate the command to several parts, and after send one, then i make program sleep for 200ms.

command = "0C00A1010000390000000000 0C00A1010000392C00000000 0C00A1010000390000000000 0C00A1010000000000000000";

String[] strArr = command.split(" ");
                ArrayList byteArr = new ArrayList();
                for (int i = 0; i < strArr.length; i++) {
                    String s = strArr[i];
                    byte[] data = hexStringToByteArray(s);
                    byteArr.add(data);
                }

                for (int i = 0; i < byteArr.size(); i++) {
                    out.write((byte[]) byteArr.get(i));
                    out.flush();
                    Thread.sleep(200);
                }

i think maybe bluetooth data need a delay.