iBeacon is Apple's Bluetooth Low Energy (BLE) technology that enables devices to broadcast and detect proximity information. This guide demonstrates how to build a Java-based iBeacon SDK for detecting, monitoring, and ranging iBeacons on various platforms.
Why Build an iBeacon SDK in Java?
- Cross-Platform Support: Run on Windows, Linux, macOS with Bluetooth adapters
- Proximity Marketing: Enable location-based notifications and content
- Indoor Navigation: Implement indoor positioning systems
- Asset Tracking: Monitor equipment and inventory location
- Attendance Systems: Track presence in specific areas
Prerequisites
- Bluetooth 4.0+ Adapter with BLE support
- Java 8+ with Bluetooth libraries
- Maven/Gradle for dependency management
Step 1: Project Dependencies
Maven (pom.xml):
<dependencies> <!-- Bluetooth LE Libraries --> <dependency> <groupId>com.github.hypfvieh</groupId> <artifactId>bluez-dbus</artifactId> <version>0.1.8</version> </dependency> <!-- TinyB Bluetooth Library (Linux) --> <dependency> <groupId>tinyb</groupId> <artifactId>tinyb</artifactId> <version>0.5.1</version> </dependency> <!-- BlueCove (Windows/macOS) --> <dependency> <groupId>net.sf.bluecove</groupId> <artifactId>bluecove</artifactId> <version>2.1.1</version> </dependency> <!-- BleGATT (Cross-platform) --> <dependency> <groupId>io.github.weltonfelix</groupId> <artifactId>bluetooth-manager</artifactId> <version>1.0.0</version> </dependency> <!-- Event Bus for Beacon Events --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <!-- Spring Boot (Optional) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <!-- Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.7.0</version> </dependency> </dependencies>
Step 2: Core Configuration
@Configuration @ConfigurationProperties(prefix = "ibeacon") @Data public class IBeaconConfig { private boolean enabled = true; private long scanDuration = 10000; // ms private long scanInterval = 5000; // ms private int rssiThreshold = -85; // dBm private double immediateRange = 0.5; // meters private double nearRange = 3.0; // meters private double farRange = 15.0; // meters private String beaconFilter; // UUID to filter specific beacons private boolean backgroundScanning = false; private int maxBeaconCacheSize = 1000; private long beaconTimeout = 30000; // ms // Platform-specific configurations private LinuxConfig linux = new LinuxConfig(); private WindowsConfig windows = new WindowsConfig(); private MacConfig mac = new MacConfig(); @Data public static class LinuxConfig { private String bluezInterface = "org.bluez"; private String adapterPath = "/org/bluez/hci0"; } @Data public static class WindowsConfig { private boolean useNativeDriver = true; private int comPort = 1; } @Data public static class MacConfig { private boolean useCoreBluetooth = true; } } @Component @Slf4j public class PlatformDetector { public enum Platform { WINDOWS, LINUX, MACOS, UNSUPPORTED } public Platform getCurrentPlatform() { String osName = System.getProperty("os.name").toLowerCase(); if (osName.contains("win")) { return Platform.WINDOWS; } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { return Platform.LINUX; } else if (osName.contains("mac")) { return Platform.MACOS; } else { return Platform.UNSUPPORTED; } } public boolean isBluetoothSupported() { try { // Check if Bluetooth hardware is available return checkBluetoothAvailability(); } catch (Exception e) { log.warn("Bluetooth not supported on this platform", e); return false; } } private boolean checkBluetoothAvailability() { // Platform-specific Bluetooth availability check Platform platform = getCurrentPlatform(); switch (platform) { case LINUX: return checkLinuxBluetooth(); case WINDOWS: return checkWindowsBluetooth(); case MACOS: return checkMacBluetooth(); default: return false; } } private native boolean checkLinuxBluetooth(); private native boolean checkWindowsBluetooth(); private native boolean checkMacBluetooth(); } Step 3: Core iBeacon Models
@Data @AllArgsConstructor @NoArgsConstructor public class IBeacon { // iBeacon advertisement structure private String uuid; // 16-byte UUID private int major; // 2-byte major number private int minor; // 2-byte minor number private int txPower; // Calibrated RSSI at 1 meter private int rssi; // Received signal strength private String macAddress; // Bluetooth MAC address private String name; // Beacon name if available private Proximity proximity; // Calculated proximity private double distance; // Estimated distance in meters private long lastSeen; // Last detection timestamp private int packetCount; // Number of packets received private int manufacturer; // Manufacturer ID (Apple = 76) // Helper methods public String getIdentifier() { return String.format("%s:%d:%d", uuid, major, minor); } public boolean isValid() { return uuid != null && !uuid.isEmpty() && manufacturer == 76; // Apple manufacturer ID } public void updateRssi(int newRssi) { this.rssi = newRssi; this.distance = calculateDistance(); this.proximity = calculateProximity(); this.lastSeen = System.currentTimeMillis(); this.packetCount++; } private double calculateDistance() { if (rssi == 0 || txPower == 0) { return -1.0; // Unknown distance } // Log-distance path loss model double ratio = (txPower - rssi) / 20.0; return Math.pow(10, ratio); } private Proximity calculateProximity() { if (distance < 0) { return Proximity.UNKNOWN; } else if (distance <= 0.5) { return Proximity.IMMEDIATE; } else if (distance <= 3.0) { return Proximity.NEAR; } else if (distance <= 15.0) { return Proximity.FAR; } else { return Proximity.FAR; } } } public enum Proximity { UNKNOWN, // Distance cannot be determined IMMEDIATE, // Within 0.5 meters NEAR, // Within 3 meters FAR // Greater than 3 meters } @Data public class BeaconRegion { private String identifier; private String uuid; private Integer major; private Integer minor; private boolean notifyOnEntry = true; private boolean notifyOnExit = true; private boolean notifyOnDisplay = true; public BeaconRegion(String identifier, String uuid) { this.identifier = identifier; this.uuid = uuid; } public BeaconRegion(String identifier, String uuid, Integer major, Integer minor) { this.identifier = identifier; this.uuid = uuid; this.major = major; this.minor = minor; } public boolean matches(IBeacon beacon) { if (!uuid.equalsIgnoreCase(beacon.getUuid())) { return false; } if (major != null && major != beacon.getMajor()) { return false; } if (minor != null && minor != beacon.getMinor()) { return false; } return true; } } @Data @AllArgsConstructor public class BeaconEvent { public enum EventType { BEACON_DISCOVERED, BEACON_UPDATED, BEACON_LOST, REGION_ENTERED, REGION_EXITED, PROXIMITY_CHANGED } private EventType type; private IBeacon beacon; private BeaconRegion region; private long timestamp; private Map<String, Object> additionalData; public BeaconEvent(EventType type, IBeacon beacon) { this.type = type; this.beacon = beacon; this.timestamp = System.currentTimeMillis(); this.additionalData = new HashMap<>(); } } Step 4: Core Scanner Service
public interface BeaconScanner { void startScanning(); void stopScanning(); boolean isScanning(); void addBeaconListener(BeaconListener listener); void removeBeaconListener(BeaconListener listener); List<IBeacon> getDiscoveredBeacons(); void setScanRegion(BeaconRegion region); } public interface BeaconListener { void onBeaconDiscovered(IBeacon beacon); void onBeaconUpdated(IBeacon beacon); void onBeaconLost(IBeacon beacon); void onRegionEntered(BeaconRegion region); void onRegionExited(BeaconRegion region); void onScanStarted(); void onScanStopped(); void onError(Exception error); } @Service @Slf4j public class IBeaconScanner implements BeaconScanner { private final IBeaconConfig config; private final PlatformDetector platformDetector; private final List<BeaconListener> listeners = new CopyOnWriteArrayList<>(); private final Map<String, IBeacon> discoveredBeacons = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); private volatile boolean isScanning = false; private BeaconScannerStrategy scannerStrategy; private ScheduledFuture<?> cleanupTask; public IBeaconScanner(IBeaconConfig config, PlatformDetector platformDetector) { this.config = config; this.platformDetector = platformDetector; initializeScannerStrategy(); } @Override public void startScanning() { if (isScanning) { log.warn("Scanner is already running"); return; } try { if (scannerStrategy.startScanning()) { isScanning = true; startCleanupTask(); notifyScanStarted(); log.info("iBeacon scanning started"); } } catch (Exception e) { log.error("Failed to start beacon scanning", e); notifyError(e); } } @Override public void stopScanning() { if (!isScanning) { return; } try { scannerStrategy.stopScanning(); isScanning = false; stopCleanupTask(); notifyScanStopped(); log.info("iBeacon scanning stopped"); } catch (Exception e) { log.error("Error stopping beacon scanner", e); notifyError(e); } } @Override public boolean isScanning() { return isScanning; } @Override public void addBeaconListener(BeaconListener listener) { listeners.add(listener); } @Override public void removeBeaconListener(BeaconListener listener) { listeners.remove(listener); } @Override public List<IBeacon> getDiscoveredBeacons() { return new ArrayList<>(discoveredBeacons.values()); } @Override public void setScanRegion(BeaconRegion region) { scannerStrategy.setScanRegion(region); } public void onBeaconDiscovered(IBeacon beacon) { if (!beacon.isValid() || beacon.getRssi() < config.getRssiThreshold()) { return; } String beaconKey = beacon.getIdentifier(); IBeacon existingBeacon = discoveredBeacons.get(beaconKey); if (existingBeacon == null) { // New beacon discovered discoveredBeacons.put(beaconKey, beacon); notifyBeaconDiscovered(beacon); log.debug("New beacon discovered: {}", beacon.getIdentifier()); } else { // Existing beacon updated existingBeacon.updateRssi(beacon.getRssi()); notifyBeaconUpdated(existingBeacon); } } public void cleanupExpiredBeacons() { long currentTime = System.currentTimeMillis(); long timeout = config.getBeaconTimeout(); Iterator<Map.Entry<String, IBeacon>> iterator = discoveredBeacons.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, IBeacon> entry = iterator.next(); IBeacon beacon = entry.getValue(); if (currentTime - beacon.getLastSeen() > timeout) { iterator.remove(); notifyBeaconLost(beacon); log.debug("Beacon lost: {}", beacon.getIdentifier()); } } } private void initializeScannerStrategy() { PlatformDetector.Platform platform = platformDetector.getCurrentPlatform(); switch (platform) { case LINUX: scannerStrategy = new LinuxBeaconScanner(config); break; case WINDOWS: scannerStrategy = new WindowsBeaconScanner(config); break; case MACOS: scannerStrategy = new MacBeaconScanner(config); break; default: scannerStrategy = new DummyBeaconScanner(); log.warn("Unsupported platform: {}", platform); } scannerStrategy.setBeaconCallback(this::onBeaconDiscovered); } private void startCleanupTask() { cleanupTask = scheduler.scheduleAtFixedRate( this::cleanupExpiredBeacons, config.getBeaconTimeout() / 2, config.getBeaconTimeout() / 2, TimeUnit.MILLISECONDS ); } private void stopCleanupTask() { if (cleanupTask != null) { cleanupTask.cancel(false); cleanupTask = null; } } // Notification methods private void notifyScanStarted() { listeners.forEach(BeaconListener::onScanStarted); } private void notifyScanStopped() { listeners.forEach(BeaconListener::onScanStopped); } private void notifyBeaconDiscovered(IBeacon beacon) { listeners.forEach(listener -> listener.onBeaconDiscovered(beacon)); } private void notifyBeaconUpdated(IBeacon beacon) { listeners.forEach(listener -> listener.onBeaconUpdated(beacon)); } private void notifyBeaconLost(IBeacon beacon) { listeners.forEach(listener -> listener.onBeaconLost(beacon)); } private void notifyError(Exception error) { listeners.forEach(listener -> listener.onError(error)); } @PreDestroy public void cleanup() { stopScanning(); scheduler.shutdown(); } } Step 5: Platform-Specific Scanner Implementations
// Scanner Strategy Interface public interface BeaconScannerStrategy { boolean startScanning(); boolean stopScanning(); void setScanRegion(BeaconRegion region); void setBeaconCallback(Consumer<IBeacon> beaconCallback); } // Linux Implementation (using BlueZ) @Slf4j public class LinuxBeaconScanner implements BeaconScannerStrategy { private final IBeaconConfig config; private Consumer<IBeacon> beaconCallback; private BluetoothManager bluetoothManager; private BluetoothAdapter bluetoothAdapter; private volatile boolean scanning = false; public LinuxBeaconScanner(IBeaconConfig config) { this.config = config; } @Override public boolean startScanning() { try { bluetoothManager = BluetoothManager.getBluetoothManager(); bluetoothAdapter = bluetoothManager.getAdapter(); if (bluetoothAdapter == null) { throw new RuntimeException("No Bluetooth adapter found"); } if (!bluetoothAdapter.isPowered()) { bluetoothAdapter.setPowered(true); } // Start discovery bluetoothAdapter.startDiscovery(); scanning = true; // Set up device discovery listener setupDiscoveryListener(); return true; } catch (Exception e) { log.error("Failed to start Linux beacon scanner", e); return false; } } @Override public boolean stopScanning() { try { if (bluetoothAdapter != null && scanning) { bluetoothAdapter.stopDiscovery(); scanning = false; } return true; } catch (Exception e) { log.error("Error stopping Linux beacon scanner", e); return false; } } @Override public void setScanRegion(BeaconRegion region) { // Region filtering can be implemented here } @Override public void setBeaconCallback(Consumer<IBeacon> beaconCallback) { this.beaconCallback = beaconCallback; } private void setupDiscoveryListener() { // Implementation for BlueZ device discovery and iBeacon parsing // This would involve D-Bus communication with BlueZ } private IBeacon parseIBeaconData(byte[] manufacturerData, String macAddress, int rssi) { // iBeacon format: // [0-1]: Company ID (0x004C for Apple) // [2]: Data Type (0x02 for iBeacon) // [3]: Data Length (0x15 = 21 bytes) // [4-19]: UUID (16 bytes) // [20-21]: Major (2 bytes) // [22-23]: Minor (2 bytes) // [24]: TX Power (1 byte, signed) if (manufacturerData.length < 25) { return null; } int companyId = ((manufacturerData[0] & 0xFF) << 8) | (manufacturerData[1] & 0xFF); if (companyId != 0x004C) { // Apple company ID return null; } if (manufacturerData[2] != 0x02 || manufacturerData[3] != 0x15) { return null; // Not iBeacon format } // Parse UUID byte[] uuidBytes = Arrays.copyOfRange(manufacturerData, 4, 20); String uuid = bytesToUuid(uuidBytes); // Parse Major int major = ((manufacturerData[20] & 0xFF) << 8) | (manufacturerData[21] & 0xFF); // Parse Minor int minor = ((manufacturerData[22] & 0xFF) << 8) | (manufacturerData[23] & 0xFF); // Parse TX Power (signed byte) int txPower = manufacturerData[24]; IBeacon beacon = new IBeacon(); beacon.setUuid(uuid); beacon.setMajor(major); beacon.setMinor(minor); beacon.setTxPower(txPower); beacon.setRssi(rssi); beacon.setMacAddress(macAddress); beacon.setManufacturer(companyId); beacon.setLastSeen(System.currentTimeMillis()); return beacon; } private String bytesToUuid(byte[] bytes) { StringBuilder uuid = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { uuid.append(String.format("%02x", bytes[i])); if (i == 3 || i == 5 || i == 7 || i == 9) { uuid.append("-"); } } return uuid.toString(); } } // Windows Implementation (using BlueCove) @Slf4j public class WindowsBeaconScanner implements BeaconScannerStrategy { private final IBeaconConfig config; private Consumer<IBeacon> beaconCallback; private volatile boolean scanning = false; private DiscoveryListener discoveryListener; public WindowsBeaconScanner(IBeaconConfig config) { this.config = config; } @Override public boolean startScanning() { try { LocalDevice localDevice = LocalDevice.getLocalDevice(); DiscoveryAgent agent = localDevice.getDiscoveryAgent(); discoveryListener = new IBeaconDiscoveryListener(); // Start inquiry agent.startInquiry(DiscoveryAgent.GIAC, discoveryListener); scanning = true; return true; } catch (Exception e) { log.error("Failed to start Windows beacon scanner", e); return false; } } @Override public boolean stopScanning() { try { if (scanning) { LocalDevice localDevice = LocalDevice.getLocalDevice(); DiscoveryAgent agent = localDevice.getDiscoveryAgent(); agent.cancelInquiry(discoveryListener); scanning = false; } return true; } catch (Exception e) { log.error("Error stopping Windows beacon scanner", e); return false; } } @Override public void setScanRegion(BeaconRegion region) { // Region filtering implementation } @Override public void setBeaconCallback(Consumer<IBeacon> beaconCallback) { this.beaconCallback = beaconCallback; } private class IBeaconDiscoveryListener implements DiscoveryListener { @Override public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) { try { // Check if device has iBeacon data String name = device.getFriendlyName(false); int rssi = 0; // RSSI not directly available in BlueCove // Parse device services to find iBeacon data // This is a simplified implementation } catch (Exception e) { log.debug("Error processing discovered device", e); } } @Override public void inquiryCompleted(int discType) { if (scanning) { // Restart inquiry for continuous scanning try { LocalDevice localDevice = LocalDevice.getLocalDevice(); DiscoveryAgent agent = localDevice.getDiscoveryAgent(); agent.startInquiry(DiscoveryAgent.GIAC, this); } catch (Exception e) { log.error("Failed to restart inquiry", e); } } } @Override public void servicesDiscovered(int transID, ServiceRecord[] services) { // Handle service discovery if needed } } } // Dummy implementation for unsupported platforms public class DummyBeaconScanner implements BeaconScannerStrategy { @Override public boolean startScanning() { return false; } @Override public boolean stopScanning() { return true; } @Override public void setScanRegion(BeaconRegion region) {} @Override public void setBeaconCallback(Consumer<IBeacon> beaconCallback) {} } Step 6: Region Monitoring Service
@Service @Slf4j public class RegionMonitoringService implements BeaconListener { private final Map<String, BeaconRegion> monitoredRegions = new ConcurrentHashMap<>(); private final Map<String, Set<String>> regionBeacons = new ConcurrentHashMap<>(); private final Map<String, Boolean> regionStates = new ConcurrentHashMap<>(); private final List<RegionListener> regionListeners = new CopyOnWriteArrayList<>(); private final IBeaconScanner beaconScanner; public RegionMonitoringService(IBeaconScanner beaconScanner) { this.beaconScanner = beaconScanner; this.beaconScanner.addBeaconListener(this); } public void startMonitoringRegion(BeaconRegion region) { monitoredRegions.put(region.getIdentifier(), region); regionBeacons.put(region.getIdentifier(), new HashSet<>()); regionStates.put(region.getIdentifier(), false); log.info("Started monitoring region: {}", region.getIdentifier()); } public void stopMonitoringRegion(String regionIdentifier) { monitoredRegions.remove(regionIdentifier); regionBeacons.remove(regionIdentifier); regionStates.remove(regionIdentifier); log.info("Stopped monitoring region: {}", regionIdentifier); } public List<BeaconRegion> getMonitoredRegions() { return new ArrayList<>(monitoredRegions.values()); } public void addRegionListener(RegionListener listener) { regionListeners.add(listener); } public void removeRegionListener(RegionListener listener) { regionListeners.remove(listener); } @Override public void onBeaconDiscovered(IBeacon beacon) { checkRegionTransitions(beacon); } @Override public void onBeaconUpdated(IBeacon beacon) { checkRegionTransitions(beacon); } @Override public void onBeaconLost(IBeacon beacon) { checkRegionExits(beacon); } private void checkRegionTransitions(IBeacon beacon) { for (BeaconRegion region : monitoredRegions.values()) { if (region.matches(beacon)) { String regionId = region.getIdentifier(); Set<String> beaconsInRegion = regionBeacons.get(regionId); boolean wasEmpty = beaconsInRegion.isEmpty(); beaconsInRegion.add(beacon.getIdentifier()); if (wasEmpty && region.isNotifyOnEntry()) { // Region entered regionStates.put(regionId, true); notifyRegionEntered(region, beacon); } } } } private void checkRegionExits(IBeacon beacon) { for (BeaconRegion region : monitoredRegions.values()) { if (region.matches(beacon)) { String regionId = region.getIdentifier(); Set<String> beaconsInRegion = regionBeacons.get(regionId); beaconsInRegion.remove(beacon.getIdentifier()); if (beaconsInRegion.isEmpty() && regionStates.get(regionId) && region.isNotifyOnExit()) { // Region exited regionStates.put(regionId, false); notifyRegionExited(region); } } } } private void notifyRegionEntered(BeaconRegion region, IBeacon beacon) { BeaconEvent event = new BeaconEvent( BeaconEvent.EventType.REGION_ENTERED, beacon, region); regionListeners.forEach(listener -> listener.onRegionEntered(event)); log.info("Region entered: {}", region.getIdentifier()); } private void notifyRegionExited(BeaconRegion region) { BeaconEvent event = new BeaconEvent( BeaconEvent.EventType.REGION_EXITED, null, region); regionListeners.forEach(listener -> listener.onRegionExited(event)); log.info("Region exited: {}", region.getIdentifier()); } // Other interface methods with empty implementations @Override public void onScanStarted() {} @Override public void onScanStopped() {} @Override public void onError(Exception error) {} } public interface RegionListener { void onRegionEntered(BeaconEvent event); void onRegionExited(BeaconEvent event); } Step 7: REST API Controllers
@RestController @RequestMapping("/api/ibeacon") @Slf4j public class IBeaconController { @Autowired private IBeaconScanner beaconScanner; @Autowired private RegionMonitoringService regionService; @PostMapping("/scan/start") public ResponseEntity<?> startScanning() { try { beaconScanner.startScanning(); return ResponseEntity.ok("Beacon scanning started"); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("SCAN_START_ERROR", e.getMessage())); } } @PostMapping("/scan/stop") public ResponseEntity<?> stopScanning() { try { beaconScanner.stopScanning(); return ResponseEntity.ok("Beacon scanning stopped"); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("SCAN_STOP_ERROR", e.getMessage())); } } @GetMapping("/beacons") public ResponseEntity<?> getDiscoveredBeacons() { try { List<IBeacon> beacons = beaconScanner.getDiscoveredBeacons(); return ResponseEntity.ok(beacons); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("BEACONS_ERROR", e.getMessage())); } } @PostMapping("/regions/monitor") public ResponseEntity<?> startMonitoringRegion(@Valid @RequestBody BeaconRegion region) { try { regionService.startMonitoringRegion(region); return ResponseEntity.ok("Started monitoring region: " + region.getIdentifier()); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("REGION_MONITOR_ERROR", e.getMessage())); } } @DeleteMapping("/regions/monitor/{regionId}") public ResponseEntity<?> stopMonitoringRegion(@PathVariable String regionId) { try { regionService.stopMonitoringRegion(regionId); return ResponseEntity.ok("Stopped monitoring region: " + regionId); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("REGION_STOP_ERROR", e.getMessage())); } } @GetMapping("/regions") public ResponseEntity<?> getMonitoredRegions() { try { List<BeaconRegion> regions = regionService.getMonitoredRegions(); return ResponseEntity.ok(regions); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("REGIONS_ERROR", e.getMessage())); } } @GetMapping("/status") public ResponseEntity<?> getScannerStatus() { try { Map<String, Object> status = new HashMap<>(); status.put("scanning", beaconScanner.isScanning()); status.put("beaconCount", beaconScanner.getDiscoveredBeacons().size()); status.put("regionCount", regionService.getMonitoredRegions().size()); status.put("platform", "Java iBeacon SDK"); return ResponseEntity.ok(status); } catch (Exception e) { return ResponseEntity.badRequest().body(new ErrorResponse("STATUS_ERROR", e.getMessage())); } } } @Data @AllArgsConstructor class ErrorResponse { private String errorCode; private String message; private LocalDateTime timestamp; public ErrorResponse(String errorCode, String message) { this.errorCode = errorCode; this.message = message; this.timestamp = LocalDateTime.now(); } } Step 8: Configuration File
application.yml:
ibeacon: enabled: true scan-duration: 10000 scan-interval: 5000 rssi-threshold: -85 immediate-range: 0.5 near-range: 3.0 far-range: 15.0 background-scanning: false max-beacon-cache-size: 1000 beacon-timeout: 30000 linux: bluez-interface: "org.bluez" adapter-path: "/org/bluez/hci0" windows: use-native-driver: true com-port: 1 mac: use-core-bluetooth: true logging: level: com.yourcompany.ibeacon: DEBUG
Step 9: Usage Examples
@Service public class BeaconExampleService implements RegionListener { @Autowired private IBeaconScanner beaconScanner; @Autowired private RegionMonitoringService regionService; @PostConstruct public void initialize() { // Add listeners beaconScanner.addBeaconListener(new ConsoleBeaconListener()); regionService.addRegionListener(this); // Define regions to monitor BeaconRegion storeRegion = new BeaconRegion("store-entrance", "B9407F30-F5F8-466E-AFF9-25556B57FE6D", 100, 1); regionService.startMonitoringRegion(storeRegion); // Start scanning beaconScanner.startScanning(); } @Override public void onRegionEntered(BeaconEvent event) { System.out.println("Entered region: " + event.getRegion().getIdentifier()); // Trigger actions like sending notifications, logging, etc. } @Override public void onRegionExited(BeaconEvent event) { System.out.println("Exited region: " + event.getRegion().getIdentifier()); } private static class ConsoleBeaconListener implements BeaconListener { @Override public void onBeaconDiscovered(IBeacon beacon) { System.out.printf("Discovered: %s (RSSI: %d, Distance: %.2fm)%n", beacon.getIdentifier(), beacon.getRssi(), beacon.getDistance()); } @Override public void onBeaconUpdated(IBeacon beacon) { // Handle beacon updates } @Override public void onBeaconLost(IBeacon beacon) { System.out.println("Lost beacon: " + beacon.getIdentifier()); } @Override public void onScanStarted() {} @Override public void onScanStopped() {} @Override public void onError(Exception error) {} } } Key Features Implemented
- Cross-Platform Support: Linux, Windows, macOS implementations
- Beacon Discovery: Detect and parse iBeacon advertisements
- Distance Calculation: Estimate proximity using RSSI and TX power
- Region Monitoring: Define and monitor geographic regions
- Event System: Comprehensive event notifications
- REST API: Web interface for beacon management
- Background Scanning: Continuous monitoring capabilities
- Filtering: UUID, major, minor based filtering
Best Practices
- Error Handling: Comprehensive exception handling for Bluetooth operations
- Resource Management: Proper cleanup of Bluetooth resources
- Performance: Efficient scanning with configurable intervals
- Power Management: Optimize for battery-powered devices
- Security: Validate beacon data and prevent spoofing
- Logging: Detailed logging for debugging and monitoring
- Configuration: Externalized configuration for different environments
This comprehensive iBeacon SDK provides a robust foundation for building proximity-aware applications in Java, enabling beacon detection, region monitoring, and proximity-based automation across multiple platforms.