Industrial Handheld Architecture

ATID Korea AT911N & AT870N RFID SDK Guide

Architect enterprise Android inventory applications for South Korea’s leading industrial terminals: ATID AT911N, AT870N, and AB700. Master atid.dev.rfid.jar, configure armeabi 32-bit ABI filters to eliminate UnsatisfiedLinkError crashes, coordinate barcode imager coexistence, and download complete sample apps and English PDFs.

ATID Korea SDK integration guide documentation
Core RFID JAR atid.dev.rfid.jar
Barcode Companion atid.dev.barcode.jar
Native ABI Target armeabi / v7a
Memory Addressing 16-bit WORD Offset

1 ATID Architecture: JNI Native Layers & Hardware Abstraction

ATID Co., Ltd. (Seoul, South Korea) manufactures some of the world's most durable handheld RFID computers. The software development kit relies on a layered architecture that bridges high-level Android Java/Kotlin APIs to low-level Linux serial bus device nodes through JNI (Java Native Interface):

Application API

atid.dev.rfid.jar

Exposes ATRfidManager, ATRfidReader, and the event-driven RfidReaderEventListener interface.

Barcode Engine

atid.dev.barcode.jar

Controls the integrated Zebra SE4710 / SE4750 1D/2D imager, lighting LEDs, and hardware aiming patterns.

JNI Native Core

armeabi / *.so

libserial_port.so and libsystem_control.so negotiate raw UART I/O with the internal RFID reader module.

Because communication with the embedded UHF module happens via raw serial bus rather than an Android system service, proper power lifecycle management and native binary loading are required.

! The 32-bit armeabi JNI UnsatisfiedLinkError Fix

The #1 Crash When Upgrading ATID Android Apps

Modern 64-bit Android systems default to launching processes in 64-bit mode (arm64-v8a). Because ATID's native C libraries (libserial_port.so) are 32-bit binaries, the Android runtime throws:

java.lang.UnsatisfiedLinkError: Couldn't load serial_port from loader: findLibrary returned null

The Solution: In your build.gradle.kts, restrict native ABI packaging to armeabi-v7a and point jniLibs to the library directory:

// app/build.gradle.kts
android {
    compileSdk = 34

    defaultConfig {
        minSdk = 19
        targetSdk = 34

        // Mandatory: Force 32-bit execution mode so 32-bit .so binaries load cleanly
        ndk {
            abiFilters.addAll(listOf("armeabi", "armeabi-v7a"))
        }
    }

    sourceSets {
        named("main") {
            // Point jniLibs to the directory containing armeabi/*.so
            jniLibs.srcDirs("libs")
        }
    }
}

dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
}

2 Production Kotlin Driver & Lifecycle Implementation

Here is the production Kotlin driver wrapping ATRfidManager and implementing RfidReaderEventListener. Notice how wakeUp, sleep, and event subscription cleanly follow Android Activity states:

// AtidRfidController.kt
package com.rfidsoftwares.atid

import android.util.Log
import com.atid.lib.dev.ATRfidManager
import com.atid.lib.dev.ATRfidReader
import com.atid.lib.dev.event.RfidReaderEventListener
import com.atid.lib.dev.rfid.type.ActionState
import com.atid.lib.dev.rfid.type.BankType
import com.atid.lib.dev.rfid.type.ConnectionState
import com.atid.lib.dev.rfid.type.ResultCode
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow

data class AtidScannedTag(
    val epc: String,
    val rssi: Float,
    val phase: Float,
    val timestamp: Long = System.currentTimeMillis()
)

class AtidRfidController : RfidReaderEventListener {

    private var reader: ATRfidReader? = null
    private val _tagFlow = MutableSharedFlow<AtidScannedTag>(extraBufferCapacity = 64)
    val tagFlow = _tagFlow.asSharedFlow()

    companion object {
        private const val TAG = "AtidRfidController"
    }

    fun initialize(): Boolean {
        reader = ATRfidManager.getInstance()
        if (reader == null) {
            Log.e(TAG, "Hardware module not found or serial port busy")
            return false
        }
        Log.i(TAG, "ATRfidReader initialized successfully")
        return true
    }

    fun onResume() {
        reader?.setEventListener(this)
        ATRfidManager.wakeUp()
    }

    fun onPause() {
        reader?.removeEventListener(this)
        ATRfidManager.sleep()
    }

    fun onDestroy() {
        reader = null
        ATRfidManager.onDestroy()
    }

    fun startInventory(): Boolean {
        val r = reader ?: return false
        return try {
            val res = r.startInventory()
            res == ResultCode.NoError
        } catch (t: Throwable) {
            Log.e(TAG, "Error starting inventory", t)
            false
        }
    }

    fun stopInventory(): Boolean {
        val r = reader ?: return false
        return try {
            val res = r.stop()
            res == ResultCode.NoError
        } catch (t: Throwable) {
            Log.e(TAG, "Error stopping inventory", t)
            false
        }
    }

    // GS1 Gen2 Standard: Word offset 2 skips CRC (Word 0) and PC (Word 1)
    fun readEpcWordPayload(offsetWords: Int = 2, lengthWords: Int = 6, passwordHex: String = "00000000"): String? {
        val r = reader ?: return null
        return try {
            val data = r.readMemory6c(BankType.EPC, offsetWords, lengthWords, passwordHex)
            data
        } catch (t: Throwable) {
            Log.e(TAG, "Memory read failed", t)
            null
        }
    }

    // --- RfidReaderEventListener Callbacks ---

    override fun onReaderReadTag(reader: ATRfidReader?, tag: String?, rssi: Float, phase: Float) {
        if (!tag.isNullOrBlank()) {
            val record = AtidScannedTag(
                epc = tag.trim().replace(" ", "").uppercase(),
                rssi = rssi,
                phase = phase
            )
            _tagFlow.tryEmit(record)
        }
    }

    override fun onReaderStateChanged(reader: ATRfidReader?, state: ConnectionState?) {
        Log.i(TAG, "Reader connection state: $state")
    }

    override fun onReaderActionChanged(reader: ATRfidReader?, action: ActionState?) {
        Log.i(TAG, "Reader action state: $action")
    }

    override fun onReaderResult(
        reader: ATRfidReader?,
        code: ResultCode?,
        action: ActionState?,
        epc: String?,
        data: String?,
        rssi: Float,
        phase: Float
    ) {
        Log.d(TAG, "Reader result: code=$code action=$action epc=$epc data=$data")
    }
}

3 ATID Troubleshooting & Pitfalls Matrix

Symptom / Error Root Cause Resolution
UnsatisfiedLinkError: serial_port App running on 64-bit Android OS without 32-bit armeabi NDK ABI filters declared in Gradle. Add ndk { abiFilters.addAll(listOf("armeabi", "armeabi-v7a")) } to build.gradle.kts.
fail_check_module on launch Another RFID application or the pre-installed ATID Demo is holding open the hardware serial node. Force-stop background ATRfidDemo processes or reboot the handheld terminal to release the serial lock.
Tags stop reading after wake from sleep ATRfidManager.wakeUp() was omitted in onStart(), leaving the RF power amplifier asleep. Call ATRfidManager.wakeUp() inside onStart() and re-verify reader.getState() == Connected.
Concurrent Barcode & RFID Crashes Both ATRfidReader and ATBarcodeReader drew power simultaneously, causing hardware brownout. Pause RFID inventory sweeps before triggering barcode decoding, and resume RFID only after barcode completion.
UI dropped frames in high-density scan onReaderReadTag() callbacks posted hundreds of direct UI ListView updates per second. Buffer scanned tags in a ConcurrentHashMap and update the UI Adapter at a throttled 10 Hz cadence (every 100ms).

4 Verified ATID SDK Downloads & SHA-256 Checksums

All binaries, native JNI libraries, and official English developer guides are packaged and verified:

FULL BUNDLE

atid-at911n-complete-sdk-bundle.zip

28.3 MB

Complete AT911N & AT870N SDK: All JARs, armeabi JNI .so binaries, ATRfidDemo & ATBarcodeDemo source code, and 8 English developer PDFs.

SHA-256: EAF226FBCAC5A756CB7D8222E87F24DFFE9648B8B711F920EFB42149B8206445

Download Bundle (28.3 MB)
JAR

atid.dev.rfid.jar

265 KB

Core ATID RFID Reader SDK interface containing ATRfidManager and RfidReaderEventListener.

Download JAR
JAR

atid.dev.barcode.jar

398 KB

Companion 1D/2D barcode imager engine driver for AT911N and AT870N.

Download JAR

Supported ATID Hardware

  • ATID AT911N Flagship Android enterprise UHF mobile computer with IP65 ruggedization.
  • ATID AT870N Heavy-duty industrial terminal with high-power circular polarized antenna.
  • ATID AB700 Ergonomic RFID pistol-grip handheld for high-throughput distribution centers.
  • ATID AT288 / AT388 Compact Bluetooth UHF sleds connecting to iOS, Android, and Windows.

Need Turnkey ATID Integration?

We engineer custom Android middleware connecting ATID AT911N and AT870N terminals directly to SAP ERP, Oracle NetSuite, TallyPrime, and Zoho Inventory.

Consult an RFID Architect

Frequently Asked Questions: ATID Korea SDK

Expert answers on native JNI linking, memory offsets, and barcode coexistence.

How do you fix java.lang.UnsatisfiedLinkError: Couldn't load serial_port on modern Android?
The ATID native shared objects (libserial_port.so, libsystem_control.so) were compiled for 32-bit armeabi architecture. On 64-bit devices, the Android OS looks for 64-bit arm64-v8a binaries and crashes if not found. You must configure ndk { abiFilters.addAll(listOf("armeabi", "armeabi-v7a")) } in your build.gradle.kts to force the 64-bit zygote to run the app in 32-bit compatibility mode.
What is the correct lifecycle pattern for ATRfidManager and ATRfidReader?
Call ATRfidManager.getInstance() in onCreate() to connect to the hardware serial module. In onStart(), call ATRfidManager.wakeUp() to restore power to the antenna transceiver. In onResume(), attach your event listener via reader.setEventListener(this). In onPause(), call reader.removeEventListener(this). In onStop(), put the reader to sleep with ATRfidManager.sleep(), and finally call ATRfidManager.onDestroy() in onDestroy() to release Linux serial device nodes.
Can the ATID barcode scanner and UHF RFID reader run simultaneously in the same app?
Yes, but they must be managed cooperatively. The ATID AT911N hardware routes power through internal multiplexers. When an operator triggers a barcode scan via ATBarcodeManager, pause active RFID inventory loops. Once barcode decoding completes, resume RFID inventory sweeps to avoid serial bus power starvation.
How does ATID memory bank addressing compare to Seuic and Chainway?
ATID follows standard GS1 EPC Gen2 16-bit WORD offset addressing. For the EPC memory bank (BankType.EPC), the default data offset is Word 2 (skipping Word 0 CRC and Word 1 PC). This differs from Seuic which uses byte-offset addressing.
Which ATID handheld terminals are supported by this SDK library bundle?
The atid.dev.rfid.jar and native JNI binaries support the ATID AT911N, AT870N, AT880, AB700, and AT288 Bluetooth barcode/RFID readers across Android 4.4 up to modern Android versions running 32-bit ABI compatibility.
Where can I download the complete ATID AT911N developer bundle and English manuals?
You can download the full atid-at911n-complete-sdk-bundle.zip (28.3 MB) containing all JARs, native armeabi JNI libraries, sample Android Studio projects (ATRfidDemo & ATBarcodeDemo), and official English PDF programming guides directly from our developer catalog.