React Native plugin for iMin POS device hardware features.
Crane 1, Swan 1, Swan 2, Swan 2 Pro, Swift 1, Swift 2, Swift 2 Ultra, Lark 1, Falcon 1 Pro, Falcon 2, M2-Pro and other iMin Android POS devices.
| Module | Description |
|---|---|
| Device | Device info (model, serial number, brand, etc.) |
| Scanner | Hardware barcode/QR code scanner |
| CameraScan | Camera-based barcode/QR code scanning (ZXing + ML Kit) |
| CashBox | Cash drawer control |
| NFC | NFC card reading |
| MSR | Magnetic stripe card reader |
| RFID | UHF RFID tag read/write |
| Scale | Electronic scale (serial) |
| ScaleNew | Electronic scale (Android 13+ iMinEscale SDK) |
| Serial | Serial port communication |
| Display | Secondary display control |
| Light | USB LED indicator light |
| Segment | Segment display (digital tube) |
| FloatingWindow | Floating window overlay |
npm install react-native-imin-hardware
# or
yarn add react-native-imin-hardware- minSdkVersion: 24
- compileSdkVersion: 34+
- React Native: 0.68+
No additional native setup required. The plugin auto-links.
import { Device, CameraScan } from 'react-native-imin-hardware';
// Get device model
const model = await Device.getModel();
console.log('Device:', model);
// Quick scan a barcode
const code = await CameraScan.scanQuick();
console.log('Scanned:', code);Get device hardware information.
import { Device } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
getModel() |
Promise<string> |
Device model name |
getSerialNumber() |
Promise<string> |
Device serial number |
getBrand() |
Promise<string> |
Device brand |
getDeviceName() |
Promise<string> |
Device name |
getAndroidVersion() |
Promise<string> |
Android SDK version number |
getAndroidVersionName() |
Promise<string> |
Android version name (e.g. "13") |
getSdkVersion() |
Promise<string> |
iMin SDK version |
getServiceVersion() |
Promise<string> |
iMin service version |
getDeviceInfo() |
Promise<DeviceInfo> |
All info in one call |
// Get all device info at once
const info = await Device.getDeviceInfo();
// info = { model, serialNumber, androidVersion, sdkVersion, brand, deviceName, ... }Listen for barcode data from the built-in hardware scanner.
import { Scanner } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
startListening() |
Promise<boolean> |
Start receiving scan events |
stopListening() |
Promise<boolean> |
Stop receiving scan events |
isConnected() |
Promise<boolean> |
Check scanner connection |
configure(config) |
Promise<void> |
Set custom broadcast action/data keys |
addListener(callback) |
EmitterSubscription |
Listen for scan events |
// Start listening
await Scanner.startListening();
// Listen for scan results
const subscription = Scanner.addListener((event) => {
if (event.type === 'scanResult') {
console.log('Code:', event.data.data);
console.log('Type:', event.data.labelType);
}
});
// Stop when done
await Scanner.stopListening();
subscription.remove();Custom config (optional, for non-standard scanner broadcast):
await Scanner.configure({
action: 'com.example.SCAN_ACTION',
dataKey: 'barcode_string',
byteDataKey: 'barcode_bytes',
});Open the camera to scan barcodes and QR codes. Supports ZXing (default) and ML Kit engines.
import { CameraScan, BarcodeFormat, DecodeEngine } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
scan(options?) |
Promise<ScanResultData> |
Scan one code, returns { code, format } |
scanQuick() |
Promise<string> |
Scan and return code string only |
scanQRCode() |
Promise<string> |
Scan QR codes only |
scanBarcode() |
Promise<string> |
Scan 1D barcodes only |
scanAll() |
Promise<ScanResultData> |
Scan with all formats enabled |
// Simplest usage
const code = await CameraScan.scanQuick();
// With options
const result = await CameraScan.scan({
formats: [BarcodeFormat.QR_CODE, BarcodeFormat.EAN_13],
useFlash: false,
beepEnabled: true,
timeout: 30000, // 30s timeout, 0 = no timeout
});
console.log(result.code, result.format);CameraScanOptions:
| Field | Type | Default | Description |
|---|---|---|---|
formats |
BarcodeFormatType[] |
Default 4 formats | Barcode formats to recognize |
useFlash |
boolean |
false |
Turn on flashlight |
beepEnabled |
boolean |
true |
Play beep sound on scan |
timeout |
number |
0 |
Timeout in ms, 0 = no timeout |
| Method | Return | Description |
|---|---|---|
scanMulti(options?) |
Promise<ScanResultData[]> |
Scan multiple codes, returns array |
isMLKitAvailable() |
Promise<boolean> |
Check if ML Kit is available |
// Check ML Kit availability
const mlkit = await CameraScan.isMLKitAvailable();
// Multi-barcode scan (returns array)
const results = await CameraScan.scanMulti();
results.forEach(r => console.log(r.code, r.format));
// With full options
const results = await CameraScan.scanMulti({
formats: [BarcodeFormat.QR_CODE, BarcodeFormat.CODE_128],
supportMultiBarcode: true,
supportMultiAngle: true,
fullAreaScan: true,
areaRectRatio: 0.9,
decodeEngine: DecodeEngine.MLKIT,
timeout: 30000,
});MultiScanOptions:
| Field | Type | Default | Description |
|---|---|---|---|
formats |
BarcodeFormatType[] |
All formats | Barcode formats to recognize |
useFlash |
boolean |
false |
Turn on flashlight |
beepEnabled |
boolean |
true |
Play beep sound |
timeout |
number |
0 |
Timeout in ms |
supportMultiBarcode |
boolean |
true |
Recognize multiple codes at once |
supportMultiAngle |
boolean |
true |
Recognize codes at any angle |
decodeEngine |
0 | 1 |
1 (MLKit) |
0=ZXing, 1=ML Kit |
fullAreaScan |
boolean |
true |
Scan full camera area |
areaRectRatio |
number |
0.8 |
Scan area ratio (0.5~1.0) |
Supported barcode formats:
QR_CODE, EAN_13, EAN_8, UPC_A, UPC_E, CODE_128, CODE_39, CODE_93, CODABAR, ITF, RSS_14, RSS_EXPANDED, DATA_MATRIX, PDF_417, AZTEC, MAXICODE
Control the cash drawer.
import { CashBox, CashBoxVoltage } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
open() |
Promise<void> |
Open the cash drawer |
getStatus() |
Promise<boolean> |
true = open, false = closed |
setVoltage(voltage) |
Promise<boolean> |
Set voltage: CashBoxVoltage.V9 / V12 / V24 |
await CashBox.open();
const isOpen = await CashBox.getStatus();
await CashBox.setVoltage(CashBoxVoltage.V12);Read NFC tags.
import { Nfc } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
isAvailable() |
Promise<boolean> |
Device supports NFC |
isEnabled() |
Promise<boolean> |
NFC is turned on |
openSettings() |
Promise<boolean> |
Open NFC system settings |
startListening() |
Promise<boolean> |
Start listening for tags |
stopListening() |
Promise<boolean> |
Stop listening |
addListener(callback) |
EmitterSubscription |
Listen for tag events |
const available = await Nfc.isAvailable();
if (!available) return;
await Nfc.startListening();
const subscription = Nfc.addListener((tag) => {
console.log('Tag ID:', tag.id);
console.log('Content:', tag.content);
console.log('Tech:', tag.technology);
});
// Cleanup
await Nfc.stopListening();
subscription.remove();Read and write UHF RFID tags.
import { Rfid, RfidBank } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
connect() |
Promise<boolean> |
Connect RFID device |
disconnect() |
Promise<boolean> |
Disconnect |
isConnected() |
Promise<boolean> |
Check connection |
startReading() |
Promise<boolean> |
Start continuous tag reading |
stopReading() |
Promise<boolean> |
Stop reading |
readTag(params?) |
Promise<boolean> |
Read specific memory bank |
writeTag(params) |
Promise<boolean> |
Write to memory bank |
writeEpc(params) |
Promise<boolean> |
Write new EPC |
lockTag(params) |
Promise<boolean> |
Lock tag memory |
killTag(password) |
Promise<boolean> |
Permanently kill tag |
setPower(read, write) |
Promise<boolean> |
Set RF power (dBm) |
setFilter(epc) |
Promise<boolean> |
Filter by EPC |
clearFilter() |
Promise<boolean> |
Clear filter |
getBatteryLevel() |
Promise<number> |
RFID handle battery % |
isCharging() |
Promise<boolean> |
Is handle charging |
await Rfid.connect();
await Rfid.startReading();
const subscription = Rfid.addTagListener((event) => {
if (event.type === 'tag') {
console.log('EPC:', event.epc, 'RSSI:', event.rssi);
}
});
await Rfid.stopReading();
await Rfid.disconnect();
subscription.remove();Read weight data from serial-connected electronic scale.
import { Scale } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
connect(path?) |
Promise<boolean> |
Connect, default /dev/ttyS4 |
disconnect() |
Promise<boolean> |
Disconnect |
tare() |
Promise<boolean> |
Tare (zero with load) |
zero() |
Promise<boolean> |
Zero (reset to 0) |
addListener(callback) |
EmitterSubscription |
Listen for weight data |
await Scale.connect('/dev/ttyS4');
const subscription = Scale.addListener((data) => {
console.log('Weight:', data.weight, 'Status:', data.status);
});
await Scale.tare();
await Scale.disconnect();
subscription.remove();Electronic scale using iMinEscale SDK (AIDL service).
import { ScaleNew, ScaleUnit } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
connectService() |
Promise<boolean> |
Connect to scale service |
getData() |
Promise<boolean> |
Start receiving weight data |
cancelGetData() |
Promise<boolean> |
Stop receiving data |
zero() |
Promise<boolean> |
Zero |
tare() |
Promise<boolean> |
Tare |
digitalTare(weight) |
Promise<boolean> |
Digital tare (grams) |
setUnitPrice(price) |
Promise<boolean> |
Set unit price |
getUnitPrice() |
Promise<string> |
Get unit price |
setUnit(unit) |
Promise<boolean> |
Set weight unit |
getUnit() |
Promise<number> |
Get weight unit |
getServiceVersion() |
Promise<string> |
Service version |
getFirmwareVersion() |
Promise<string> |
Firmware version |
restart() |
Promise<boolean> |
Restart scale |
addListener(callback) |
EmitterSubscription |
Listen for events |
await ScaleNew.connectService();
await ScaleNew.getData();
const subscription = ScaleNew.addListener((event) => {
if (event.type === 'weight') {
console.log('Net:', event.net, 'Tare:', event.tare, 'Stable:', event.isStable);
}
if (event.type === 'price') {
console.log('Total:', event.totalPrice);
}
});
await ScaleNew.tare();
await ScaleNew.cancelGetData();
subscription.remove();Raw serial port communication.
import { Serial } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
open(path, baudRate?) |
Promise<boolean> |
Open serial port |
close() |
Promise<boolean> |
Close serial port |
write(data) |
Promise<boolean> |
Write bytes (comma-separated) |
writeString(text) |
Promise<boolean> |
Write string |
isOpen() |
Promise<boolean> |
Check if port is open |
addListener(callback) |
EmitterSubscription |
Listen for received data |
await Serial.open('/dev/ttyS4', 115200);
const subscription = Serial.addListener((event) => {
console.log('Received:', event.data); // number[]
});
await Serial.writeString('Hello');
await Serial.write('72,101,108,108,111'); // byte values
await Serial.close();
subscription.remove();Control the customer-facing secondary display.
import { Display } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
isAvailable() |
Promise<boolean> |
Check if secondary display exists |
enable() |
Promise<boolean> |
Enable display |
disable() |
Promise<boolean> |
Disable display |
showText(text) |
Promise<boolean> |
Show text |
showImage(path) |
Promise<boolean> |
Show image (URL or local path) |
playVideo(path) |
Promise<boolean> |
Play video (loops) |
clear() |
Promise<boolean> |
Clear display content |
const available = await Display.isAvailable();
if (!available) return;
await Display.enable();
await Display.showText('Total: $12.50');
await Display.showImage('https://example.com/promo.png');
await Display.playVideo('https://example.com/ad.mp4');
await Display.clear();
await Display.disable();Control USB LED indicator light.
import { Light } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
connect() |
Promise<boolean> |
Connect LED device (requests USB permission) |
turnOnGreen() |
Promise<boolean> |
Green light on |
turnOnRed() |
Promise<boolean> |
Red light on |
turnOff() |
Promise<boolean> |
Light off |
disconnect() |
Promise<boolean> |
Disconnect |
await Light.connect();
await Light.turnOnGreen();
// ... later
await Light.turnOff();
await Light.disconnect();Control the segment display (digital tube).
import { Segment } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
findDevice() |
Promise<SegmentDeviceInfo> |
Find USB segment device |
requestPermission() |
Promise<boolean> |
Request USB permission |
connect() |
Promise<boolean> |
Connect device |
sendData(data, align?) |
Promise<boolean> |
Display data (max 9 chars), align: 'left' or 'right' |
clear() |
Promise<boolean> |
Clear display |
full() |
Promise<boolean> |
All segments on (test) |
disconnect() |
Promise<boolean> |
Disconnect |
await Segment.findDevice();
await Segment.requestPermission();
await Segment.connect();
await Segment.sendData('12345', 'right');
await Segment.clear();
await Segment.disconnect();Show a floating overlay window on screen.
import { FloatingWindow } from 'react-native-imin-hardware';| Method | Return | Description |
|---|---|---|
hasPermission() |
Promise<boolean> |
Check overlay permission |
requestPermission() |
Promise<boolean> |
Request permission (opens settings) |
show() |
Promise<boolean> |
Show floating window |
hide() |
Promise<boolean> |
Hide floating window |
isShowing() |
Promise<boolean> |
Check if visible |
updateText(text) |
Promise<boolean> |
Update displayed text |
setPosition(x, y) |
Promise<boolean> |
Set window position (px) |
if (!(await FloatingWindow.hasPermission())) {
await FloatingWindow.requestPermission();
}
await FloatingWindow.show();
await FloatingWindow.updateText('Order #1234 - $25.00');
await FloatingWindow.setPosition(100, 200);
await FloatingWindow.hide();MSR works as a keyboard input device. When a card is swiped, data is automatically typed into the currently focused TextInput. No special API calls needed beyond checking availability.
import { Msr } from 'react-native-imin-hardware';
const available = await Msr.isAvailable();All async methods may throw errors. Common error codes:
| Code | Description |
|---|---|
CANCELED |
User canceled the operation |
NO_ACTIVITY |
No active Android activity |
ALREADY_ACTIVE |
A scan is already in progress |
NO_DATA |
No result returned |
ERROR |
General error (check message) |
try {
const result = await CameraScan.scan();
} catch (e) {
if (e.code === 'CANCELED') {
// User pressed back
} else {
console.error('Scan error:', e.message);
}
}All types are exported and available for import:
import type {
// Device
DeviceInfo,
// Scanner
ScannerConfig, ScanResult, ScannerEvent,
// CameraScan
ScanResultData, CameraScanOptions, MultiScanOptions,
BarcodeFormatType, DecodeEngineType,
// CashBox
CashBoxVoltage,
// NFC
NfcTagData, NfcEvent,
// RFID
RfidTagData, RfidEvent, ReadTagParams, WriteTagParams, LockTagParams,
// Scale
ScaleDataEvent,
// ScaleNew
ScaleNewEvent, ScaleNewWeightEvent, ScaleNewPriceEvent,
// Serial
SerialDataEvent,
// Segment
SegmentDeviceInfo, SegmentAlign,
} from 'react-native-imin-hardware';scan() |
scanMulti() |
|
|---|---|---|
| Engine | ZXing | ML Kit (with ZXing fallback) |
| Returns | Single ScanResultData |
Array ScanResultData[] |
| Multi-barcode | No | Yes |
| Multi-angle | No | Yes (any rotation) |
| Use case | Simple one-code scan | Multiple codes, rotated codes |
| Dependency | Built-in | Requires Google Play Services |
Use scan() for simple scenarios. Use scanMulti() when you need multi-angle or multi-barcode support.
| Module | Requirement | Notes |
|---|---|---|
| Device | All devices | Always available |
| Scanner | Hardware scan head | Not all models have built-in scanner |
| CameraScan | Camera | Requires camera permission |
| CashBox | Cash drawer port | Check device specs |
| NFC | NFC hardware | Use Nfc.isAvailable() to check |
| MSR | MSR hardware | Keyboard input mode |
| RFID | RFID handle | External UHF RFID accessory |
| Scale | Serial port | Requires external scale hardware |
| ScaleNew | Android 13+ | iMinEscale SDK, newer devices only |
| Serial | Serial port | Device-specific port path |
| Display | Secondary screen | Dual-screen devices only |
| Light | USB LED | External USB LED accessory |
| Segment | USB segment display | External USB accessory |
| FloatingWindow | Android 6.0+ | Requires SYSTEM_ALERT_WINDOW permission |
Modules that emit events (Scanner, NFC, RFID, Scale, ScaleNew, Serial) return an EmitterSubscription. Always clean up in useEffect:
useEffect(() => {
const subscription = Scanner.addListener((event) => {
// handle event
});
return () => subscription.remove();
}, []);Q: scan() opens camera but nothing happens?
A: Make sure camera permission is granted. Check AndroidManifest.xml has <uses-permission android:name="android.permission.CAMERA" /> (the plugin adds this automatically).
Q: Which should I use, scan() or scanMulti()?
A: Use scan() for simple one-code scanning. Use scanMulti() if you need to scan codes at odd angles or multiple codes at once. scan() is lighter and doesn't require Google Play Services.
Q: Scanner events not received?
A: Make sure you call Scanner.startListening() first. Some devices use custom broadcast actions — use Scanner.configure() to set the correct action and data keys for your device.
Q: ScaleNew methods fail?
A: ScaleNew requires Android 13+ and the iMinEscale service. Use Scale (serial) module for older devices.
Q: ML Kit not available?
A: ML Kit requires Google Play Services. Call CameraScan.isMLKitAvailable() to check. If unavailable, scanMulti() falls back to ZXing automatically.
See CHANGELOG.md for version history.
https://github.com/iminsoftware/ReactNativeApiTest
MIT