Skip to content

Instantly share code, notes, and snippets.

@searler
Created March 21, 2015 18:26
Show Gist options
  • Save searler/9bb4ac189ee772cdf645 to your computer and use it in GitHub Desktop.
Save searler/9bb4ac189ee772cdf645 to your computer and use it in GitHub Desktop.
A simple remote control protocol codec for a serial port
import scodec.Codec
import scodec.codecs._
import scodec.bits.ByteVector
import scodec.bits.BitVector
import scodec.Attempt
import scodec.DecodeResult
object SerialProtocol {
sealed trait Parity
case object NoParity extends Parity
case object EvenParity extends Parity
case object OddParity extends Parity
case class Config(port: Int, speed: Int, parity: Parity, dataBits: Int, stopBits: Int)
sealed trait Command
sealed trait Response
case class ConfigurationCommand(configs: Vector[Config]) extends Command
case class ReadCommand(maxSize: Int, timeout: Int) extends Command
case class WriteBlockingCommand(payload: ByteVector) extends Command
case class WriteCommand(payload: ByteVector) extends Command
case object FlushCommand extends Command
case class ReadData(payload: ByteVector) extends Response
case object Written extends Response
case object Flushed extends Response
case object WriteInFlight extends Response
case object WriteTimedOut extends Response
case class ReadFailed(error: String) extends Response
case class FlushFailed(error: String) extends Response
case class WriteFailed(error: String) extends Response
case class Configured(major: Int, minor: Int) extends Response
case class ConfigurationRejected(major: Int, minor: Int, error: String) extends Response
case class ConfigurationFailed(major: Int, minor: Int, error: String) extends Response
private val uint8 = uint(8)
private val int32 = int(32)
private val size = uint8
private def empty[T](marker: T) = constant(BitVector.fromInt(0, 8)) ~> provide(marker)
private def body[T](contents: Codec[T]) = variableSizeBytes(size, contents)
private val string = body(ascii)
private val binary = body(bytes)
private def wrap[T](contents: Codec[T])(implicit sequence: Int) = constant(BitVector.fromInt(sequence, 8)) ~> (contents.as[T])
private def strip[T](contents: Codec[T]): Codec[(Int, T)] = (uint8 ~ contents)
private val parity: Codec[Parity] = mappedEnum(uint8,
NoParity -> 0,
OddParity -> 1,
EvenParity -> 2)
private val config = (uint8 :: int32 :: parity :: uint8 :: uint8).as[Config]
private val ver = body(uint8 :: uint8)
private val verError = body(uint8 :: uint8 :: ascii)
def cmd(implicit sequence: Int) = discriminated[Command].by(uint8)
.typecase(1, wrap(binary.as[WriteBlockingCommand]))
.typecase(2, wrap(empty(FlushCommand)))
.typecase(4, wrap(binary.as[WriteCommand]))
.typecase(5, wrap(body(vector(config)).as[ConfigurationCommand]))
.typecase(6, wrap(body(uint8 :: uint8).as[ReadCommand]))
val response = discriminated[(Int, Response)].by(uint8)
.typecase(1, strip(binary.as[ReadData]))
.typecase(2, strip(empty(Flushed)))
.typecase(4, strip(string.as[ReadFailed]))
.typecase(5, strip(empty(WriteInFlight)))
.typecase(6, strip(empty(Written)))
.typecase(7, strip(string.as[WriteFailed]))
.typecase(8, strip(string.as[FlushFailed]))
.typecase(9, strip(verError.as[ConfigurationFailed]))
.typecase(10, strip(ver.as[Configured]))
.typecase(11, strip(empty(WriteTimedOut)))
.typecase(13, strip(verError.as[ConfigurationRejected]))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment