summaryrefslogtreecommitdiff
path: root/cfa635/decode.go
diff options
context:
space:
mode:
Diffstat (limited to 'cfa635/decode.go')
-rw-r--r--cfa635/decode.go128
1 files changed, 128 insertions, 0 deletions
diff --git a/cfa635/decode.go b/cfa635/decode.go
new file mode 100644
index 0000000..dc77e03
--- /dev/null
+++ b/cfa635/decode.go
@@ -0,0 +1,128 @@
+// Copyright 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+package cfa635
+
+import (
+ "io"
+ "log"
+ "time"
+)
+
+const (
+ maxPacketBytes = 26
+
+ msgPacketFailed = "failed to read packet from CFA635:"
+ msgTimedOut = "timed out"
+)
+
+var (
+ timeout = 250 * time.Millisecond // maximum response latency
+)
+
+// buffer copies bytes from an io.Reader into a channel, logging any errors as
+// it goes. It closes the channel when no more bytes are left or when it
+// receives on the done channel.
+func buffer(r io.ByteReader, w chan<- byte) {
+ defer close(w)
+ for {
+ b, err := r.ReadByte()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ log.Print("failed to read byte from CFA635: ", err)
+ continue
+ }
+ w <- b
+ }
+}
+
+// decode reassembles bytes into packets, logging any errors as it goes.
+func decode(bytes <-chan byte, packets chan<- []byte) {
+ defer close(packets)
+Outer:
+ for {
+ p := make([]byte, 0, maxPacketBytes)
+
+ // Read packet type.
+ typ, ok := <-bytes
+ if !ok {
+ break
+ }
+ p = append(p, typ)
+
+ // The rest of the packet should come in fairly quickly.
+ timedout := time.After(timeout)
+
+ // Read packet length.
+ var length byte
+ select {
+ case <-timedout:
+ log.Print(msgPacketFailed, ' ', msgTimedOut)
+ continue
+
+ case length, ok = <-bytes:
+ if !ok {
+ break Outer
+ }
+ if length > 22 {
+ log.Print(msgPacketFailed, " got too-long data_length ", length)
+ continue
+ }
+ p = append(p, length)
+ }
+
+ // Read the data and CRC.
+ for i := 0; i < int(length)+2; i++ {
+ select {
+ case <-timedout:
+ log.Print(msgPacketFailed, ' ', msgTimedOut)
+ continue
+ case b, ok := <-bytes:
+ if !ok {
+ break Outer
+ }
+ p = append(p, b)
+ }
+ }
+
+ if p, ok = popCRC(p); !ok {
+ log.Printf("%s CRC failure\n", msgPacketFailed)
+ continue
+ }
+
+ // Save the packet.
+ packets <- p
+ }
+}
+
+// route splits a channel of packets into channels of reports and responses.
+func route(packets <-chan []byte, reports chan<- any, responses chan<- []byte) {
+ defer close(reports)
+ defer close(responses)
+ for p := range packets {
+ switch p[0] & 0b1100_0000 >> 6 {
+ case 0b10:
+ r, err := decodeReport(p)
+ if err != nil {
+ log.Print(err.Error())
+ continue
+ }
+ reports <- r
+ default:
+ responses <- p
+ }
+ }
+}