0

I'm setting up a Serial communication with Golang. I'm using the a https://github.com/jacobsa/go-serial library for this. The Golang code is a master setup. So, it has to write to the serialport and listen to the reply. This works great when everything is working like expected. So writing to the serialport is successful, and the reply is successfully retrieved.

However, when the reply went wrong 1 time, or the slave doesn't respond, the Golang io.Reader can't restore itself. Then every single message is wrong, because it misses some bytes. I've used a scope to try and see what's going wrong, but the request from the master and reply from the slave are correct.

I'm using a RS485 communication with an Arduino-based PCB. Nothing wrong with the communication, only with the Golang code.

The communication structure is as follows:

STX | DESTINATION | SOURCE | Number of Bytes | Data | CRC16

The STX is always 0x02, so that indicates the start of a message. The destination and source is 32 bits, number of bytes is 16 bits, data depends on the number of bytes and the CRC is 16 bits. Like I said, this data is transmitted successfully.

I've made some software in Golang that opens the port, and uses a "for" loop to write data and wait for new data. This is added below. I've used the "Peek" function below, just for an example. But I've also tried ReadByte and UnreadByte, and even ReadByte and that the ReadMessage function doesn't wait for the 0x02. And I've tried it without the bufio reader, but without any result.

I've added the code to reproduce it and probably notice my mistakes:

package main

import (
    "bufio"
    "encoding/binary"
    "errors"
    "fmt"
    "log"
    "time"

    "github.com/jacobsa/go-serial/serial"
)

func main() {
    options := serial.OpenOptions{
        PortName:        "/dev/ttyAMA0",
        BaudRate:        57600,
        DataBits:        8,
        StopBits:        1,
        ParityMode:      0,
        MinimumReadSize: 1,
    }

    port, err := serial.Open(options)
    if err != nil {
        log.Fatalf("serial.Open: %v", err)
    }

    defer port.Close()

    writeBytes := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0x92}
    b := bufio.NewReader(port)

    for {

        _, err = port.Write(writeBytes)
        if err != nil {
            log.Fatalf("port.Write: %v", err)
        }

        recievedSTX := make(chan bool, 1)
        go func(buf *bufio.Reader) {
            gotSTX := false

            for {
                n, err := b.Peek(1)
                if err != nil {
                    // Got error while peeking
                    break
                }

                // Check if peek output is STX
                // This goes wrong very often when communication didn't go as expected once
                if n[0] == 0x02 {
                    gotSTX = true
                    break
                }

            }

            recievedSTX <- gotSTX

        }(b)

        select {
        case stx := <-recievedSTX:
            if stx == true {

                // Function to read whole message
                message, err := ReadMessage(b)
                if err != nil {
                    // Error while reading whole message
                    // When 0x02 is read before, and communication didn't go as expected once, this is always going wrong
                    fmt.Println(err)
                } else {
                    // Message Successful!
                    fmt.Println(message)
                }
            } else {
                // STX not found, so just sleep like the timeout did
                time.Sleep(time.Duration(500) * time.Millisecond)
            }
        case <-time.After(time.Duration(500) * time.Millisecond):
            // Timeout while reading, so stopping. 500Ms for testing purposes
            fmt.Println("TIMEOUT")
        }

    }

}

func ReadMessage(b *bufio.Reader) ([]byte, error) {
    var message []byte
    var getNumberOfBytes int
    var countOfBytes int

    messageComplete := make(chan error, 1)
    go func() {

        STX := make([]byte, 1)
        getNumberOfBytes = len(STX)
        countOfBytes = 0
        for countOfBytes < getNumberOfBytes {
            n, err := b.ReadByte()
            if err != nil {
                messageComplete <- err
            }

            if n == 0x02 {
                STX[countOfBytes] = n
                message = append(message, n)
                break
            } else {
                fmt.Println("STX BYTE", n)
                messageComplete <- errors.New("First byte is not a STX byte (0x02)")
            }
        }

        Destination := make([]byte, 4)
        getNumberOfBytes = len(Destination)
        countOfBytes = 0
        for countOfBytes < getNumberOfBytes {
            n, err := b.ReadByte()
            if err != nil {
                messageComplete <- err
            }

            Destination[countOfBytes] = n
            message = append(message, n)
            countOfBytes++
        }

        Source := make([]byte, 4)
        getNumberOfBytes = len(Source)
        countOfBytes = 0
        for countOfBytes < getNumberOfBytes {
            n, err := b.ReadByte()
            if err != nil {
                messageComplete <- err
            }

            Source[countOfBytes] = n
            message = append(message, n)
            countOfBytes++
        }

        Length := make([]byte, 2)
        getNumberOfBytes = len(Length)
        countOfBytes = 0
        for countOfBytes < getNumberOfBytes {
            n, err := b.ReadByte()
            if err != nil {
                messageComplete <- err
            }

            Length[countOfBytes] = n
            message = append(message, n)
            countOfBytes++
        }
        NumberOfBytes := binary.LittleEndian.Uint16(Length)

        getNumberOfBytes = int(NumberOfBytes)
        countOfBytes = 0
        for countOfBytes < getNumberOfBytes {
            n, err := b.ReadByte()
            if err != nil {
                messageComplete <- err
            }

            message = append(message, n)
            countOfBytes++
        }

        CRC := make([]byte, 2)
        getNumberOfBytes = 2
        countOfBytes = 0
        for countOfBytes < getNumberOfBytes {
            n, err := b.ReadByte()
            if err != nil {
                messageComplete <- err
            }

            CRC[countOfBytes] = n
            message = append(message, n)
            countOfBytes++
        }

        messageComplete <- nil

    }()

    select {
    case mComplete := <-messageComplete:
        if mComplete != nil {
            return message, mComplete
        }

        return message, nil
    case <-time.After(time.Duration(200) * time.Millisecond):
        return message, errors.New("TIMEOUT: Could not read any more bytes")
    }
}

The expected result is that every time a message is sent to the Master that begins with 0x02, it's going to read the whole message. Whenever that message is complete, it has to print the whole message. Or print an error. When no byte is received after the write, it has to print "TIMEOUT" after 200ms.

The actual result is that at the start, when the Slave is plugged in to the Master, I get all the nice replies from the slave. However, when I unplug the Slave for even the slightest bit of time and plug it back in, I only get timeouts, because the Golang code never sees the 0x02 again. So all messages are completely ignored.

The same happens when I add another Slave ID, that isn't connected, so it tries to talk to 2 slaves after each other. (First slave 1, then slave 2, then slave 1 again etc.), but the same happens as before.

I've tried everything I know, even used another Master PC and another connection shield, but the hardware checks out. The scope tells me everything is fine. I'm really out of ideas, any help would be really appreciated.

Bart Versluijs
  • 186
  • 1
  • 8
  • *"However, when I unplug the Slave..."* -- Just because a message always starts with STX, that does not mean that encountering a 0x02 byte means that you have found the start of a message. The only way to determine if you have a real or false STX is to "read" the "message" and try to verify the CRC. If the CRC is not valid, then do not toss those bytes!!! You have to rescan those bytes for a 0x02 starting with the byte following that false STX byte. See https://stackoverflow.com/questions/16177947/identification-of-packets-in-a-byte-stream/16180135#16180135 for related concept. – sawdust Jan 04 '19 at 02:06
  • @sawdust thanks for the reply! I’ve read that question, but the difference is that he has a fixed package size. Mine is depending on the number of bytes. You’re right that 0x02 doesn’t always mean that a message is starting, but 9/10 times it does. And if the message loses data, it’s stuck in a timeout. The first read for the byte is just so that the timeout isn’t triggered, because the master sees a reply coming. At the end of ReadMessage, I have a CRC16 check over the message, but I see that I haven’t posted it. I’ll change that. But that doesn’t change the fact that I’m missing some bytes. – Bart Versluijs Jan 04 '19 at 06:48
  • *"... the difference is that he has a fixed package size. Mine is depending on the number of bytes"* -- You're focusing on an irrelevant detail (fixed versus variable length messages) instead of the salient concept. You're *"missing some bytes"* because your program is not robust; it can only handle a pre-synchronized input. *"but 9/10 times it does"* -- You really don't know that, and it looks like that alleged 1 out of 10 can occur reliably to reveal your code's flaws. Your code does not validate the CRC, it does not rescan the bytes, and it unnecessarily performs sleeps. – sawdust Jan 07 '19 at 00:17

0 Answers0