aboutsummaryrefslogtreecommitdiff
path: root/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala
blob: 306d3dcc42e1dbfb31955fe37fc2320766f759f5 (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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package vfd.uav

import java.util.concurrent.TimeUnit.MILLISECONDS
import scala.concurrent.duration.FiniteDuration
import org.mavlink.Parser
import org.mavlink.messages.Message
import com.github.jodersky.flow.Parity
import com.github.jodersky.flow.Serial
import com.github.jodersky.flow.SerialSettings
import akka.actor.Actor
import akka.actor.ActorRef
import akka.actor.Props
import akka.actor.Terminated
import akka.actor.actorRef2Scala
import akka.io.IO
import akka.util.ByteString
import akka.actor.ActorLogging

class SerialConnection(id: Byte, heartbeat: Option[FiniteDuration], port: String, settings: SerialSettings) extends Actor with ActorLogging with Connection {
  import context._

  val Heartbeat = ByteString(
    Array(-2, 9, -121, 20, -56, 0, 0, 0, 0, 0, 2, 0, 0, 3, 3, -112, 76).map(_.toByte))

  override def preStart() = {
    heartbeat foreach { interval =>
      context.system.scheduler.schedule(interval, interval, self, Connection.Send(Heartbeat))
    }
  }

  def _closed: Receive = {

    case Connection.Register =>
      register(sender)
      IO(Serial) ! Serial.Open(port, settings)
      context become opening

    case Connection.Send(_) =>
      IO(Serial) ! Serial.Open(port, settings)
      context become opening

  }

  def _opening: Receive = {

    case Serial.CommandFailed(cmd: Serial.Open, reason) =>
      sendAll(Connection.Closed(reason.toString))
      context become closed

    case Serial.Opened(_) =>
      context watch (sender)
      context become opened(sender)

    case Connection.Send(_) => () // ignore
      /*
    	 * During opening, any outgoing messages are discarded.
       * By using some kind of message stashing, maybe messages could be treated
       * once the port has been opened. However, in such a case failure also needs
       * to be considered complicating the protocol. Since opening is typically
       * quite fast and since mavlink uses heartbeats and acknowledgements (in certain
       * circumstances) anyway, keeping messages is not really required.  
       */

  }
  
  val parser = new Parser(
      pckt => try {
         log.info(Message.unpack(pckt).toString()) 
      } catch {
        case err: MatchError =>
          log.info("unknown message: " + pckt.payload.map(_.formatted("%02x").mkString(" ")))
      }
  )

  def _opened(operator: ActorRef): Receive = {

    case Terminated(`operator`) =>
      sendAll(Connection.Closed("Serial connection crashed."))
      context become closed

    case Serial.Closed =>
      sendAll(Connection.Closed("Serial connection was closed."))
      context become closed

    case Serial.Received(bstr) =>
      for (b <- bstr) {
        parser.push(b)
      }
      sendAll(Connection.Received(bstr))

    case Connection.Send(bstr) =>
      operator ! Serial.Write(bstr)

  }

  def receive = closed
  def closed = _closed orElse registration
  def opening = _opening orElse registration
  def opened(op: ActorRef) = _opened(op) orElse registration

}

object SerialConnection {
  def apply(id: Byte, heartbeat: Int, port: String, baud: Int, tsb: Boolean, parity: Int) = {
    val settings = SerialSettings(
      baud,
      8,
      tsb,
      parity match {
        case 0 => Parity.None
        case 1 => Parity.Odd
        case 2 => Parity.Even
      })
    val hb = if (heartbeat == 0) None else Some(FiniteDuration(heartbeat, MILLISECONDS))

    Props(classOf[SerialConnection], id, hb, port, settings)
  }
}