Created
March 21, 2015 18:26
-
-
Save searler/9bb4ac189ee772cdf645 to your computer and use it in GitHub Desktop.
A simple remote control protocol codec for a serial port
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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