package dispenser import ( // "encoding/hex" "fmt" // "log" "time" log "github.com/sirupsen/logrus" "github.com/tarm/serial" ) // Control characters. const ( STX = 0x02 // Start of Text ETX = 0x03 // End of Text ACK = 0x06 // Positive response NAK = 0x15 // Negative response ENQ = 0x05 // Enquiry from host space = 0x00 // Space character baudRate = 9600 // Baud rate for serial communication delay = 500 * time.Millisecond // Delay for processing commands ) // type ( // configRec struct { // SerialPort string `yaml:"port"` // Address string `yaml:"addr"` // } // ) var ( SerialPort string Address []byte commandFC7 = []byte{ETX, 0x46, 0x43, 0x37} // "FC7" command dispense card at read card position commandFC0 = []byte{ETX, 0x46, 0x43, 0x30} // "FC0" command dispense card out of card mouth command statusPos0 = map[byte]string{ 0x38: "Keep", 0x34: "Command cannot execute", 0x32: "Preparing card fails", 0x31: "Preparing card", 0x30: "Normal", // Default if none of the above } statusPos1 = map[byte]string{ 0x38: "Dispensing card", 0x34: "Capturing card", 0x32: "Dispense card error", 0x31: "Capture card error", 0x30: "Normal", } statusPos2 = map[byte]string{ 0x38: "No captured card", 0x34: "Card overlapped", 0x32: "Card jammed", 0x31: "Card pre-empty", 0x30: "Normal", } statusPos3 = map[byte]string{ 0x38: "Card empty", 0x34: "Card ready position", 0x33: "Card at encoder position", 0x32: "Card at hold card position", 0x31: "Card out of card mouth position", 0x30: "Normal", } ) func checkStatus(statusResp []byte) (string, error) { if len(statusResp) > 3 { statusBytes := statusResp[7:11] // Extract the relevant bytes from the response // For each position, get the ASCII character, hex value, and mapped meaning. posStatus := []struct { pos int value byte mapper map[byte]string }{ {pos: 1, value: statusBytes[0], mapper: statusPos0}, {pos: 2, value: statusBytes[1], mapper: statusPos1}, {pos: 3, value: statusBytes[2], mapper: statusPos2}, {pos: 4, value: statusBytes[3], mapper: statusPos3}, } result := "" for _, p := range posStatus { statusMsg, exists := p.mapper[p.value] if !exists { statusMsg = "Unknown status" } if p.value != 0x30 { result += fmt.Sprintf("Status: %s; ", statusMsg) } if p.pos == 4 && p.value == 0x38 { return result, fmt.Errorf("Card well empty") } } return result, nil } else { if len(statusResp) == 3 && statusResp[0] == ACK && statusResp[1] == Address[0] && statusResp[2] == Address[1] { return "active;", nil } else if len(statusResp) > 0 && statusResp[0] == NAK { return "", fmt.Errorf("negative response from dispenser") } else { return "", fmt.Errorf("unexpected response status: % X", statusResp) } } } // calculateBCC computes the Block Check Character (BCC) as the XOR of all bytes from STX to ETX. func calculateBCC(data []byte) byte { var bcc byte for _, b := range data { bcc ^= b } return bcc } func createPacket(address []byte, command []byte) []byte { packet := []byte{STX} packet = append(packet, address...) // Address bytes packet = append(packet, space) // Space character packet = append(packet, command...) packet = append(packet, ETX) bcc := calculateBCC(packet) packet = append(packet, bcc) return packet } func buildCheckRF(address []byte) []byte { return createPacket(address, []byte{STX, 0x52, 0x46}) } func buildCheckAP(address []byte) []byte { return createPacket(address, []byte{STX, 0x41, 0x50}) } func sendAndReceive(port *serial.Port, packet []byte, delay time.Duration) ([]byte, error) { n, err := port.Write(packet) if err != nil { return nil, fmt.Errorf("error writing to port: %w", err) } // log.Printf("TX %d bytes: % X", n, packet[:n]) time.Sleep(delay) // Wait for the dispenser to process the command buf := make([]byte, 128) n, err = port.Read(buf) if err != nil { return nil, fmt.Errorf("error reading from port: %w", err) } resp := buf[:n] // log.Printf("RX %d bytes: % X", n, buf[:n]) return resp, nil } func InitializeDispenser() (*serial.Port, error) { const funcName = "initializeDispenser" serialConfig := &serial.Config{ Name: SerialPort, Baud: baudRate, ReadTimeout: time.Second * 2, } port, err := serial.OpenPort(serialConfig) if err != nil { return nil, fmt.Errorf("error opening dispenser COM port: %w", err) } return port, nil } func DispenserSequence(port *serial.Port) (string, error) { const funcName = "dispenserSequence" var result string // Check dispenser status status, err := CheckDispenserStatus(port) if err != nil { return status, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err) } result += status // Send card to encoder position status, err = CardToEncoderPosition(port) if err != nil { return status, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err) } result += "; " + status return result, nil } // if dispenser is not responding, I should repeat the command func CheckDispenserStatus(port *serial.Port) (string, error) { const funcName = "checkDispenserStatus" var result string checkCmd := buildCheckAP(Address) enq := append([]byte{ENQ}, Address...) // Send check command (AP) statusResp, err := sendAndReceive(port, checkCmd, delay) if err != nil { return "", fmt.Errorf("error sending check command: %v", err) } if len(statusResp) == 0 { return "", fmt.Errorf("no response from dispenser") } status, err := checkStatus(statusResp) if err != nil { return status, err } result += "; " + status // Send ENQ+ADDR to prompt device to execute the command. statusResp, err = sendAndReceive(port, enq, delay) if err != nil { log.Errorf("error sending ENQ: %v", err) } if len(statusResp) == 0 { return "", fmt.Errorf("no response from dispenser") } status, err = checkStatus(statusResp) if err != nil { return status, err } result += status return result, nil } func CardToEncoderPosition(port *serial.Port) (string, error) { const funcName = "cartToEncoderPosition" enq := append([]byte{ENQ}, Address...) //Send Dispense card to encoder position (FC7) --- dispenseCmd := createPacket(Address, commandFC7) log.Println("Send card to encoder position") statusResp, err := sendAndReceive(port, dispenseCmd, delay) if err != nil { return "", fmt.Errorf("error sending card to encoder position: %v", err) } _, err = checkStatus(statusResp) if err != nil { return "", err } //Send ENQ to prompt device --- _, err = port.Write(enq) if err != nil { return "", fmt.Errorf("error sending ENQ to prompt device: %v", err) } time.Sleep(delay) //Check card position status status, err := CheckDispenserStatus(port) if err != nil { return "", err } return status, nil } func CardOutOfMouth(port *serial.Port) (string, error) { const funcName = "CardOutOfMouth" enq := append([]byte{ENQ}, Address...) // Send card out of card mouth (FC0) --- dispenseCmd := createPacket(Address, commandFC0) log.Println("Send card to out mouth position") statusResp, err := sendAndReceive(port, dispenseCmd, delay) if err != nil { return "", fmt.Errorf("error sending out of mouth command: %v", err) } _, err = checkStatus(statusResp) if err != nil { return "", err } //Send ENQ to prompt device --- _, err = port.Write(enq) if err != nil { return "", fmt.Errorf("error sending ENQ to prompt device: %v", err) } time.Sleep(delay) //Check card position status status, err := CheckDispenserStatus(port) if err != nil { return "", err } return status, nil }