Code Monkey home page Code Monkey logo

react-native-usb-serialport's Introduction

react-native-usb-serialport

npm version npm downloads npm licence Platform

High performance JAVA native gateway passthrough between multi usb serial port and multi socket without JS bridge, or just use multi usb serial port or socket alone.

The time socket -> serial port -> socket:

  • JAVA native gateway takes 8ms
  • JS bridge gateway takes 300ms

The usb serial port part include .aar library from felHR85/UsbSerial

The socket part copy and modify code from [email protected]

Changelog

[2.3.0] - 2021-11-05

Remove executorService usage in writeSocketBytes, thus JAVA native gateway can have more smoother socket response rate.

[2.2.0] - 2021-09-01

High performance JAVA native gateway passthrough between socket and serialport without JS bridge

[2.1.0] - 2021-01-22

USB device access pop-up suppression

[2.0.0] - 2021-01-22

Add multi usb serial port connections support

Gateway Example As Usage

Gateway serial port and socket server side runs in your react native APP

const {DeviceEventEmitter, Platform} = require('react-native');

import {RNSerialport, actions} from 'react-native-usb-serialport';
const TcpSocketCreateServer = require('react-native-usb-serialport/src/index.js')
  .createServer;
import _ from 'lodash';

class SerialportGateway {
  static tcpSocketPort = 7700;
  static tcpSocketServer = undefined;

  // JAVA native gateway need these
  static isNativeGateway = true;
  static isNativeGatewayJsEventEmitOnSerialportData = true; // won't reduce native gateway performance

  // JS bridge gateway need these and isNativeGateway should be false
  static jsAppBus2DeviceName = {};
  static jsDeviceName2Socket = {};

  static emitter =
    Platform.OS === 'ios'
      ? {
          addListener: () => {},
          removeListener: () => {},
          emit: () => {},
        }
      : DeviceEventEmitter;

  static doInit() {
    this.emitter.addListener(
      actions.ON_SERVICE_STARTED,
      this.onServiceStarted,
      this,
    );
    this.emitter.addListener(
      actions.ON_SERVICE_STOPPED,
      this.onServiceStopped,
      this,
    );
    this.emitter.addListener(
      actions.ON_DEVICE_ATTACHED,
      this.onDeviceAttached,
      this,
    );
    this.emitter.addListener(
      actions.ON_DEVICE_DETACHED,
      this.onDeviceDetached,
      this,
    );
    this.emitter.addListener(actions.ON_ERROR, this.onError, this);
    this.emitter.addListener(actions.ON_CONNECTED, this.onConnected, this);
    this.emitter.addListener(
      actions.ON_DISCONNECTED,
      this.onDisconnected,
      this,
    );
    this.emitter.addListener(actions.ON_READ_DATA, this.onReadData, this);
    // RNSerialport.setReturnedDataType(definitions.RETURNED_DATA_TYPES.HEXSTRING);
    Platform.OS === 'android' && RNSerialport.startUsbService();

    if (Platform.OS === 'android') {
      // who would build IoT project with ios :P
      this.tcpSocketServer = TcpSocketCreateServer((socket) => {
        console.warn(
          'connected client ' + socket.remoteAddress + ':' + socket.remotePort,
        );
        if (!this.isNativeGateway) {
          // setEncoding is meaningless when not isNativeGateway
          socket.setEncoding('binary');
        }

        socket.on('data', (rawData) => {
          if (this.isNativeGateway) {
            console.warn('should not be here ');
            return;
          }

          let data;
          try {
            data = JSON.parse(rawData);
          } catch (err) {
            data = [];
            [].map.call(rawData, (value, index, str) => {
              data.push(str.charCodeAt(index));
            });
          }
          if (__DEV__) {
            console.warn('from client ' + this.byteArray2HexArray(data));
          }

          let appBus = 0; // this example passthrough assume only one serial port, so 0 here, you can customize appBus from somewhere in the data received
          let deviceName = this.jsAppBus2DeviceName[appBus];
          if (deviceName) {
            this.jsDeviceName2Socket[deviceName] = socket;
            RNSerialport.writeBytes(deviceName, rawData);
          }
        });

        socket.on('error', (error) => {
          console.warn('client error ', error);
          // socket.end();
        });

        socket.on('close', (error) => {
          console.warn(
            'closed client ' + socket.remoteAddress + ':' + socket.remotePort,
          );
          _.omitBy(this.jsDeviceName2Socket, (value) => value === socket);
          // socket.destroy();
        });
      }).listen({port: this.tcpSocketPort});

      this.tcpSocketServer.on('error', (error) => {
        console.warn('server error ', error);
      });

      this.tcpSocketServer.on('close', () => {
        console.warn('server closed');
      });
    }
  }

  static doDestroy() {
    this.emitter.removeListener(
      actions.ON_SERVICE_STARTED,
      this.onServiceStarted,
      this,
    );
    this.emitter.removeListener(
      actions.ON_SERVICE_STOPPED,
      this.onServiceStopped,
      this,
    );
    this.emitter.removeListener(
      actions.ON_DEVICE_ATTACHED,
      this.onDeviceAttached,
      this,
    );
    this.emitter.removeListener(
      actions.ON_DEVICE_DETACHED,
      this.onDeviceDetached,
      this,
    );
    this.emitter.removeListener(actions.ON_ERROR, this.onError, this);
    this.emitter.removeListener(actions.ON_CONNECTED, this.onConnected, this);
    this.emitter.removeListener(
      actions.ON_DISCONNECTED,
      this.onDisconnected,
      this,
    );
    this.emitter.removeListener(actions.ON_READ_DATA, this.onReadData, this);

    if (Platform.OS === 'android') {
      RNSerialport.disconnectAllDevices();
      RNSerialport.stopUsbService();
      if (this.tcpSocketServer) {
        this.tcpSocketServer.destroy();
        this.tcpSocketServer = undefined;
      }
    }
  }

  static onServiceStarted(response) {
    console.warn('USB service started');

    if (this.isNativeGateway) {
      RNSerialport.setIsNativeGateway(this.isNativeGateway);
      RNSerialport.setIsNativeGatewayJsEventEmitOnSerialportData(
        this.isNativeGatewayJsEventEmitOnSerialportData,
      );
    }

    if (response.deviceAttached) {
      this.onDeviceAttached();
    }
  }

  static onServiceStopped() {
    console.warn('USB service stopped');
  }

  static onDeviceAttached(deviceName) {
    console.warn('USB device attached ' + deviceName);
    this.fillDeviceListAndConnect();
  }

  static onDeviceDetached(deviceName) {
    console.warn('USB device detached ' + deviceName);
  }

  static onConnected(deviceName) {
    console.warn(deviceName, 'USB serialPort connected');

    let busIndex = this.getBusIndexFromDevPath(deviceName);

    if (this.isNativeGateway) {
      RNSerialport.appBus2DeviceNamePut(busIndex, deviceName);
    } else {
      this.jsAppBus2DeviceName[busIndex] = deviceName;
    }
  }
  static onDisconnected(deviceName) {
    console.warn(deviceName, 'USB serialPort disconnected');

    if (this.isNativeGateway) {
      // will auto appBus2DeviceName.values().removeIf(deviceName::equals) in RNSerialportModule.java
    } else {
      _.omitBy(this.jsAppBus2DeviceName, (value) => value === deviceName);
    }
  }

  static onReadData(data) {
    if (__DEV__) {
      console.warn('onUsbSerialportReceiveData', {
        linuxDevPath: data.deviceName,
        valueArray: this.byteArray2HexArray(data.payload),
      });
    }

    if (this.isNativeGateway) {
      // will comes here when isNativeGatewayJsEventEmitOnSerialportData is true
      this.emitter.emit('YOUR_APP_MAYBE_ALSO_NEED_JS_onSerialportReceiveData', {
        linuxDevPath: data.deviceName,
        valueArray: data.payload,
      });
    } else {
      let socket = this.jsDeviceName2Socket[data.deviceName];
      if (socket) {
        socket.write(data.payload);
      }
    }
  }

  static onError(error) {
    console.error(error);
  }

  // deviceName of usb serial device on your board, maybe replaced in fillDeviceListAndConnect()
  static usbSerialPath = [
    '/dev/bus/usb/001/003',
    '/dev/bus/usb/001/005',
    '/dev/bus/usb/001/007',
    '/dev/bus/usb/001/009',
  ];

  static getDevPathFromBusIndex(busIndex) {
    return this.usbSerialPath[busIndex] || this.usbSerialPath[0];
  }

  static getBusIndexFromDevPath(devPath) {
    let index = this.usbSerialPath.indexOf(devPath);
    return index === -1 ? 0 : index;
  }

  static fillDeviceListAndConnect() {
    RNSerialport.getDeviceList().then((list) => {
      // console.warn(list);
      // [
      //     {
      //         "name": "/dev/bus/usb/001/010",
      //         "productId": 20752,
      //         "vendorId": 1578
      //     },
      //     {
      //         "name": "/dev/bus/usb/001/009",
      //         "productId": 29987,
      //         "vendorId": 6790 // ch34x
      //     },
      //     {
      //         "name": "/dev/bus/usb/001/005",
      //         "productId": 29987,
      //         "vendorId": 6790 // ch34x
      //     },
      //     {
      //         "name": "/dev/bus/usb/002/002",
      //         "productId": 46880,
      //         "vendorId": 3034
      //     },
      //     {
      //         "name": "/dev/bus/usb/001/003",
      //         "productId": 29987,
      //         "vendorId": 6790 // ch34x
      //     },
      //     {
      //         "name": "/dev/bus/usb/001/007",
      //         "productId": 29987,
      //         "vendorId": 6790 // ch34x
      //     }
      // ]

      let ch34xUsbSerialPath = [];
      list.map((item) => {
        if (item.vendorId === 6790) {
          ch34xUsbSerialPath.push(item.name);
        }
      });

      // If you use Beckhoff like module, pull and plug some modules,
      // their increased name should also be sorted.
      ch34xUsbSerialPath = ch34xUsbSerialPath.sort();

      ch34xUsbSerialPath.map((path, index) => {
        this.usbSerialPath[index] = path;
      });

      this.enableLinuxDev({
        devPaths: this.usbSerialPath,
      });
    });
  }

  static enableLinuxDev({devPaths}) {
    if (Platform.OS === 'android') {
      devPaths.map((path) => {
        RNSerialport.connectDevice(path, 115200);
      });
    }
  }

  static disableLinuxDev({devPaths}) {
    if (Platform.OS === 'android') {
      devPaths.map((path) => {
        RNSerialport.disconnectDevice(path);
      });
    }
  }

  static padHexString(string) {
    if (string.length === 1) {
      return '0' + string;
    } else {
      return string;
    }
  }

  static hexString2ByteArray(string) {
    let array = [];
    [].map.call(string, (value, index, str) => {
      if (index % 2 === 0) {
        array.push(parseInt(value + str[index + 1], 16));
      }
    });

    return array;
  }

  static byteArray2HexString(bytes) {
    return bytes
      .map((byte) => this.padHexString((byte & 0xff).toString(16)))
      .toString()
      .replace(/,/g, '')
      .toUpperCase();
  }

  static byteArray2HexArray(bytes) {
    return bytes
      .map((byte) => this.padHexString((byte & 0xff).toString(16)))
      .toString();
  }
}

module.exports = SerialportGateway;

Gateway socket client side runs in nodejs

HOST=192.168.1.108 PORT=7700 node app/utils/tcpSocketClientTest.js
  • 192.168.1.108 is the IP address of your phone which runs APP
  • 7700 equals tcpSocketPort inside SerialportGateway.js above
  • tcpSocketClientTest.js is below
var net = require('net');
var port = process.env.PORT || 7700;
var host = process.env.HOST || '127.0.0.1';
var client = new net.Socket();

function padHexString(string) {
  if (string.length === 1) {
    return '0' + string;
  } else {
    return string;
  }
}

function byteArray2HexArray(bytes) {
  return bytes
    .map((byte) => padHexString((byte & 0xff).toString(16)))
    .toString();
}

function sleepMs(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

const DELAY_MS_SOCKET = 0;
let isBusy = true;

client.setEncoding('binary');

client.connect(port, host, async function () {
  console.log('connected server ' + host + ':' + port);

  for (let i = 0; i < 10; i++) {
    client.write(Buffer.from([0xaa, 0, 1, 2, 3, 4, 5, 6, 7, 0x55]));
    while (isBusy) {
      await sleepMs(DELAY_MS_SOCKET);
    }
    isBusy = true;
  }
  return;
});

client.on('data', function (rawData) {
  isBusy = false;
  let data;
  try {
    data = JSON.parse(rawData);
  } catch (err) {
    data = [];
    [].map.call(rawData, (value, index, str) => {
      data.push(str.charCodeAt(index));
    });
  }
  console.log('from server ' + byteArray2HexArray(data));
});

client.on('error', function (exception) {
  console.log('socket error ' + exception);
  client.destory();
});

client.on('close', function () {
  console.log('closed server ' + host + ':' + port);
});

Customize JAVA native gateway

Write your own YOUR_RN_PROJECT/app/utils/SerialportGateway.java, and run below in YOUR_RN_PROJECT/:

ln -sf ../../../../../../../../../app/utils/SerialportGateway.java node_modules/react-native-usb-serialport/android/src/main/java/com/melihyarikkaya/rnserialport/Gateway.java

Documents of gateway

Ref to Gateway Example As Usage above with methods below:

setIsNativeGateway
setIsNativeGatewayJsEventEmitOnSerialportData
appBus2DeviceNamePut

Documents of socket part

Ref to Gateway Example As Usage above and README of react-native-tcp-socket.

Add multi usb serial port connections support after forked from react-native-serialport

-  $ npm install --save react-native-serialport
+  $ npm install --save react-native-usb-serialport

-  import { RNSerialport, definitions, actions } from "react-native-serialport";
+  import { RNSerialport, definitions, actions } from "react-native-usb-serialport";

-  RNSerialport.disconnect();
+  RNSerialport.disconnectDevice('/dev/bus/usb/001/007');

+  RNSerialport.disconnectAllDevices();

-  RNSerialport.isOpen();
+  RNSerialport.isOpen('/dev/bus/usb/001/007');

-  RNSerialport.writeBytes([0, 1, 2, 3]);
+  RNSerialport.writeBytes('/dev/bus/usb/001/007', [0, 1, 2, 3]);

-  RNSerialport.writeString('HELLO');
+  RNSerialport.writeString('/dev/bus/usb/001/007', 'HELLO');

-  RNSerialport.writeBase64('SEVMTE8=');
+  RNSerialport.writeBase64('/dev/bus/usb/001/007', 'SEVMTE8=');

-  RNSerialport.writeHexString('48454C4C4F');
+  RNSerialport.writeHexString('/dev/bus/usb/001/007', '48454C4C4F');

Add the following android intent to android/app/src/main/AndroidManifest.xml so that permissions are remembered on android (VS not remembered by usbManager.requestPermission())

<activity
    ...
    ...
    >
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>
    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/usb_device_filter" />
</activity>

And create a filter file in android/app/src/main/res/xml/usb_device_filter.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <!-- vendor-id 6790 means ch34x usb serial port -->
    <usb-device vendor-id="6790" product-id="29987" />
</resources>

The vendor-id and product-id here have to be given in decimal, and they can be get by RNSerialport.getDeviceList().then(console.warn)

Documents of usb serial port part (note above Fork diff)

  1. Download & Installation
  2. Auto Connection
  3. Manual Connection
  4. Methods
  5. Error Descriptions

DEFAULT DEFINITIONS

KEY VALUE
RETURNED DATA TYPE INT ARRAY (Options: INTARRAY, HEXSTRING)
BAUND RATE 9600
AUTO CONNECT BAUD RATE 9600
PORT INTERFACE -1
DATA BIT 8
STOP BIT 1
PARITY NONE
FLOW CONTROL OFF
DRIVER AUTO

Java Package Name

com.melihyarikkaya.rnserialport

Donate

To support my work, please consider donate.

  • ETH: 0xd02fa2738dcbba988904b5a9ef123f7a957dbb3e

react-native-usb-serialport's People

Contributors

flyskywhy avatar melihyarikkaya avatar zl810881283 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.