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

expo-jl-ota

v0.0.6

Published

My new module

Readme

Expo 杰理OTA插件

针对JL(杰理)芯片的OTA固件更新的Expo插件。支持带有进度回调和错误处理的Android和iOS平台,支持与expo-bluetooth混用。

| | Android | iOS | |----------|---------|-------| | 版本支持 | SDK 21+ | 12.0+ |


安装

与最新的JL OTA插件包集成

  • 安卓 SDK V1.9.2
  • IOS SDK V2.3.1

Android杰理官方github仓库地址 IOS杰理官方github仓库地址


下载

在裸React Native项目中安装

对于裸React Native项目,您必须确保在继续之前已安装并配置了expo

添加包到npm依赖

npm install expo-jl-ota

Android配置

android/app/src/main/AndroidManifest.xml中添加必要的权限:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

iOS配置

安装npm包后运行npx pod-install

ios/YourApp/Info.plist中添加蓝牙权限:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用需要蓝牙权限来连接和更新设备固件</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>此应用需要蓝牙权限来连接和更新设备固件</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>此应用需要位置权限来扫描蓝牙设备</string>

使用方法

基本用法

import React, { useEffect } from 'react';
import { Alert, Platform, PermissionsAndroid } from 'react-native';
import ExpoJlOta from 'expo-jl-ota';

export default function App() {
  useEffect(() => {
    setupEventListeners();
    requestPermissions();
  }, []);

  const setupEventListeners = () => {
    // 监听OTA进度事件
    ExpoJlOta.addListener('otaProgress', (event) => {
      console.log(`OTA Progress: ${event.progress}%, Status: ${event.status}`);
    });

    // 监听连接状态事件
    ExpoJlOta.addListener('connectionStatus', (event) => {
      console.log('Connection status:', event.connected);
    });

    // 监听错误事件
    ExpoJlOta.addListener('error', (event) => {
      Alert.alert('Error', event.message);
    });
  };

  const requestPermissions = async () => {
    if (Platform.OS === 'android') {
      const permissions = [
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
        PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
      ];

      const granted = await PermissionsAndroid.requestMultiple(permissions);
      const allGranted = Object.values(granted).every(
        (permission) => permission === PermissionsAndroid.RESULTS.GRANTED
      );
      
      if (!allGranted) {
        Alert.alert('Permissions Required', 'Please grant all required permissions for OTA functionality.');
      }
    }
  };

  const startOtaUpdate = async () => {
    try {
      // 开始扫描设备
      await ExpoJlOta.startScan();
      
      // 连接设备(使用实际设备UUID)
      const deviceUuid = Platform.OS === 'android' 
        ? '00:00:00:55:FB:D4' 
        : '2B3681AF-B077-297D-D291-FA4A908CE06A';
      
      const connected = await ExpoJlOta.connectDevice(deviceUuid);
      if (!connected) {
        Alert.alert('Error', 'Failed to connect to device');
        return;
      }

      // 获取设备信息
      await ExpoJlOta.getDeviceInfo();

      // 开始OTA升级
      const firmwarePath = '/path/to/your/firmware.ufw';
      const result = await ExpoJlOta.startOtaUpdate(deviceUuid, firmwarePath);
      
      if (result) {
        Alert.alert('Success', 'OTA update started');
      } else {
        Alert.alert('Error', 'Failed to start OTA update');
      }
    } catch (error) {
      console.error('OTA Error:', error);
      Alert.alert('Error', 'OTA operation failed');
    }
  };

  return (
    // 你的UI组件
  );
}

完整示例

import React, { useState, useEffect } from 'react';
import { 
  Alert, 
  Button, 
  SafeAreaView, 
  ScrollView, 
  Text, 
  View, 
  StyleSheet,
  Platform,
  PermissionsAndroid
} from 'react-native';
import * as FileSystem from 'expo-file-system';
import ExpoJlOta from 'expo-jl-ota';

export default function App() {
  const [otaProgress, setOtaProgress] = useState<number>(0);
  const [otaStatus, setOtaStatus] = useState<string>('');
  const [isConnected, setIsConnected] = useState<boolean>(false);
  const [permissionsGranted, setPermissionsGranted] = useState<boolean>(false);

  useEffect(() => {
    setupEventListeners();
    requestPermissions();
  }, []);

  const setupEventListeners = () => {
    // 监听OTA进度事件
    ExpoJlOta.addListener('otaProgress', (event) => {
      setOtaProgress(event.progress);
      setOtaStatus(event.status);
      console.log(`OTA Progress: ${event.progress}%, Status: ${event.status}`);
    });

    // 监听连接状态事件
    ExpoJlOta.addListener('connectionStatus', (event) => {
      setIsConnected(event.connected);
      console.log('Connection status:', event.connected);
    });

    // 监听错误事件
    ExpoJlOta.addListener('error', (event) => {
      Alert.alert('Error', event.message);
      console.error('OTA Error:', event.message);
    });

    // 监听设备信息事件
    ExpoJlOta.addListener('deviceInfo', (event) => {
      console.log('Device info:', event);
    });
  };

  const requestPermissions = async () => {
    try {
      if (Platform.OS === 'android') {
        const permissions = [
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
          PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
          PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        ];

        const granted = await PermissionsAndroid.requestMultiple(permissions);
        const allGranted = Object.values(granted).every(
          (permission) => permission === PermissionsAndroid.RESULTS.GRANTED
        );
        
        setPermissionsGranted(allGranted);
        
        if (!allGranted) {
          Alert.alert(
            'Permissions Required',
            'Please grant all required permissions for OTA functionality.',
            [{ text: 'OK' }]
          );
        }
      } else {
        // iOS permissions are handled in Info.plist
        setPermissionsGranted(true);
      }
    } catch (error) {
      console.error('Error requesting permissions:', error);
      setPermissionsGranted(false);
    }
  };

  const startOtaUpdate = async () => {
    try {
      if (!permissionsGranted) {
        Alert.alert('Error', 'Please grant all required permissions first');
        return;
      }

      // 复制固件文件到应用目录
      const fileName = 'update.ufw';
      const firmwarePath = `${FileSystem.documentDirectory}${fileName}`;
      
      // 检查文件是否已存在
      const fileInfo = await FileSystem.getInfoAsync(firmwarePath);
      if (!fileInfo.exists) {
        Alert.alert(
          'Firmware File Required',
          'Please place the firmware file in the app directory.',
          [{ text: 'OK' }]
        );
        return;
      }

      const deviceUuid = Platform.OS === 'android' 
        ? '00:00:00:55:FB:D4' 
        : '2B3681AF-B077-297D-D291-FA4A908CE06A';

      console.log('Starting OTA update with:', { deviceUuid, firmwarePath });
      
      const result = await ExpoJlOta.startOtaUpdate(deviceUuid, firmwarePath);
      console.log('OTA update started:', result);
      
      if (result) {
        Alert.alert('OTA Update', 'OTA update started successfully');
      } else {
        Alert.alert('Error', 'Failed to start OTA update');
      }
    } catch (error) {
      console.error('Error starting OTA update:', error);
      Alert.alert('Error', 'Failed to start OTA update');
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        <Text style={styles.header}>Expo JL OTA Example</Text>
        
        <View style={styles.group}>
          <Text style={styles.groupHeader}>OTA Status</Text>
          <Text style={styles.text}>Progress: {otaProgress}%</Text>
          <Text style={styles.text}>Status: {otaStatus || 'Idle'}</Text>
          <Text style={styles.text}>Connected: {isConnected ? 'Yes' : 'No'}</Text>
          <Text style={styles.text}>Permissions: {permissionsGranted ? 'Granted' : 'Not Granted'}</Text>
        </View>

        <View style={styles.group}>
          <Text style={styles.groupHeader}>OTA Operations</Text>
          <Button title="Start OTA Update" onPress={startOtaUpdate} />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#eee',
  },
  scrollView: {
    flex: 1,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    margin: 20,
    textAlign: 'center',
  },
  group: {
    margin: 20,
    backgroundColor: '#fff',
    borderRadius: 10,
    padding: 20,
  },
  groupHeader: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 15,
  },
  text: {
    fontSize: 16,
    marginBottom: 8,
  },
});

API参考

方法

  • startScan(): Promise<boolean> - 开始扫描设备
  • connectDevice(uuid: string): Promise<boolean> - 连接指定UUID的设备
  • getDeviceInfo(): Promise<boolean> - 获取设备信息
  • startOtaUpdate(uuid: string, filePath: string): Promise<boolean> - 开始OTA升级
  • cancelOtaUpdate(): Promise<boolean> - 取消OTA升级

事件

  • otaProgress - OTA进度更新事件
  • otaStatus - OTA状态更新事件
  • connectionStatus - 连接状态更新事件
  • error - 错误事件
  • deviceInfo - 设备信息事件
  • deviceInfoError - 设备信息错误事件

事件监听

// 添加事件监听器
ExpoJlOta.addListener('otaProgress', (event) => {
  console.log(`Progress: ${event.progress}%, Status: ${event.status}`);
});

// 移除事件监听器
const subscription = ExpoJlOta.addListener('otaProgress', callback);
subscription.remove();

注意事项

  1. 权限要求: 确保在Android和iOS上正确配置了蓝牙和位置权限
  2. 固件文件: 确保UFW固件文件路径正确且文件存在
  3. 设备UUID: 使用正确的设备UUID进行连接
  4. 错误处理: 始终监听错误事件并适当处理
  5. 内存管理: 在组件卸载时移除事件监听器

贡献

欢迎贡献!请参考贡献指南中描述的指导原则。