Skip to content

Commit

Permalink
feat: add PCIC commands
Browse files Browse the repository at this point in the history
  • Loading branch information
graugans committed Jul 11, 2024
1 parent 6b8fadf commit 713bc74
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 9 deletions.
40 changes: 33 additions & 7 deletions cmd/ovp8xx/cmd/pcic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"fmt"
"sync"

"github.com/graugans/go-ovp8xx/v2/pkg/pcic"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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")
}
95 changes: 93 additions & 2 deletions pkg/pcic/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"net"
"strconv"
"strings"
)

type (
Expand Down Expand Up @@ -47,6 +50,7 @@ type MessageHandler interface {
Result(Frame)
Error(ErrorMessage)
Notification(NotificationMessage)
CommandResponse(Response)
}

type NotificationMessage struct {
Expand All @@ -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{}
Expand Down Expand Up @@ -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),
)
}
Expand All @@ -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
Expand All @@ -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
// <ticket><length>CRLF<ticket><content>CRLF
// <ticket> is a 4-digit number in the range 100-9999
// <length> 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
// <content> is the actual data that is being sent
length := len(ticketBytes) /*<ticket>*/ + 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{}
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/pcic/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down

0 comments on commit 713bc74

Please sign in to comment.