{"id":12066,"date":"2026-02-19T11:44:41","date_gmt":"2026-02-19T02:44:41","guid":{"rendered":"https:\/\/www.moonmile.net\/blog\/?p=12066"},"modified":"2026-02-19T11:47:24","modified_gmt":"2026-02-19T02:47:24","slug":"android-%e3%81%a7-ibeacon-%e3%81%a8-en-api-%e7%99%ba%e4%bf%a1%e6%a9%9f%e3%82%92%e4%bd%9c%e3%82%8b","status":"publish","type":"post","link":"http:\/\/www.moonmile.net\/blog\/archives\/12066","title":{"rendered":"Android \u3067 iBeacon \u3068 EN API \u767a\u4fe1\u6a5f\u3092\u4f5c\u308b"},"content":{"rendered":"\n<p>iBeacon \u306e\u53d7\u4fe1\u6a5f\u304c\u3067\u304d\u305f\u306e\u3067\u3001\u9006\u306b\u767a\u4fe1\u6a5f\u3092\u4f5c\u3063\u3066\u3044\u304d\u307e\u3059\u3002<\/p>\n\n\n\n<p>Android \u3067 iBeacon \u3068 EN API \u53d7\u4fe1\u6a5f\u3092\u4f5c\u308b<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-moonmile-solutions-blog wp-block-embed-moonmile-solutions-blog\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"xrI4EPAGvH\"><a href=\"https:\/\/www.moonmile.net\/blog\/archives\/12046\">Android \u3067 iBeacon \u3068 EN API \u53d7\u4fe1\u6a5f\u3092\u4f5c\u308b<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;Android \u3067 iBeacon \u3068 EN API \u53d7\u4fe1\u6a5f\u3092\u4f5c\u308b&#8221; &#8212; Moonmile Solutions Blog\" src=\"https:\/\/www.moonmile.net\/blog\/archives\/12046\/embed#?secret=YNwxckhczV#?secret=xrI4EPAGvH\" data-secret=\"xrI4EPAGvH\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\u3053\u308c\u3092\u7d44\u307f\u5408\u308f\u305b\u308b\u3068 Android\/iOS \u306e\u76f8\u4e92\u306b iBeacon \u3084\u305d\u306e\u4ed6\u306e BLE \u306e\u9001\u53d7\u4fe1\u72b6\u614b\u304c\u78ba\u8a8d\u3067\u304d\u307e\u3059\u3002\u901a\u5e38\u306e\u53d7\u767a\u4fe1\u306f\u3042\u3061\u3053\u3061\u306e\u30d6\u30ed\u30b0\u306b\u3042\u308b\u306e\u3067\u53c2\u7167\u3067\u304d\u307e\u3059\u3002\u79c1\u304c\u78ba\u8a8d\u3057\u3066\u3044\u304d\u305f\u3044\u306e\u306f\u3001<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u53d7\u4fe1\u30bf\u30a4\u30df\u30f3\u30b0\uff08Scan Window\/Scan Interval\uff09\u304c\u7570\u306a\u308b\u5834\u5408<\/li>\n\n\n\n<li>\u767a\u4fe1\u5074\u306e\u767a\u4fe1\u30bf\u30a4\u30df\u30f3\u30b0\uff08Advertising Interval\uff09\u304c\u7570\u306a\u308b\u5834\u5408<\/li>\n<\/ul>\n\n\n\n<p>\u306e\u7d44\u307f\u5408\u308f\u305b\u3067\u3059\u3002\u3053\u308c\u306f\u5b9f\u6e2c\u3059\u308b\u3068\u308f\u304b\u308a\u307e\u3059\u304c\u3001\u4e21\u65b9\u3068\u3082 LOW POWER \u3067\u52d5\u304b\u3059\u3068\u3001iBeacon \u306e\u53d7\u4fe1\u304c\u306a\u304b\u306a\u304b\u767a\u751f\u3057\u306a\u3044\u3068\u3044\u3046\u73fe\u8c61\u304c\u767a\u751f\u3057\u307e\u3059\u3002\u5834\u5408\u306b\u3088\u3063\u3066\u306f 1 \u5206\u4f4d\u5f85\u305f\u3055\u308c\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u3053\u306e\u73fe\u8c61\u3092\u5b9f\u6e2c\u3059\u308b\u305f\u3081\u306e\u30c4\u30fc\u30eb\u3067\u3059\u3002<br>Android \u3067\u306f\u7d30\u304b\u3044\u8a2d\u5b9a\u306f\u3067\u304d\u306a\u3044\u306e\u3067\u3059\u304c\u3001\u5148\u884c\u304d\u306f m5stack \u306a\u3069\u3092\u4f7f\u3063\u3066\u7d30\u304b\u304f\u30c1\u30a7\u30c3\u30af\u3057\u3066\u3044\u304f\u4e88\u5b9a\u3067\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Jetpack Compose \u3067\u4f5c\u308b<\/strong><\/h2>\n\n\n\n<p>\u53d7\u4fe1\u6a5f\u3068\u540c\u3058\u3088\u3046\u306b\u3001Jetpack Compose \u3092\u4f7f\u3044\u307e\u3059\u3002<br>\u3060\u3093\u3060\u3093\u3001\u8907\u96d1\u602a\u5947\u306b\u306a\u3063\u3066\u304f\u308b\u306e\u3067\u3059\u304c\u3001\u30bf\u30d6\u5207\u308a\u66ff\u3048\u3067 iBeacon \u3092\u767a\u4fe1\u3057\u307e\u3059\u3002\u3053\u3053\u306e\u30bf\u30d6\u3060\u3051\u3001\u6a29\u9650\u306e\u78ba\u8a8d\u3068\u30ea\u30af\u30a8\u30b9\u30c8\u3082\u5165\u308c\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: scala; title: ; notranslate\" title=\"\">\n@Composable\nprivate fun IBeaconTransmitterTab(\n    advertiseMode: Int,\n    advertiseTxPowerLevel: Int,\n    onAdvertiseModeChange: (Int) -&gt; Unit,\n    onAdvertiseTxPowerChange: (Int) -&gt; Unit,\n) {\n    val context = LocalContext.current\n    var majorHex by rememberSaveable { mutableStateOf((0..0xFFFF).random().toString(16).uppercase().padStart(4, &#039;0&#039;)) }\n    var minorHex by rememberSaveable { mutableStateOf((0..0xFFFF).random().toString(16).uppercase().padStart(4, &#039;0&#039;)) }\n    val transmitter = remember(majorHex, minorHex) { BeaconTransmitter(context, major = majorHex.toIntOrNull(16) ?: 0, minor = minorHex.toIntOrNull(16) ?: 0) }\n    var isAdvertising by rememberSaveable { mutableStateOf(false) }\n\n    fun restartIfAdvertising() {\n        if (isAdvertising) {\n            transmitter.stopTransmitter()\n            transmitter.advertiseMode = advertiseMode\n            transmitter.advertiseTxPowerLevel = advertiseTxPowerLevel\n            transmitter.startTransmitter()\n        }\n    }\n\n    var hasPermission by remember { mutableStateOf(hasScanPermissions(context)) }\n    val permissionLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.RequestMultiplePermissions()\n    ) { result -&gt;\n        hasPermission = result.values.all { it }\n    }\n\n    \/\/ Collect scan results\n    if (!hasPermission) {\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(16.dp)\n        ) {\n            Text(\n                text = &quot;Bluetooth\u30b9\u30ad\u30e3\u30f3\u6a29\u9650\u304c\u5fc5\u8981\u3067\u3059\u3002\u8a31\u53ef\u3057\u3066\u304f\u3060\u3055\u3044\u3002&quot;,\n                style = MaterialTheme.typography.bodyLarge\n            )\n            Button(\n                onClick = {\n                    if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {\n                        permissionLauncher.launch(\n                            arrayOf(\n                                android.Manifest.permission.BLUETOOTH_ADVERTISE,\n                                android.Manifest.permission.BLUETOOTH_CONNECT,\n                            )\n                        )\n                    } else {\n                        permissionLauncher.launch(\n                            arrayOf(\n                                android.Manifest.permission.BLUETOOTH,\n                                android.Manifest.permission.BLUETOOTH_ADMIN,\n                            )\n                        )\n                    }\n                },\n                modifier = Modifier.padding(top = 12.dp)\n            ) {\n                Text(&quot;\u6a29\u9650\u3092\u30ea\u30af\u30a8\u30b9\u30c8&quot;)\n            }\n        }\n        return\n    }\n\n    Column(modifier = Modifier\n        .fillMaxSize()\n        .padding(16.dp)) {\n        Text(text = if (isAdvertising) &quot;iBeacon \u767a\u4fe1\u4e2d&quot; else &quot;iBeacon \u505c\u6b62\u4e2d&quot;, style = MaterialTheme.typography.titleMedium)\n\n        Row(modifier = Modifier.padding(top = 12.dp)) {\n            OutlinedTextField(\n                value = majorHex,\n                onValueChange = { majorHex = it.filterHex(limit = 4) },\n                label = { Text(&quot;Major (hex)&quot;) },\n                singleLine = true,\n                modifier = Modifier.weight(1f)\n            )\n            OutlinedTextField(\n                value = minorHex,\n                onValueChange = { minorHex = it.filterHex(limit = 4) },\n                label = { Text(&quot;Minor (hex)&quot;) },\n                singleLine = true,\n                modifier = Modifier\n                    .weight(1f)\n                    .padding(start = 8.dp)\n            )\n        }\n\n        AdvertiseSettingRow(\n            advertiseMode = advertiseMode,\n            advertiseTxPowerLevel = advertiseTxPowerLevel,\n            onAdvertiseModeChange = { mode -&gt;\n                onAdvertiseModeChange(mode)\n                transmitter.advertiseMode = mode\n                restartIfAdvertising()\n            },\n            onAdvertiseTxPowerChange = { level -&gt;\n                onAdvertiseTxPowerChange(level)\n                transmitter.advertiseTxPowerLevel = level\n                restartIfAdvertising()\n            }\n        )\n\n        Row(modifier = Modifier.padding(top = 12.dp)) {\n            Switch(\n                checked = isAdvertising,\n                onCheckedChange = { checked -&gt;\n                    if (checked) {\n                        transmitter.major = majorHex.toIntOrNull(16) ?: 0\n                        transmitter.minor = minorHex.toIntOrNull(16) ?: 0\n                        transmitter.advertiseMode = advertiseMode\n                        transmitter.advertiseTxPowerLevel = advertiseTxPowerLevel\n                        transmitter.startTransmitter()\n                    } else {\n                        transmitter.stopTransmitter()\n                    }\n                    isAdvertising = checked\n                }\n            )\n            Text(\n                text = if (isAdvertising) &quot;ON&quot; else &quot;OFF&quot;,\n                modifier = Modifier.padding(start = 8.dp),\n                style = MaterialTheme.typography.bodyMedium\n            )\n        }\n\n        Text(\n            text = &quot;UUID: ${BeaconTransmitter.SERVICE_UUID}&quot;,\n            style = MaterialTheme.typography.bodyMedium,\n            modifier = Modifier.padding(top = 16.dp)\n        )\n    }\n}\n<\/pre><\/div>\n\n\n<p>iBeaqcon \u3092\u767a\u4fe1\u3059\u308b\u3068\u304d\u306b major \u3068 minor \u3092\u6307\u5b9a\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>iBeacon \u306e\u767a\u4fe1<\/strong><\/h2>\n\n\n\n<p>iBeacon \u306e\u767a\u4fe1\u306f &nbsp;AltBeacon \u3092\u4f7f\u3063\u3066\u3044\u308b\u306e\u3067\u3059\u304c\u3001\u3053\u308c\u3082 Android \u306e BluetoothLeAdvertiser \u3092\u76f4\u63a5\u4f7f\u3046\u65b9\u6cd5\u3082\u691c\u8a0e\u3057\u3066\u3044\u307e\u3059\u3002\u96fb\u6587\u30c7\u30fc\u30bf\u3092\u4f5c\u308b\u306e\u306f BluetoothLeAdvertiser \u3067\u3082\u3042\u307e\u308a\u5909\u308f\u3089\u306a\u3044\u3068\u3044\u3046\u3053\u3068\u3068\u3001BLE5 \u306e\u62e1\u5f35\u6a5f\u80fd\u3092\u4f7f\u3046\u3068\u304d\u306b\u3001AltBeacon \u3060\u3068\u5bfe\u5fdc\u3067\u304d\u306a\u3044\u53ef\u80fd\u6027\u304c\u3042\u308b\u305f\u3081\u3067\u3059\u3002Extended Advertising \u3092\u4f7f\u3046\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u3044\u304d\u307e\u3059\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: scala; title: ; notranslate\" title=\"\">\nclass BeaconTransmitter(\n    private val context: Context,\n    major: Int = 0,\n    minor: Int = 0\n) {\n    \n    companion object {\n        const val TAG = &quot;BeaconTransmitter&quot;\n        val SERVICE_UUID: UUID = UUID.fromString(&quot;90FA7ABE-FAB6-485E-B700-1A17804CAA13&quot;)        \/\/ FolkBears \u30b5\u30fc\u30d3\u30b9\n    }\n    private var beaconTransmitter: org.altbeacon.beacon.BeaconTransmitter? = null\n    var major: Int = major\n    var minor: Int = minor\n    var advertiseMode: Int = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER\n    var advertiseTxPowerLevel: Int = AdvertiseSettings.ADVERTISE_TX_POWER_LOW\n\n    private fun startBeaconTransmission() {\n        \/\/ Permission check (Android 12+ requires BLUETOOTH_ADVERTISE)\n        val advertiseGranted = ContextCompat.checkSelfPermission(\n            context,\n            android.Manifest.permission.BLUETOOTH_ADVERTISE\n        ) == android.content.pm.PackageManager.PERMISSION_GRANTED\n        if (!advertiseGranted) {\n            Log.e(TAG, &quot;BLUETOOTH_ADVERTISE permission not granted; cannot start advertising&quot;)\n            return\n        }\n\n        val adapter = BluetoothAdapter.getDefaultAdapter()\n        if (adapter == null) {\n            Log.e(TAG, &quot;BluetoothAdapter not available&quot;)\n            return\n        }\n        if (!adapter.isEnabled) {\n            Log.e(TAG, &quot;BluetoothAdapter disabled; enable Bluetooth and retry&quot;)\n            return\n        }\n\n        \/\/ \u4ee5\u4e0b org.altbeacon.beacon \u3092\u5229\u7528\u3057\u306a\u3044\u65b9\u6cd5\u3082\u691c\u8a0e\n\n        val support = org.altbeacon.beacon.BeaconTransmitter.checkTransmissionSupported(context)\n        if (support != org.altbeacon.beacon.BeaconTransmitter.SUPPORTED) {\n            Log.e(TAG, &quot;Beacon transmission not supported: code=$support&quot;)\n            return\n        }\n\n        val beacon = Beacon.Builder()\n            .setId1(SERVICE_UUID.toString()) \/\/ UUID\n            .setId2(major.toString()) \/\/ Major (10\u9032\u6570\u6587\u5b57\u5217)\n            .setId3(minor.toString()) \/\/ Minor (10\u9032\u6570\u6587\u5b57\u5217)\n            .setManufacturer(0x004C) \/\/ Apple iBeacon \u306e\u30e1\u30fc\u30ab\u30fc\u30b3\u30fc\u30c9\n            .setTxPower(-59) \/\/ \u4fe1\u53f7\u5f37\u5ea6 (dBm)\u306f\u4eee\u8a2d\u5b9a\n            .build()\n\n        \/\/ val beaconParser = BeaconParser().setBeaconLayout(BeaconParser.ALTBEACON_LAYOUT)\n        val beaconParser = BeaconParser().setBeaconLayout(&quot;m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24&quot;)\n        val altBeaconTransmitter = BeaconTransmitter(context, beaconParser).apply {\n            advertiseMode = this@BeaconTransmitter.advertiseMode\n            advertiseTxPowerLevel = this@BeaconTransmitter.advertiseTxPowerLevel\n            isConnectable = false \/\/ \u975e\u30b3\u30cd\u30af\u30bf\u30d6\u30eb\u306b\n        }\n\n        try {\n            altBeaconTransmitter?.startAdvertising(beacon, object : AdvertiseCallback() {\n                override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {\n                    Log.d(TAG, &quot;iBeacon \u767a\u4fe1\u958b\u59cb&quot;)\n                }\n                override fun onStartFailure(errorCode: Int) {\n                    Log.e(TAG, &quot;iBeacon \u767a\u4fe1\u306b\u5931\u6557: $errorCode&quot;)\n                }\n            })\n        } catch (e: SecurityException) {\n            Log.e(TAG, &quot;SecurityException when starting advertising: ${e.message}&quot;)\n        } catch (e: Throwable) {\n            Log.e(TAG, &quot;Unexpected error when starting advertising: ${e.message}&quot;)\n        }\n    }\n\n    \/\/\/\n    \/\/\/ @break Beacon \u306e\u767a\u4fe1\u958b\u59cb\n    \/\/\/\n    fun startTransmitter() {\n        Log.d(TAG, &quot;startTransmitter&quot;)\n        if (beaconTransmitter == null ) {\n            startBeaconTransmission()\n        }\n    }\n    \/\/\/\n    \/\/\/ @brief Beacon \u306e\u767a\u4fe1\u505c\u6b62\n    \/\/\/\n    fun stopTransmitter() {\n        Log.d(TAG, &quot;stopTransmitter&quot;)\n        beaconTransmitter?.stopAdvertising()\n        beaconTransmitter = null\n    }\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>EN API \u5f62\u5f0f\u306e\u767a\u4fe1<\/strong><\/h2>\n\n\n\n<p>EN API \u5f62\u5f0f\u306e\u767a\u4fe1\u306f\u300116 bit Service UUID \u3092\u6307\u5b9a\u3057\u3066\u767a\u4fe1\u3059\u308b\u30d1\u30bf\u30fc\u30f3\u3067\u3059\u3002BluetoothLeAdvertiser \u3092\u4f7f\u3044\u307e\u3059\u3002<br>EN API \u306e 0xFD6F \u3068\u5b9f\u9a13\u7528\u306e 0xFF00 \u306e\u3069\u3061\u3089\u304b\u3067\u9001\u4fe1\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u307e\u3059\u3002<br>Android \u306e\u5834\u5408\u306f 0xFD6F \u3082 0xFF00 \u3082\u4e21\u65b9\u3068\u3082\u53d7\u4fe1\u3067\u304d\u307e\u3059\u3002iOS \u306e\u5834\u5408\u306f 0xFF00 \u306e\u307b\u3046\u3060\u3051\u304c\u53d7\u4fe1\u3067\u304d\u307e\u3059\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: scala; title: ; notranslate\" title=\"\">\nclass ENSimTransmitter(\n\tprivate val context: Context,\n\ttempIdBytes: ByteArray = ByteArray(16),\n\tuseAltService: Boolean = false\n) {\n\n\tcompanion object {\n\t\tconst val TAG = &quot;ENSimTransmitter&quot;\n\t\tval SERVICE_UUID: UUID = UUID.fromString(&quot;0000FD6F-0000-1000-8000-00805F9B34FB&quot;)\n\t\tval SERVICE_UUID_ALT: UUID = UUID.fromString(&quot;0000FF00-0000-1000-8000-00805F9B34FB&quot;)\n\t\tval SERVICE_DATA_UUID_ALT: UUID = UUID.fromString(&quot;00000001-0000-1000-8000-00805F9B34FB&quot;)\n\t}\n\n    var useAltService: Boolean = useAltService\n    var tempIdBytes: ByteArray = tempIdBytes\n\tvar advertiseMode: Int = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER\n\tvar advertiseTxPowerLevel: Int = AdvertiseSettings.ADVERTISE_TX_POWER_LOW\n\n\tprivate var advertiser: BluetoothLeAdvertiser? = null\n\tprivate var advertiseCallback: AdvertiseCallback? = null\n\t@Volatile\n\tprivate var isAdvertising = false\n\n\t\/\/\/\n\t\/\/\/ ENSim \u306e\u767a\u4fe1\u958b\u59cb\n\t\/\/\/\n\tfun startTransmitter() {\n\t\tLog.d(TAG, &quot;startTransmitter&quot;)\n        startAdvertisingInternal()\n\t}\n\n\t\/\/\/\n\t\/\/\/ ENSim \u306e\u767a\u4fe1\u505c\u6b62\n\t\/\/\/\n\tfun stopTransmitter() {\n\t\tLog.d(TAG, &quot;stopTransmitter&quot;)\n\t\tadvertiser?.stopAdvertising(advertiseCallback)\n\t}\n\n\tprivate fun startAdvertisingInternal() {\n        if ( advertiser == null ) {\n            val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager\n            val adapter = bluetoothManager.adapter\n            advertiser = adapter.bluetoothLeAdvertiser\n        }\n\t\tval adv = advertiser ?: run {\n\t\t\tLog.e(TAG, &quot;BluetoothLeAdvertiser \u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f&quot;)\n\t\t\treturn\n\t\t}\n\n\t\tval settings = AdvertiseSettings.Builder()\n\t\t\t.setAdvertiseMode(advertiseMode)\n\t\t\t.setTxPowerLevel(advertiseTxPowerLevel)\n\t\t\t.setConnectable(false)\n\t\t\t.build()\n\n\t\tval targetUuid = if (useAltService) SERVICE_UUID_ALT else SERVICE_UUID\n\t\tval dataUuidForPayload = if (useAltService) SERVICE_DATA_UUID_ALT else targetUuid\n\n\t\tval data = AdvertiseData.Builder()\n\t\t\t.setIncludeDeviceName(false)\n\t\t\t.setIncludeTxPowerLevel(true)\n\t\t\t.addServiceUuid(ParcelUuid(targetUuid))\n\t\t\t.addServiceData(ParcelUuid(dataUuidForPayload), tempIdBytes)\n\t\t\t.build()\n\n\t\tadvertiseCallback = object : AdvertiseCallback() {\n\t\t\toverride fun onStartSuccess(settingsInEffect: AdvertiseSettings) {\n\t\t\t\tsuper.onStartSuccess(settingsInEffect)\n\t\t\t\tisAdvertising = true\n\t\t\t\tLog.d(TAG, &quot;ENSim advertise start&quot;)\n\t\t\t}\n\n\t\t\toverride fun onStartFailure(errorCode: Int) {\n\t\t\t\tsuper.onStartFailure(errorCode)\n\t\t\t\tisAdvertising = false\n\t\t\t\tLog.e(TAG, &quot;ENSim advertise failed: $errorCode&quot;)\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tadv.startAdvertising(settings, data, advertiseCallback)\n\t\t} catch (e: Exception) {\n\t\t\tisAdvertising = false\n\t\t\tLog.e(TAG, &quot;startAdvertising exception: ${e.message}&quot;)\n\t\t}\n\t}\n\n\tprivate fun String.toByteArrayFromHex(): ByteArray {\n\t\tif (length % 2 != 0) return ByteArray(0)\n\t\treturn chunked(2)\n\t\t\t.mapNotNull { it.toIntOrNull(16)?.toByte() }\n\t\t\t.toByteArray()\n\t}\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>\u30c7\u30d0\u30a4\u30b9\u767a\u898b\u306e\u305f\u3081\u306e\u30a2\u30c9\u30d0\u30bf\u30a4\u30ba\u767a\u4fe1<\/strong><\/h2>\n\n\n\n<p>GATT \u63a5\u7d9a\u3092\u3059\u308b\u305f\u3081\u306b\u306f\u3001\u307e\u305a\u306f\u30c7\u30d0\u30a4\u30b9\u3092\u767a\u898b\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u3002\u305d\u306e\u767a\u898b\u90e8\u5206\u3060\u3051\u3092\u767a\u4fe1\u3057\u307e\u3059\u3002\u3053\u308c\u306f\u3001FolkBears \u306e\u30b3\u30cd\u30af\u30b7\u30e7\u30f3\u7248\u3092\u4f5c\u3063\u3066\u3044\u305f\u3068\u304d\u306b\u3001\u306a\u304b\u306a\u304b\u30c7\u30d0\u30a4\u30b9\u304c\u767a\u898b\u3067\u304d\u306a\u3044\u3068\u3053\u308d\u306e\u539f\u56e0\u3092\u63b4\u3080\u305f\u3081\u306e\u30c4\u30fc\u30eb\u3067\u3059\u3002\u5b9f\u969b\u306e\u3068\u3053\u308d\u3001\u30c7\u30d0\u30a4\u30b9\u540d\u767a\u4fe1\u3068\u5909\u308f\u3089\u306a\u3044\uff08iBeacon \u3068\u3082\u5909\u308f\u3089\u306a\u3044\uff09\u73fe\u8c61\u304c\u767a\u751f\u3057\u307e\u3059\u3002\u3064\u307e\u308a\u306f\u3001\u767a\u4fe1\u30bf\u30a4\u30df\u30f3\u30b0\u3068\u53d7\u4fe1\u30bf\u30a4\u30df\u30f3\u30b0\u306e\u7d44\u307f\u5408\u308f\u305b\u306b\u3088\u3063\u3066\u3001\u30c7\u30d0\u30a4\u30b9\u304c\u767a\u898b\u3055\u308c\u308b\u307e\u3067\u306e\u6642\u9593\u304c\u5927\u304d\u304f\u5909\u308f\u308a\u307e\u3059\u3002<br>\u767a\u898b\u3057\u305f\u5f8c\u306f\u3001\u304a\u305d\u3089\u304f\u901a\u5e38\u306b GATT \u30b5\u30fc\u30d3\u30b9\u3092\u63a5\u7d9a\u3067\u304d\u308b\u306f\u305a\u3067\u3059\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: scala; title: ; notranslate\" title=\"\">\nclass GattAdvertise(\n    private val context: Context\n)\n{\n    companion object {\n        const val TAG = &quot;GattAdvertise&quot;\n        val SERVICE_UUID: UUID = UUID.fromString(&quot;90FA7ABE-FAB6-485E-B700-1A17804CAA13&quot;)        \/\/ FolkBears \u30b5\u30fc\u30d3\u30b9\n    }\n\n    private var advertiser: BluetoothLeAdvertiser? = null\n    @Volatile\n    var isAdvertising = false\n    private var lastStopTime = 0L\n    private var backgroundRetryRunnable: Runnable? = null\n\n    var advertiseMode: Int = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER\n    var advertiseTxPowerLevel: Int = AdvertiseSettings.ADVERTISE_TX_POWER_LOW\n\n\n    private var currentCallback: AdvertiseCallback? = null\n\n    private fun createAdvertiseCallback(): AdvertiseCallback {\n        return object : AdvertiseCallback() {\n            override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {\n                super.onStartSuccess(settingsInEffect)\n                Log.d(TAG, &quot;Advertising onStartSuccess&quot;)\n                isAdvertising = true\n            }\n\n            override fun onStartFailure(errorCode: Int) {\n                super.onStartFailure(errorCode)\n                val reason: String\n\n                when (errorCode) {\n                    ADVERTISE_FAILED_ALREADY_STARTED -&gt; {\n                        Log.w(TAG, &quot;Advertising already started on Android ${Build.VERSION.SDK_INT}, forcing stop and retry&quot;)\n                        return\n                    }\n                    ADVERTISE_FAILED_FEATURE_UNSUPPORTED -&gt; {\n                        reason = &quot;ADVERTISE_FAILED_FEATURE_UNSUPPORTED&quot;\n                        isAdvertising = false\n                    }\n                    ADVERTISE_FAILED_INTERNAL_ERROR -&gt; {\n                        reason = &quot;ADVERTISE_FAILED_INTERNAL_ERROR&quot;\n                        isAdvertising = false\n                    }\n                    ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -&gt; {\n                        reason = &quot;ADVERTISE_FAILED_TOO_MANY_ADVERTISERS&quot;\n                        isAdvertising = false\n                    }\n                    ADVERTISE_FAILED_DATA_TOO_LARGE -&gt; {\n                        reason = &quot;ADVERTISE_FAILED_DATA_TOO_LARGE&quot;\n                        isAdvertising = false\n                    }\n                    else -&gt; {\n                        reason = &quot;UNDOCUMENTED&quot;\n                        isAdvertising = false\n                    }\n                }\n                Log.d(TAG, &quot;Advertising onStartFailure: $errorCode - $reason&quot;)\n            }\n        }\n    }\n\n    private var data: AdvertiseData? = null\n\n    fun startAdvertising() {\n\n        if (  advertiser == null ) {\n            val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager\n            val adapter = bluetoothManager.adapter\n            advertiser = adapter.bluetoothLeAdvertiser\n        }   \n\n        if (isAdvertising) {\n            Log.d(TAG, &quot;Already advertising or starting: advertising=$isAdvertising&quot;)\n            return\n        }\n\n        data = AdvertiseData.Builder()\n            .setIncludeDeviceName(false)\n            .setIncludeTxPowerLevel(true)\n            .addServiceUuid(ParcelUuid(SERVICE_UUID))\n            .build()\n\n        currentCallback = createAdvertiseCallback()\n        val settings = AdvertiseSettings.Builder()\n            .setTxPowerLevel(advertiseTxPowerLevel)\n            .setAdvertiseMode(advertiseMode)\n            .setConnectable(true)\n            .build()\n\n        advertiser?.startAdvertising(settings, data, currentCallback)\n    }\n\n    fun stopAdvertising() {\n        if ( isAdvertising == false ) {\n            Log.d(TAG, &quot;Not currently advertising, skipping stop&quot;)\n            return\n        }\n        currentCallback?.let { advertiser?.stopAdvertising(it) }\n        isAdvertising = false\n    }\n}\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>Manufacturer Data \u5f62\u5f0f\u306e\u767a\u4fe1<\/strong><\/h2>\n\n\n\n<p>\u81ea\u7531\u306a\u578b\u5f0f\u3067\u30c7\u30fc\u30bf\u3092\u30d6\u30ed\u30fc\u30c9\u30ad\u30e3\u30b9\u30c8\u3059\u308b\u5834\u5408\u306f\u3001Manufacturer Data \u5f62\u5f0f\u3067\u767a\u4fe1\u3059\u308b\u306e\u304c\u4e00\u756a\u3044\u3044\u306e\u3067\u3059\u3002Manufacturer Data \u5f62\u5f0f\u306e\u5834\u5408\u306f\u3001Android \u3067\u3082 iOS \u3067\u3082\u53d7\u4fe1\u304c\u53ef\u80fd\u3067\u3059\u3002<br>\u305f\u3060\u3057\u3001iOS \u306e\u5834\u5408\u306f\u3001Manufacturer Data \u5f62\u5f0f\u3067\u306e\u767a\u4fe1\u304c\u3067\u304d\u306a\u3044\u306e\u3067\u3001\u63a5\u89e6\u78ba\u8a8d\u30a2\u30d7\u30ea FolkBears \u306e\u4f5c\u6210\u306b\u306f\u5411\u3044\u3066\u3044\u307e\u305b\u3093&#8230;\u304c\u3001m5stack \u306a\u3069\u306e\u5c02\u7528\u30c7\u30d0\u30a4\u30b9\u3092\u4f5c\u308c\u3070\u7d50\u69cb\u3044\u3051\u308b\u306e\u3067\u306f\u306a\u3044\u304b\u3001\u3068\u601d\u3063\u3066\u3044\u307e\u3059\u3002\u305d\u306e\u5834\u5408\u306f\u300116 bit Service UUID \u3092\u4f7f\u3046\u65b9\u6cd5\u3082\u3042\u308b\u306e\u3067\u3059\u304c\u3002<\/p>\n\n\n\n<p>Manufacturer Data \u306f\u81ea\u7531\u306b\u4f5c\u308c\u308b\u306e\u3067\u3059\u304c\u3001iBeacon \u3063\u307d\u304f\u5148\u982d\u306b beacon_type \u3068 beacon_length \u3092\u5165\u308c\u3066\u3042\u308a\u307e\u3059\u3002\u3053\u306e\u3042\u305f\u308a\u3001Android \u3067\u4f7f\u308f\u308c\u3066\u3044\u305f AltBeacon \u5f62\u5f0f\u3067\u3082\u69cb\u3044\u307e\u305b\u3093\u3002\u76f8\u4e92\u904b\u7528\u3092\u8003\u3048\u306a\u3051\u308c\u3070\u3001\u72ec\u81ea\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u5341\u5206\u3060\u3068\u601d\u3044\u307e\u3059\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: scala; title: ; notranslate\" title=\"\">\nclass ManufacturerDataTransmitter(\n\tprivate val context: Context,\n\tmanufacturerId: Int = 0xFFFF,\n    tempIdBytes: ByteArray = ByteArray(16)\n) {\n\n\tcompanion object {\n\t\tconst val TAG = &quot;ManufacturerDataTx&quot;\n\t}\n\n    var tempIdBytes: ByteArray = tempIdBytes\n    var manufacturerId: Int = manufacturerId\n\tvar advertiseMode: Int = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER\n\tvar advertiseTxPowerLevel: Int = AdvertiseSettings.ADVERTISE_TX_POWER_LOW\n\n\tprivate var advertiser: BluetoothLeAdvertiser? = null\n\tprivate var advertiseCallback: AdvertiseCallback? = null\n\t@Volatile\n\tprivate var isAdvertising = false\n\t@Volatile\n\tprivate var payload: ByteArray = ByteArray(0)\n\n\tinit {\n\t\t\/\/ TempId \u3092\u4e8b\u524d\u306b\u30ed\u30fc\u30c9\n\t\tCoroutineScope(Dispatchers.IO).launch {\n\t\t\tpayload = buildPayload()\n\t\t}\n\t}\n\n\t\/\/\/\n\t\/\/\/ Manufacturer Data \u767a\u4fe1\u958b\u59cb\n\t\/\/\/\n\tfun startTransmitter() {\n\t\tLog.d(TAG, &quot;startTransmitter&quot;)\n\t\tif (isAdvertising) return\n        startAdvertisingInternal()\n\t}\n\n\t\/\/\/\n\t\/\/\/ Manufacturer Data \u767a\u4fe1\u505c\u6b62\n\t\/\/\/\n\tfun stopTransmitter() {\n\t\tLog.d(TAG, &quot;stopTransmitter&quot;)\n\t\tadvertiser?.stopAdvertising(advertiseCallback)\n\t\tadvertiseCallback = null\n\t\tisAdvertising = false\n\t}\n\n\tprivate fun startAdvertisingInternal() {\n        if ( advertiser == null ) {\n            val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager\n            val adapter = bluetoothManager.adapter\n            advertiser = adapter.bluetoothLeAdvertiser\n        }\n\n\t\tval adv = advertiser ?: run {\n\t\t\tLog.e(TAG, &quot;BluetoothLeAdvertiser \u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f&quot;)\n\t\t\treturn\n\t\t}\n\n\t\tval settings = AdvertiseSettings.Builder()\n\t\t\t.setAdvertiseMode(advertiseMode)\n\t\t\t.setTxPowerLevel(advertiseTxPowerLevel)\n\t\t\t.setConnectable(false)\n\t\t\t.build()\n\n\t\tval data = AdvertiseData.Builder()\n\t\t\t.setIncludeDeviceName(false)\n\t\t\t.setIncludeTxPowerLevel(true)\n\t\t\t.addManufacturerData(manufacturerId, payload)\n\t\t\t.build()\n\n\t\tadvertiseCallback = object : AdvertiseCallback() {\n\t\t\toverride fun onStartSuccess(settingsInEffect: AdvertiseSettings) {\n\t\t\t\tsuper.onStartSuccess(settingsInEffect)\n\t\t\t\tisAdvertising = true\n\t\t\t\tLog.d(TAG, &quot;Manufacturer advertise start (id=0x${manufacturerId.toString(16)})&quot;)\n\t\t\t}\n\n\t\t\toverride fun onStartFailure(errorCode: Int) {\n\t\t\t\tsuper.onStartFailure(errorCode)\n\t\t\t\tisAdvertising = false\n\t\t\t\tLog.e(TAG, &quot;Manufacturer advertise failed: $errorCode&quot;)\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tadv.startAdvertising(settings, data, advertiseCallback)\n\t\t} catch (e: Exception) {\n\t\t\tisAdvertising = false\n\t\t\tLog.e(TAG, &quot;startAdvertising exception: ${e.message}&quot;)\n\t\t}\n\t}\n\n\tprivate suspend fun buildPayload(): ByteArray {\n\t\tval currentTime = System.currentTimeMillis()\n\t\tif (tempIdBytes.size &lt; 16) return ByteArray(0)\n\n\t\t\/\/ 0x02(type), 0x10(length=16), then 16-byte tempId\n\t\tval payload = ByteArray(2 + 16)\n\t\tpayload&#x5B;0] = 0x02\n\t\tpayload&#x5B;1] = 0x10\n\t\ttempIdBytes.copyInto(destination = payload, destinationOffset = 2, endIndex = 16)\n\t\treturn payload\n\t}\n\n\tprivate fun String.toByteArrayFromHex(): ByteArray {\n\t\tif (length % 2 != 0) return ByteArray(0)\n\t\treturn chunked(2)\n\t\t\t.mapNotNull { it.toIntOrNull(16)?.toByte() }\n\t\t\t.toByteArray()\n\t}\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>\u52d5\u4f5c\u78ba\u8a8d<\/strong><\/h2>\n\n\n\n<p>\u5b9f\u969b\u306e\u52d5\u304d\u306f\u3001<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u53d7\u4fe1\u5074\u3092 Low Power \u306b\u56fa\u5b9a<\/li>\n\n\n\n<li>\u767a\u4fe1\u5074\u3092 Low Power \u3068 Low Latency \u3067\u5207\u308a\u66ff\u3048\u308b<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Low Power \u3067\u767a\u4fe1<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-47.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-47.png\" alt=\"\" class=\"wp-image-12067\" srcset=\"http:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-47.png 1024w, http:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-47-300x225.png 300w, http:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-47-768x576.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Low Latency \u3067\u767a\u4fe1<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-48.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-48.png\" alt=\"\" class=\"wp-image-12068\" srcset=\"http:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-48.png 1024w, http:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-48-300x225.png 300w, http:\/\/www.moonmile.net\/blog\/wp-content\/uploads\/2026\/02\/image-48-768x576.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>\u5b9f\u6e2c\u3059\u308b\u3068\u3001\u53d7\u767a\u4fe1\u306e\u3069\u3061\u3089\u304b\u304c Low Latency \u306b\u306a\u3063\u3066\u3044\u308b\u3068\u53d7\u4fe1\u306e\u9045\u5ef6\u306f\u5c11\u306a\u3044\u306e\u3067\u3059\u304c\u3001\u4e21\u65b9\u3068\u3082 Low Power \u306b\u306a\u3063\u3066\u3044\u308b\u3068\u3001\u305f\u307e\u306b\u53d7\u4fe1\u304c\u9045\u304f\u306a\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u3053\u308c\u306f\u5f8c\u3067\u8a08\u7b97\u5f0f\u3092\u51fa\u3057\u3066\u5b9f\u6e2c\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<p>\u3042\u3068\u3001Android \u306e\u5834\u5408\u306f BLE 5 \u306e Extended Advertising \u3092\u4f7f\u3046\u3053\u3068\u304c\u3067\u304d\u308b\u306e\u3067\u3001startAdvertisingSet \u3092\u4f7f\u3063\u3066\u767a\u4fe1\u306e\u65b9\u3092\u7d30\u304b\u304f\u8a2d\u5b9a\u3057\u3066\u3044\u304d\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u53c2\u8003\u30b3\u30fc\u30c9<\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/FolkBearsGroup\/ble-tools\/tree\/master\/folkbears-transmitter-droid\">https:\/\/github.com\/FolkBearsGroup\/ble-tools\/tree\/master\/folkbears-transmitter-droid<\/a><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>iBeacon \u306e\u53d7\u4fe1\u6a5f\u304c\u3067\u304d\u305f\u306e\u3067\u3001\u9006\u306b\u767a\u4fe1\u6a5f\u3092\u4f5c\u3063\u3066\u3044\u304d\u307e\u3059\u3002 Android \u3067 iBeacon \u3068 EN API \u53d7\u4fe1\u6a5f\u3092\u4f5c\u308b \u3053\u308c\u3092\u7d44\u307f\u5408\u308f\u305b\u308b\u3068 Android\/iOS \u306e\u76f8\u4e92\u306b iBeacon \u3084\u305d\u306e\u4ed6\u306e  &hellip; <a href=\"http:\/\/www.moonmile.net\/blog\/archives\/12066\">\u7d9a\u304d\u3092\u8aad\u3080 <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[3,110],"tags":[],"class_list":["post-12066","post","type-post","status-publish","format-standard","hentry","category-dev","category-folkbears"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/posts\/12066","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/comments?post=12066"}],"version-history":[{"count":3,"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/posts\/12066\/revisions"}],"predecessor-version":[{"id":12071,"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/posts\/12066\/revisions\/12071"}],"wp:attachment":[{"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/media?parent=12066"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/categories?post=12066"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.moonmile.net\/blog\/wp-json\/wp\/v2\/tags?post=12066"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}