diff --git a/cmd/ovp8xx/cmd/pcic.go b/cmd/ovp8xx/cmd/pcic.go index 5c0b6d7..6c756ad 100644 --- a/cmd/ovp8xx/cmd/pcic.go +++ b/cmd/ovp8xx/cmd/pcic.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "sync" "github.com/graugans/go-ovp8xx/v2/pkg/pcic" "github.com/spf13/cobra" @@ -40,6 +41,10 @@ func (r *PCICReceiver) Notification(msg pcic.NotificationMessage) { fmt.Printf("Notification: %v\n", msg) } +func (r *PCICReceiver) CommandResponse(rsp pcic.Response) { + fmt.Printf("Command Response Ticket: %v Data: %s\n", rsp.Ticket, string(rsp.Data)) +} + // pcicCommand is a function that handles the execution of the "pcic" command. // It initializes a PCICReceiver, creates a helper, and establishes a connection to the PCIC client. // It then continuously processes incoming data using the PCIC client and the testHandler. @@ -48,6 +53,14 @@ func (r *PCICReceiver) Notification(msg pcic.NotificationMessage) { func pcicCommand(cmd *cobra.Command, args []string) error { var testHandler *PCICReceiver = &PCICReceiver{} var err error + + // Retrieve the slice of commands + cmds, err := cmd.Flags().GetStringSlice("cmd") + if err != nil { + // Handle the error + return err + } + helper, err := NewHelper(cmd) if err != nil { return err @@ -56,16 +69,28 @@ func pcicCommand(cmd *cobra.Command, args []string) error { pcic, err := pcic.NewPCICClient( pcic.WithTCPClient(helper.hostname(), helper.remotePort()), ) - if err != nil { - return err - } - for { - err = pcic.ProcessIncomming(testHandler) + var wg sync.WaitGroup + wg.Add(1) // We're going to wait for one goroutine + + go func() { + defer wg.Done() // This will be called when the goroutine finishes + for { + err = pcic.ProcessIncomming(testHandler) + if err != nil { + // An error occured, we break the loop + break + } + } + }() + // execute the commands + for _, cmd := range cmds { + _, err = pcic.Send([]byte(cmd)) if err != nil { - // An error occured, we break the loop - break + return fmt.Errorf("failed to send command: %w", err) } } + // Wait for the goroutine to finish before executing the commands + wg.Wait() return err } @@ -79,4 +104,5 @@ var pcicCmd = &cobra.Command{ func init() { rootCmd.AddCommand(pcicCmd) pcicCmd.Flags().Uint16("port", 50010, "The port to connect to") + pcicCmd.Flags().StringSlice("cmd", []string{}, "Commands to send to the device") } diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 7ef1038..b96e1cd 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -6,7 +6,10 @@ import ( "errors" "fmt" "io" + "math/rand" "net" + "strconv" + "strings" ) type ( @@ -47,6 +50,7 @@ type MessageHandler interface { Result(Frame) Error(ErrorMessage) Notification(NotificationMessage) + CommandResponse(Response) } type NotificationMessage struct { @@ -59,6 +63,11 @@ type ErrorMessage struct { Message string } +type Response struct { + Ticket string + Data []byte +} + func NewPCICClient(options ...PCICClientOption) (*PCICClient, error) { var err error pcic := &PCICClient{} @@ -105,10 +114,12 @@ func (p *PCICClient) ProcessIncomming(handler MessageHandler) error { return err } firstTicket := header[:ticketFieldLength] + ticketStr := string(firstTicket) + secondTicket := header[secondTicketOffset:dataOffset] if !bytes.Equal(firstTicket, secondTicket) { return fmt.Errorf("mismatch in the tickets %s != %s ", - string(firstTicket), + string(ticketStr), string(secondTicket), ) } @@ -132,7 +143,21 @@ func (p *PCICClient) ProcessIncomming(handler MessageHandler) error { if !bytes.Equal(trailer, []byte{'\r', '\n'}) { return errors.New("invalid trailer detected") } - if bytes.Equal(resultTicket, firstTicket) { + var ticketNum = 0 + if ticketStr != "0000" { + ticketNum, err = strconv.Atoi(strings.TrimLeft(ticketStr, "0")) + if err != nil { + return fmt.Errorf("unable to convert the ticket number %s to an integer", ticketStr) + } + } + if ticketNum > 100 { + r, err := responseParser(ticketStr, data) + if err != nil { + return fmt.Errorf("unable to parse the response: %w", err) + } + handler.CommandResponse(r) + return nil + } else if bytes.Equal(resultTicket, firstTicket) { frame, err := asyncResultParser(data) handler.Result(frame) return err @@ -144,6 +169,66 @@ func (p *PCICClient) ProcessIncomming(handler MessageHandler) error { return fmt.Errorf("unknown ticket received: %s", string(firstTicket)) } +func (p *PCICClient) Send(data []byte) (uint16, error) { + var ticket uint16 + if p.writer == nil { + return ticket, errors.New("no bufio.Writer provided, please instantiate the object") + } + // Let's generate a random ticket number + ticket = uint16(rand.Intn(8999) + 1000) + if ticket < 100 || ticket > 9999 { + return ticket, fmt.Errorf( + "invalid ticket number: %d, needs to be in the range 100-9999", ticket, + ) + } + + // Create a new buffer to aggregate the message + var buf bytes.Buffer + var delimter = []byte("\r\n") + // Convert ticket to a 4-digit string and then to bytes + ticketBytes := []byte(fmt.Sprintf("%04d", ticket)) + buf.Write(ticketBytes) + // A Command message is composed like this + // CRLFCRLF + // is a 4-digit number in the range 100-9999 + // is a character string starting with an 'L' followed by 9 digits + // interpreted as a decimal value. The number is the length of data that follows + // is the actual data that is being sent + length := len(ticketBytes) /**/ + len(data) + 2 /*CRLF*/ + lengthStr := fmt.Sprintf("L%09d", length) + lengthBytes := []byte(lengthStr) + buf.Write(lengthBytes) + buf.Write(delimter) + buf.Write(ticketBytes) + buf.Write(data) + buf.Write(delimter) + + // Write the buffer to the underlying writer + _, err := p.writer.Write(buf.Bytes()) + if err != nil { + return ticket, fmt.Errorf("unable to write to the buffer: %w", err) + } + // This is necessary to flush the buffer to the underlying writer + // Otherwise, the data will not be sent over the network + err = p.writer.Flush() + if err != nil { + return ticket, fmt.Errorf("unable to flush to the buffer: %w", err) + } + + return ticket, nil +} + +func responseParser(ticket string, data []byte) (Response, error) { + var err error + res := Response{} + if len(data) <= delimiterFieldLength { + return res, fmt.Errorf("the data is too short to be a valid frame: %d", len(data)) + } + res.Ticket = ticket + res.Data = data[:len(data)-delimiterFieldLength] + return res, err +} + func errorParser(data []byte) (ErrorMessage, error) { var err error errorStatus := ErrorMessage{} @@ -162,7 +247,13 @@ func errorParser(data []byte) (ErrorMessage, error) { func asyncResultParser(data []byte) (Frame, error) { frame := Frame{} var err error + if len(data) <= delimiterFieldLength { + return frame, fmt.Errorf("the data is too short to be a valid frame: %d", len(data)) + } contentDecorated := data[:len(data)-delimiterFieldLength] + if len(contentDecorated)-len(endMarker) < 0 { + return frame, fmt.Errorf("the data is too short to be a valid frame: %d: content: %s", len(data), string(data)) + } content := contentDecorated[len(endMarker) : len(contentDecorated)-len(endMarker)] if len(content) == 0 { // no content is available diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index c3d3ef6..69dbd7e 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -23,6 +23,7 @@ type PCICAsyncReceiver struct { frame pcic.Frame notificationMsg pcic.NotificationMessage errorMsg pcic.ErrorMessage + response pcic.Response } func (r *PCICAsyncReceiver) Result(frame pcic.Frame) { @@ -37,6 +38,10 @@ func (r *PCICAsyncReceiver) Notification(msg pcic.NotificationMessage) { r.notificationMsg = msg } +func (r *PCICAsyncReceiver) CommandResponse(res pcic.Response) { + r.response = res +} + var testHandler *PCICAsyncReceiver = &PCICAsyncReceiver{} func TestMinimalReceive(t *testing.T) {