| 1 | package io.github.krolyxon.budctl |
| 2 | |
| 3 | import java.io.Closeable |
| 4 | import java.io.IOException |
| 5 | import java.io.InputStream |
| 6 | import java.io.OutputStream |
| 7 | import java.util.concurrent.LinkedBlockingQueue |
| 8 | import java.util.concurrent.TimeUnit |
| 9 | import kotlin.concurrent.thread |
| 10 | |
| 11 | /** |
| 12 | * One RFCOMM link to the buds. A Bluetooth socket's input stream has no read timeout, so a thread |
| 13 | * of its own reads it into a queue and [recv] waits on the queue. Closing the socket ends that |
| 14 | * thread. |
| 15 | */ |
| 16 | class Link(private val input: InputStream, private val output: OutputStream, private val socket: Closeable) { |
| 17 | private val received = LinkedBlockingQueue<ByteArray>() |
| 18 | |
| 19 | init { |
| 20 | thread(name = "budctl-link", isDaemon = true) { |
| 21 | val buffer = ByteArray(1024) |
| 22 | try { |
| 23 | while (true) { |
| 24 | val n = input.read(buffer) |
| 25 | if (n < 0) break |
| 26 | if (n > 0) received.put(buffer.copyOf(n)) |
| 27 | } |
| 28 | } catch (_: IOException) { |
| 29 | // Closed, by us or by the buds. |
| 30 | } |
| 31 | received.put(CLOSED) |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | fun send(bytes: ByteArray) { |
| 36 | output.write(bytes) |
| 37 | output.flush() |
| 38 | } |
| 39 | |
| 40 | /** The next bytes; null if none came within [timeoutMs]; an empty array once the link is closed. */ |
| 41 | fun recv(timeoutMs: Long): ByteArray? { |
| 42 | val bytes = received.poll(timeoutMs, TimeUnit.MILLISECONDS) ?: return null |
| 43 | // Closed stays closed, for the next call too. |
| 44 | if (bytes.isEmpty()) received.put(CLOSED) |
| 45 | return bytes |
| 46 | } |
| 47 | |
| 48 | fun close() { |
| 49 | try { |
| 50 | socket.close() |
| 51 | } catch (_: IOException) { |
| 52 | // Already closed. |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | private companion object { |
| 57 | val CLOSED = ByteArray(0) |
| 58 | } |
| 59 | } |