Implement the Hardware Services
This guide shows the exact calls the Middleware App and the Devkit make against your Manufacturer Service App, service by service. Use it to confirm your implementation responds the way Getnet’s software expects.
How it works
Every call below reaches your service through mainService, the IMainService stub the Middleware App gets after binding to your service. Each service — card, printer, beeper, and so on — hangs off mainService as its own property. Most operations report their result through a callback rather than a return value.
Before you begin
Complete HAL integration setup so your service binds successfully, then run the Devkit to test each call as you implement it.
Beeper
The Devkit calls custom with a duration in milliseconds:
mainService.beeper.custom(500) // 500 millisecondsYour IBeeperService implementation must also support four named sounds, each with a fixed duration:
| Sound | Duration |
|---|---|
success | 500 ms beep. |
error | 1000 ms beep. |
digit | 100 ms beep. |
nfc | 100 ms beep, 300 ms pause, 100 ms beep. |
Card
Get the card service once, then call the search method matching the read type your caller needs:
val cardService = mainService.cardEach search type uses a fixed constant:
| Constant | Value |
|---|---|
MAG | "1" |
CHIP | "2" |
NFC | "3" |
Chip search:
cardService.searchChip(1000, object : ICardCallback.Stub() {
override fun onCard(cardResponse: CardResponse?) {
val pan = cardResponse?.pan
cardService.stopAllReaders()
}
override fun onMessage(message: String?) {
cardService.stopAllReaders()
}
override fun onError(error: String?) {
cardService.stopAllReaders()
}
})Magnetic stripe search returns track1, track2, and track3 on onCard instead of pan. NFC search returns the same CardResponse shape as chip search.
Both follow the same onCard / onMessage / onError pattern, and both must call stopAllReaders() once a result arrives.
Mifare
IMifareService covers card presence, authentication, and block-level read/write:
mainService.mifare.searchCard(object : IMifareCallback.Stub() {
override fun onCard(type: Int) { /* card type detected */ }
override fun onError(error: String?) { /* handle error */ }
})
mainService.mifare.searchCardAndActivate(object : IMifareActivateCallback.Stub() {
override fun onActivate(key: ByteArray?) { /* card activated */ }
override fun onError(error: String?) { /* handle error */ }
})Sector and block authentication take a 6-byte key:
val key = byteArrayOf(0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte(), 0xff.toByte())
mainService.mifare.authenticateSectorWithKeyA(sector = 2, key)
mainService.mifare.authenticateBlockWithKeyA(block = 10, key)
mainService.mifare.authenticateSectorWithKeyB(sector = 2, key)
mainService.mifare.authenticateBlockWithKeyB(block = 10, key)After authentication, your service must support decrement, increment, readBlock, restore, transfer, and writeBlock on a given block index. It must also support close, isExist, activate, and halt for session control. getCardSerialNo returns the card’s UID as a hex string.
LED
ILedService exposes an on/off pair per color — red, blue, yellow, and green:
mainService.led.turnOnRed()
mainService.led.turnOffRed()Implement the same pair for blue, yellow, and green.
Printer
Every IPrinterService method must throw IllegalStateException if your service hasn’t initialized the printer yet:
mainService.printer?.init()
?: throw IllegalStateException("service isn't initiated")Once initialized, your service builds a print job from a sequence of calls — addText, addBarCode, addQrCode, addImageBitmap, addImageByteArray — followed by print or printAndRemovePaper:
mainService.printer?.addText(align, text)
mainService.printer?.print(object : IPrinterCallback.Stub() {
override fun onSuccess() { /* job printed */ }
override fun onError(cause: Int) { /* map cause to a status */ }
})setGray sets the grayscale level, and defineFontFormat sets the active font. See the service interfaces reference for the fixed printer specifications — image size, character limits, and the grayscale threshold your service must apply.
Camera
readFront reads the front camera with a timeout and reports the result through a callback:
mainService.camera.readFront(
timeout = 2000,
callback = object : ICameraCallback.Stub() {
override fun onSuccess(code: String?) { /* code read */ }
override fun onTimeout() { /* no result within timeout */ }
override fun onCancel() { /* caller canceled the read */ }
override fun onError(error: String?) { /* handle error */ }
},
)System statistics
IStatService reports success and failure counts per read type, both device-wide and per calling app:
mainService.stats.getAllStatisticsByApp(object : IStatCallback.Stub() {
override fun onStatistic(statResponse: StatResponse?) {
val paperStatus = statResponse?.generalPaperStatus
val mifareStatus = statResponse?.generalMifareStatus
}
override fun onError(error: String?) { /* handle error */ }
})Some HAL methods, such as print(userId, callback) and searchMag(userId, timeout, callback), receive a userId parameter identifying the calling app. Resolve it to a package name with packageManager.getNameForUid(userId). Use that package name to attribute statistics to the correct app.
Read the result
A correct implementation always reports hardware events through the caller’s callback, never through a return value alone. It also calls stopAllReaders() after a card read completes, whether it succeeded or not.
Printer methods are the one exception worth calling out on its own: any of them called before init() must throw IllegalStateException rather than fail silently.
Next steps
- Submit your integration for validation — once every call above responds correctly.
- Service interfaces reference — full method signatures, including the interfaces not shown here.
- EMV state machine — the flow that follows a successful chip or contactless card read.