npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-jl-ota

v0.1.0

Published

React Native / Expo OTA firmware updates for JieLi (JL) BLE chips, bridged to react-native-ble-plx

Readme

react-jl-ota

React Native / Expo OTA firmware-update library for JieLi (杰理 / JL) Bluetooth chips, wrapping the official jl_bt_ota Android SDK.

It is built for apps that already manage their own BLE connection with react-native-ble-plx. The native module runs only the JieLi OTA (RCSP) protocol; your JS keeps owning the BLE link. No second GATT connection, no duplicate scanning, no conflict with your existing device-controller app.

Platform support: Android only. The JieLi OTA SDK is an Android .aar. The library is importable on iOS/web but every OTA call rejects with ERR_UNSUPPORTED_PLATFORM.


How it works

The JieLi BluetoothOTAManager is an abstract transport — it implements the OTA protocol but delegates the actual reading/writing of bytes. This library plugs that transport into your react-native-ble-plx connection:

 JS (your app, react-native-ble-plx)          Native (react-jl-ota + JL SDK)
 ────────────────────────────────────         ──────────────────────────────
 scan + connect the device          ─────────▶  (already connected)
 subscribe to AE02 notify char
 onOtaWriteRequest  ◀───────────────────────── SDK wants to send bytes
   → write bytes to AE01
 AE02 notification ──── notifyData() ────────▶  SDK parses device reply
 startOta({ deviceAddress, … })     ─────────▶  BluetoothOTAManager.startOTA()
 onOtaProgress / resolve(startOta)  ◀───────── IUpgradeCallback

JieLi OTA GATT profile (exported as JL_OTA_UUIDS):

| Role | UUID | |-------------|----------------------------------------| | Service | 0000ae00-0000-1000-8000-00805f9b34fb | | Write (AE01)| 0000ae01-0000-1000-8000-00805f9b34fb | | Notify(AE02)| 0000ae02-0000-1000-8000-00805f9b34fb | | CCCD | 00002902-0000-1000-8000-00805f9b34fb |

For a deeper explanation of the SDK internals, the RCSP protocol, dual-bank reconnection and error codes, see docs/JL_OTA_INTERNALS.md.


Installation

This is a local Expo module that bundles the vendor AAR. The AAR is already in android/libs/jl_bt_ota_V1.11.0_11015-release.aar.

In your app:

npm install react-jl-ota react-native-ble-plx
npx expo prebuild   # config plugin / autolinking picks up the native module

react-native-ble-plx requires a custom dev client (it does not run in Expo Go). Add its config plugin in app.json and request the BLE runtime permissions (BLUETOOTH_SCAN, BLUETOOTH_CONNECT, and location on Android < 12).

Min Android SDK 21. The vendor AAR ships native .so files for armeabi-v7a, arm64-v8a, x86, x86_64 only.


Quick start

import { Buffer } from 'buffer';
import { BleManager, Device } from 'react-native-ble-plx';
import * as JlOta from 'react-jl-ota';

const ble = new BleManager();

/**
 * Run an OTA on an already-connected ble-plx Device.
 * @param device  a connected react-native-ble-plx Device
 * @param filePath absolute path to the .ufw firmware file on disk
 */
export async function runOta(device: Device, filePath: string) {
  await device.discoverAllServicesAndCharacteristics();

  // 1) Forward every AE02 notification into the OTA engine.
  const notifySub = device.monitorCharacteristicForService(
    JlOta.JL_OTA_UUIDS.service,
    JlOta.JL_OTA_UUIDS.notify,
    (error, characteristic) => {
      if (error || !characteristic?.value) return;
      JlOta.notifyData(characteristic.value); // ble-plx value is already base64
    }
  );

  // 2) When the SDK wants to send bytes, write them to AE01.
  const writeSub = JlOta.onWriteRequest(async ({ dataBase64 }) => {
    try {
      await device.writeCharacteristicWithoutResponseForService(
        JlOta.JL_OTA_UUIDS.service,
        JlOta.JL_OTA_UUIDS.write,
        dataBase64 // base64 in, ble-plx writes the raw bytes
      );
    } catch (e) {
      console.warn('AE01 write failed', e);
    }
  });

  // 3) Progress + lifecycle.
  const progressSub = JlOta.onProgress(({ type, progress }) =>
    console.log(`OTA phase ${type}: ${progress.toFixed(1)}%`)
  );

  try {
    const result = await JlOta.startOta({
      deviceAddress: device.id,        // on Android, Device.id is the MAC
      filePath,
      mtu: 20,                         // bump after negotiating a larger MTU (see below)
    });
    console.log('OTA complete', result);
  } finally {
    notifySub.remove();
    writeSub.remove();
    progressSub.remove();
    JlOta.release();
  }
}

Firmware sources

startOta accepts exactly one of:

JlOta.startOta({ deviceAddress, filePath: '/data/.../app.ufw' });   // local file
JlOta.startOta({ deviceAddress, fileBase64: '<base64 of .ufw>' });  // in-memory bytes
JlOta.startOta({ deviceAddress, url: 'https://cdn/app.ufw' });      // lib downloads first

Going faster (MTU)

By default the SDK sends 20-byte packets (mtu: 20), which works on every device but is slow. To speed up:

const negotiated = await device.requestMTU(247); // ble-plx
await JlOta.startOta({
  deviceAddress: device.id,
  filePath,
  mtu: negotiated.mtu - 3, // never exceed (MTU − 3)
});

Dual-bank devices & reconnection

Some JieLi devices reboot into a loader and re-advertise with a changed MAC address mid-OTA. When that happens the SDK emits onOtaNeedReconnect with the new address; your JS must reconnect to it and tell the engine:

JlOta.onNeedReconnect(async ({ reconnectAddress }) => {
  if (!reconnectAddress) return;
  const reconnected = await ble.connectToDevice(reconnectAddress);
  await reconnected.discoverAllServicesAndCharacteristics();
  // re-attach the AE02 monitor on the new device, then:
  JlOta.notifyConnectionState(true);
});

If your firmware engineer confirmed the device does not change address (single bank), you can ignore onOtaNeedReconnect. Confirm the OTA mode with them — this is the single most common source of "OTA stalls after ~95%" issues.


API reference

Functions

| Function | Description | |---|---| | configure(options) | Set engine options (see below). Optional. | | startOta(options): Promise<OtaResult> | Start OTA; resolves on success, rejects on error/cancel. | | cancelOta(): Promise<void> | Cancel the running OTA. | | notifyData(base64) | Forward an AE02 notification into the SDK. | | notifyConnectionState(connected) | Report the BLE link up/down. | | isOta(): boolean | True while an OTA is running. | | getDeviceInfo(): Promise<DeviceInfo \| null> | Cached firmware version info. | | release() | Free native resources. |

configure / startOta options

| Key | Type | Default | Notes | |---|---|---|---| | deviceAddress | string | — | Required for startOta. Device MAC. | | filePath / fileBase64 / url | string | — | Firmware source (pick one). | | mtu | number | 20 | Bytes per write; ≤ negotiated MTU − 3. | | useSpp | boolean | false | Use classic SPP instead of BLE. | | useAuthDevice | boolean | false | Device authentication (ask firmware eng.). | | useReconnect | boolean | false | Let the SDK manage reconnect (advanced). | | timeoutMs | number | SDK default | Command timeout. | | bleIntervalMs | number | SDK default | Connection-interval hint. |

Event subscriptions

Each returns an EventSubscription — call .remove() when done.

onWriteRequest, onProgress, onStateChange, onNeedReconnect, onConnectRequest, onDisconnectRequest, onConnectionStateChange, onMandatoryUpgrade, onError.

Constants

JL_OTA_UUIDS { service, write, notify, clientConfig }, JL_MTU_MIN (20), JL_MTU_MAX (509).


Troubleshooting

| Symptom | Likely cause | |---|---| | ERR_BAD_ADDRESS | deviceAddress is not a valid MAC. Use device.id on Android. | | OTA never starts / times out | AE02 monitor not attached, or onOtaWriteRequest not writing to AE01. | | Stalls near the end, then errors | Dual-bank device changed address — handle onOtaNeedReconnect. | | Direct local .aar … not supported at build | See build notes. | | Crash on x86 emulator only | Unsupported ABI — test on arm64 or a real device. |

Enable verbose SDK logs by checking logcat for the JL_* tags during OTA.


License

MIT (this wrapper). The bundled JieLi jl_bt_ota SDK is © JieLi Technology and governed by its own license — see https://gitee.com/Jieli-Tech/Android-JL_OTA.