291 lines
7.6 KiB
Go
291 lines
7.6 KiB
Go
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)
|
|
}
|
|
|
|
//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)
|
|
}
|
|
|
|
//Check card position status
|
|
status, err := CheckDispenserStatus(port)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return status, nil
|
|
}
|