feat(companion): implement ble

This commit is contained in:
2026-01-12 22:24:33 +00:00
parent 8305726f83
commit 75cfbe8dd4
55 changed files with 3457 additions and 142 deletions

View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

View File

@@ -0,0 +1,32 @@
# @aris/ble
BLE peripheral module for the Aris companion app.
# API documentation
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/example.com/)
- [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/example.com/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects — it is likely to be included in an upcoming Expo SDK release.
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npm install @aris/ble
```
### Configure for iOS
Run `npx pod-install` after installing the npm package.
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide]( https://github.com/expo/expo#contributing).

View File

@@ -0,0 +1,7 @@
{
"platforms": ["apple"],
"apple": {
"modules": ["ArisBleModule"],
"appDelegateSubscribers": ["ArisBleAppDelegateSubscriber"]
}
}

View File

@@ -0,0 +1,35 @@
{
"name": "@aris/ble",
"version": "0.1.0",
"description": "BLE peripheral module for the Aris companion app.",
"author": "Iris",
"homepage": "https://example.com",
"main": "build/index.js",
"types": "build/index.d.ts",
"sideEffects": false,
"exports": {
"./package.json": "./package.json",
".": {
"types": "./build/index.d.ts",
"default": "./build/index.js"
}
},
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": ["react-native", "expo", "ble"],
"license": "MIT",
"devDependencies": {
"expo-module-scripts": "^5.0.8"
},
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
}

View File

@@ -0,0 +1,7 @@
export { Ble, defaultBleState } from "./native";
export { BleBluetoothState } from "./types";
export type {
BleNativeModuleEvents,
BleStatePayload,
BleUuids,
} from "./types";

View File

@@ -0,0 +1,90 @@
import { NativeModule, requireNativeModule } from "expo";
import { Platform } from "react-native";
import type { EventSubscription } from "expo-modules-core";
import {
BleBluetoothState,
type BleNativeModuleEvents,
type BleStatePayload,
type BleUuids,
} from "./types";
declare class ArisBleNativeModule extends NativeModule<BleNativeModuleEvents> {
start: () => void;
stop: () => void;
setAdvertisingEnabled: (enabled: boolean) => void;
setWifiRequested: (requested: boolean) => void;
sendOpaque: (payload: string, msgType?: number) => void;
getState: () => BleStatePayload;
serviceUUID: string;
feedTxUUID: string;
controlRxUUID: string;
wifiRequestTxUUID: string;
}
const isSupported = Platform.OS === "ios";
const nativeModule = isSupported
? requireNativeModule<ArisBleNativeModule>("ArisBle")
: null;
export const defaultBleState: BleStatePayload = {
bluetoothState: BleBluetoothState.Unknown,
advertisingEnabled: true,
isAdvertising: false,
isSubscribed: false,
subscribedCount: 0,
lastMsgIdSent: 0,
lastPingAt: null,
lastCommand: null,
notifyQueueDepth: 0,
droppedNotifyPackets: 0,
lastNotifyAt: null,
lastDataAt: null,
wifiRequested: false,
};
const emptyUuids: BleUuids = {
serviceUUID: "",
feedTxUUID: "",
controlRxUUID: "",
wifiRequestTxUUID: "",
};
type BleApi = {
isSupported: boolean;
start: () => void;
stop: () => void;
setAdvertisingEnabled: (enabled: boolean) => void;
setWifiRequested: (requested: boolean) => void;
sendOpaque: (payload: string, msgType?: number) => void;
getState: () => BleStatePayload;
addStateListener: (
listener: (state: BleStatePayload) => void,
) => EventSubscription | undefined;
getUuids: () => BleUuids;
};
export const Ble: BleApi = {
isSupported,
start: () => nativeModule?.start?.(),
stop: () => nativeModule?.stop?.(),
setAdvertisingEnabled: (enabled: boolean) =>
nativeModule?.setAdvertisingEnabled?.(enabled),
setWifiRequested: (requested: boolean) =>
nativeModule?.setWifiRequested?.(requested),
sendOpaque: (payload: string, msgType?: number) =>
nativeModule?.sendOpaque?.(payload, msgType),
getState: () => nativeModule?.getState?.() ?? { ...defaultBleState },
addStateListener: (listener: (state: BleStatePayload) => void) =>
nativeModule?.addListener("onStateChange", listener),
getUuids: (): BleUuids =>
nativeModule
? {
serviceUUID: nativeModule.serviceUUID,
feedTxUUID: nativeModule.feedTxUUID,
controlRxUUID: nativeModule.controlRxUUID,
wifiRequestTxUUID: nativeModule.wifiRequestTxUUID,
}
: emptyUuids,
};

View File

@@ -0,0 +1,39 @@
export const BleBluetoothState = {
Unknown: "Unknown",
Resetting: "Resetting",
Unsupported: "Unsupported",
Unauthorized: "Unauthorized",
PoweredOff: "Powered Off",
PoweredOn: "Powered On",
Other: "Other",
} as const;
export type BleBluetoothState =
(typeof BleBluetoothState)[keyof typeof BleBluetoothState];
export type BleStatePayload = {
bluetoothState: BleBluetoothState;
advertisingEnabled: boolean;
isAdvertising: boolean;
isSubscribed: boolean;
subscribedCount: number;
lastMsgIdSent: number;
lastPingAt: number | null;
lastCommand: string | null;
notifyQueueDepth: number;
droppedNotifyPackets: number;
lastNotifyAt: number | null;
lastDataAt: number | null;
wifiRequested: boolean;
};
export type BleUuids = {
serviceUUID: string;
feedTxUUID: string;
controlRxUUID: string;
wifiRequestTxUUID: string;
};
export type BleNativeModuleEvents = {
onStateChange: (state: BleStatePayload) => void;
};

View File

@@ -0,0 +1,9 @@
// @generated by expo-module-scripts
{
"extends": "expo-module-scripts/tsconfig.base",
"compilerOptions": {
"outDir": "./build"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
}