aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/com/github/jodersky/flow/internal/InternalSerial.scala
blob: 315f3959a8f3f4a5a7d7efbdeee18c7397e51c6c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.github.jodersky.flow.internal

import java.io.IOException
import com.github.jodersky.flow._
import java.util.concurrent.atomic.AtomicBoolean

/** Wraps NativeSerial in a more object-oriented style, still quite low level. */
class InternalSerial private (val port: String, private val pointer: Long) {
  import InternalSerial._

  private val reading = new AtomicBoolean(false)
  private val writing = new AtomicBoolean(false)
  private val closed = new AtomicBoolean(false)
  
  /** Closes the underlying serial connection. Any threads blocking on read or write will return. */
  def close(): Unit = synchronized {
    if (!closed.get()) {
      closed.set(true)
      except(NativeSerial.interrupt(pointer), port)
      if (writing.get()) wait() // if reading, wait for read to finish
      if (reading.get()) wait()
      except(NativeSerial.close(pointer), port)
    }
  }

  /** Read data from underlying serial connection.
   *  @throws PortInterruptedException if port is closed from another thread */
  def read(): Array[Byte] = if (!closed.get) {
    reading.set(true)
    try {
      val buffer = new Array[Byte](100)
      val bytesRead = except(NativeSerial.read(pointer, buffer), port)
      buffer take bytesRead
    } finally {
      synchronized {
        reading.set(false)
        if (closed.get) notify(); //read was interrupted by close
      }
    }
  } else {
    throw new PortClosedException(s"port ${port} is already closed")
  }

  /** Write data to underlying serial connection.
   *  @throws PortInterruptedException if port is closed from another thread */
  def write(data: Array[Byte]): Array[Byte] = if (!closed.get) {
    writing.set(true)
    try {
      val bytesWritten = except(NativeSerial.write(pointer, data), port)
      data take bytesWritten
    } finally {
      synchronized {
        writing.set(false)
        if (closed.get) notify()
      }
    }
  } else {
    throw new PortClosedException(s"port ${port} is already closed")
  }

}

object InternalSerial {
  import NativeSerial._

  /** Transform error code to exception if necessary. */
  private def except(result: Int, port: String): Int = result match {
    case E_IO => throw new IOException(port)
    case E_ACCESS_DENIED => throw new AccessDeniedException(port)
    case E_BUSY => throw new PortInUseException(port)
    case E_INVALID_BAUD => throw new IllegalBaudRateException("use standard baud rate")
    case E_INTERRUPT => throw new PortInterruptedException(port)
    case E_NO_PORT => throw new NoSuchPortException(port)
    case error if error < 0 => throw new IOException(s"unknown error code: ${error}")
    case success => success
  }

  /** Open a new connection to a serial port. */
  def open(port: String, baud: Int): InternalSerial = synchronized {
    val pointer = new Array[Long](1)
    except(NativeSerial.open(port, baud, pointer), port)
    new InternalSerial(port, pointer(0))
  }

  /** Set debugging for all serial connections. Debugging results in printing extra messages (from the native library) in case of errors. */
  def debug(value: Boolean) = NativeSerial.debug(value)

}