release version 1.1.0
This commit is contained in:
parent
9f0a9c939f
commit
4a255c06ed
@ -3,6 +3,8 @@ package dispenser
|
||||
import (
|
||||
// "encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// "log"
|
||||
"time"
|
||||
|
||||
@ -69,44 +71,78 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
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},
|
||||
}
|
||||
func logStatus(statusBytes []byte) {
|
||||
// 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")
|
||||
}
|
||||
var result strings.Builder
|
||||
for _, p := range posStatus {
|
||||
statusMsg, exists := p.mapper[p.value]
|
||||
if !exists {
|
||||
statusMsg = fmt.Sprintf("Unknown status 0x%X;", p.value)
|
||||
}
|
||||
return result, nil
|
||||
if p.value != 0x30 {
|
||||
result.WriteString(statusMsg + "; ")
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Dispenser status: %s", result.String())
|
||||
}
|
||||
|
||||
func isAtEncoderPosition(statusBytes []byte) bool {
|
||||
if statusBytes == nil {
|
||||
return false
|
||||
}
|
||||
switch statusBytes[3] {
|
||||
case 0x33: // Card at encoder position
|
||||
return true
|
||||
default:
|
||||
return false // Not at encoder position
|
||||
}
|
||||
}
|
||||
|
||||
func stockTake(statusBytes []byte) string {
|
||||
status := ""
|
||||
if statusBytes == nil {
|
||||
return status
|
||||
}
|
||||
if statusBytes[2] != 0x30 {
|
||||
status = statusPos2[statusBytes[2]]
|
||||
}
|
||||
if statusBytes[3] == 0x38 { // Card well empty
|
||||
status = statusPos3[statusBytes[3]]
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func isCardWellEmpty(statusBytes []byte) bool {
|
||||
if statusBytes == nil {
|
||||
return false
|
||||
}
|
||||
switch statusBytes[3] {
|
||||
case 0x38: // Card well empty
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func checkACK(statusResp []byte) error {
|
||||
if len(statusResp) == 3 && statusResp[0] == ACK && statusResp[1] == Address[0] && statusResp[2] == Address[1] {
|
||||
return nil
|
||||
} else if len(statusResp) > 0 && statusResp[0] == NAK {
|
||||
return fmt.Errorf("negative response from dispenser")
|
||||
} 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)
|
||||
}
|
||||
return fmt.Errorf("unexpected response status: % X", statusResp)
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,47 +207,130 @@ func InitializeDispenser() (*serial.Port, error) {
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func DispenserSequence(port *serial.Port) (string, error) {
|
||||
func DispenserPrepare(port *serial.Port) (string, error) {
|
||||
const funcName = "dispenserSequence"
|
||||
var result string
|
||||
|
||||
stockStatus := ""
|
||||
// Check dispenser status
|
||||
status, err := CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
return stockStatus, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
logStatus(status)
|
||||
stockStatus = stockTake(status)
|
||||
if isCardWellEmpty(status) {
|
||||
return stockStatus, nil
|
||||
}
|
||||
result += status
|
||||
|
||||
if isAtEncoderPosition(status) {
|
||||
return stockStatus, nil
|
||||
}
|
||||
// Send card to encoder position
|
||||
status, err = CardToEncoderPosition(port)
|
||||
err = CardToEncoderPosition(port)
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err)
|
||||
return stockStatus, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err)
|
||||
}
|
||||
result += "; " + status
|
||||
|
||||
return result, nil
|
||||
time.Sleep(delay)
|
||||
// Check dispenser status
|
||||
status, err = CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
logStatus(status)
|
||||
stockStatus = stockTake(status)
|
||||
|
||||
return stockStatus, nil
|
||||
}
|
||||
|
||||
func DispenserStart(port *serial.Port) (string, error) {
|
||||
const funcName = "dispenserSequence"
|
||||
stockStatus := ""
|
||||
// Check dispenser status
|
||||
status, err := CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
logStatus(status)
|
||||
stockStatus = stockTake(status)
|
||||
if isCardWellEmpty(status) {
|
||||
return stockStatus, fmt.Errorf(stockStatus)
|
||||
}
|
||||
|
||||
if isAtEncoderPosition(status) {
|
||||
return stockStatus, nil
|
||||
}
|
||||
// Send card to encoder position
|
||||
err = CardToEncoderPosition(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err)
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
// Check dispenser status
|
||||
status, err = CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
logStatus(status)
|
||||
stockStatus = stockTake(status)
|
||||
|
||||
return stockStatus, nil
|
||||
}
|
||||
|
||||
func DispenserFinal(port *serial.Port) (string, error) {
|
||||
const funcName = "dispenserSequence"
|
||||
stockStatus := ""
|
||||
|
||||
err := CardOutOfMouth(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error sending card to out mouth position: %v", funcName, err)
|
||||
}
|
||||
time.Sleep(delay)
|
||||
// Check dispenser status
|
||||
status, err := CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
logStatus(status)
|
||||
stockStatus = stockTake(status)
|
||||
|
||||
time.Sleep(delay)
|
||||
// Send card to encoder position
|
||||
err = CardToEncoderPosition(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err)
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
// Check dispenser status
|
||||
status, err = CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
logStatus(status)
|
||||
stockStatus = stockTake(status)
|
||||
|
||||
return stockStatus, nil
|
||||
}
|
||||
|
||||
// if dispenser is not responding, I should repeat the command
|
||||
func CheckDispenserStatus(port *serial.Port) (string, error) {
|
||||
func CheckDispenserStatus(port *serial.Port) ([]byte, 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)
|
||||
return nil, fmt.Errorf("error sending check command: %v", err)
|
||||
}
|
||||
if len(statusResp) == 0 {
|
||||
return "", fmt.Errorf("no response from dispenser")
|
||||
return nil, fmt.Errorf("no response from dispenser")
|
||||
}
|
||||
status, err := checkStatus(statusResp)
|
||||
err = checkACK(statusResp)
|
||||
if err != nil {
|
||||
return status, err
|
||||
return nil, err
|
||||
}
|
||||
result += "; " + status
|
||||
|
||||
// Send ENQ+ADDR to prompt device to execute the command.
|
||||
statusResp, err = sendAndReceive(port, enq, delay)
|
||||
@ -219,17 +338,15 @@ func CheckDispenserStatus(port *serial.Port) (string, error) {
|
||||
log.Errorf("error sending ENQ: %v", err)
|
||||
}
|
||||
if len(statusResp) == 0 {
|
||||
return "", fmt.Errorf("no response from dispenser")
|
||||
return nil, fmt.Errorf("no response from dispenser")
|
||||
}
|
||||
status, err = checkStatus(statusResp)
|
||||
if err != nil {
|
||||
return status, err
|
||||
if len(statusResp) < 13 {
|
||||
return nil, fmt.Errorf("incomplete status response from dispenser: % X", statusResp)
|
||||
}
|
||||
result += status
|
||||
return result, nil
|
||||
return statusResp[7:11], nil // Return status bytes
|
||||
}
|
||||
|
||||
func CardToEncoderPosition(port *serial.Port) (string, error) {
|
||||
func CardToEncoderPosition(port *serial.Port) error {
|
||||
const funcName = "cartToEncoderPosition"
|
||||
enq := append([]byte{ENQ}, Address...)
|
||||
|
||||
@ -238,30 +355,22 @@ func CardToEncoderPosition(port *serial.Port) (string, error) {
|
||||
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)
|
||||
return fmt.Errorf("error sending card to encoder position: %v", err)
|
||||
}
|
||||
_, err = checkStatus(statusResp)
|
||||
err = checkACK(statusResp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
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)
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
func CardOutOfMouth(port *serial.Port) (string, error) {
|
||||
func CardOutOfMouth(port *serial.Port) error {
|
||||
const funcName = "CardOutOfMouth"
|
||||
enq := append([]byte{ENQ}, Address...)
|
||||
|
||||
@ -270,25 +379,17 @@ func CardOutOfMouth(port *serial.Port) (string, error) {
|
||||
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)
|
||||
return fmt.Errorf("error sending out of mouth command: %v", err)
|
||||
}
|
||||
_, err = checkStatus(statusResp)
|
||||
err = checkACK(statusResp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
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)
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -27,21 +27,23 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
dispPort *serial.Port
|
||||
lockserver lockserver.LockServer
|
||||
isPayment bool
|
||||
db *sql.DB
|
||||
cfg *config.ConfigRec
|
||||
dbMu sync.Mutex
|
||||
dispPort *serial.Port
|
||||
lockserver lockserver.LockServer
|
||||
isPayment bool
|
||||
db *sql.DB
|
||||
cfg *config.ConfigRec
|
||||
dbMu sync.Mutex
|
||||
cardWellStatus string
|
||||
}
|
||||
|
||||
func NewApp(dispPort *serial.Port, lockType, encoderAddress string, db *sql.DB, cfg *config.ConfigRec) *App {
|
||||
func NewApp(dispPort *serial.Port, lockType, encoderAddress, cardWellStatus string, db *sql.DB, cfg *config.ConfigRec) *App {
|
||||
return &App{
|
||||
isPayment: cfg.IsPayment,
|
||||
dispPort: dispPort,
|
||||
lockserver: lockserver.NewLockServer(lockType, encoderAddress, errorhandlers.FatalError),
|
||||
db: db,
|
||||
cfg: cfg,
|
||||
cardWellStatus: cardWellStatus,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +52,7 @@ func (app *App) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/printroomticket", app.printRoomTicket)
|
||||
mux.HandleFunc("/takepreauth", app.takePreauthorization)
|
||||
mux.HandleFunc("/takepayment", app.takePayment)
|
||||
mux.HandleFunc("/dispenserstatus", app.reportDispenserStatus)
|
||||
}
|
||||
|
||||
func (app *App) takePreauthorization(w http.ResponseWriter, r *http.Request) {
|
||||
@ -278,18 +281,10 @@ func (app *App) issueDoorCard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// dispenser sequence
|
||||
if status, err := dispenser.DispenserSequence(app.dispPort); err != nil {
|
||||
if status != "" {
|
||||
logging.Error(serviceName, status, "Dispense error", string(op), "", "", 0)
|
||||
errorhandlers.WriteError(w, http.StatusServiceUnavailable, "Dispense error: "+err.Error())
|
||||
} else {
|
||||
logging.Error(serviceName, err.Error(), "Dispense error", string(op), "", "", 0)
|
||||
errorhandlers.WriteError(w, http.StatusServiceUnavailable, "Dispense error: "+err.Error()+"; check card stock")
|
||||
}
|
||||
if app.cardWellStatus, err = dispenser.DispenserStart(app.dispPort); err != nil {
|
||||
logging.Error(serviceName, err.Error(), "Dispense error", string(op), "", "", 0)
|
||||
errorhandlers.WriteError(w, http.StatusServiceUnavailable, "Dispense error: "+err.Error())
|
||||
return
|
||||
} else {
|
||||
log.Info(status)
|
||||
}
|
||||
|
||||
// build lock server command
|
||||
@ -299,18 +294,16 @@ func (app *App) issueDoorCard(w http.ResponseWriter, r *http.Request) {
|
||||
err = app.lockserver.LockSequence()
|
||||
if err != nil {
|
||||
logging.Error(serviceName, err.Error(), "Key encoding", string(op), "", "", 0)
|
||||
dispenser.DispenserFinal(app.dispPort)
|
||||
errorhandlers.WriteError(w, http.StatusBadGateway, err.Error())
|
||||
dispenser.CardOutOfMouth(app.dispPort)
|
||||
return
|
||||
}
|
||||
|
||||
// final dispenser steps
|
||||
if status, err := dispenser.CardOutOfMouth(app.dispPort); err != nil {
|
||||
if app.cardWellStatus, err = dispenser.DispenserFinal(app.dispPort); err != nil {
|
||||
logging.Error(serviceName, err.Error(), "Dispenser eject error", string(op), "", "", 0)
|
||||
errorhandlers.WriteError(w, http.StatusServiceUnavailable, "Dispenser eject error: "+err.Error())
|
||||
return
|
||||
} else {
|
||||
log.Info(status)
|
||||
}
|
||||
|
||||
theResponse.Code = http.StatusOK
|
||||
@ -372,3 +365,12 @@ func (app *App) printRoomTicket(w http.ResponseWriter, r *http.Request) {
|
||||
Message: "Print job sent successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (app *App) reportDispenserStatus(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(cmstypes.StatusRec{
|
||||
Code: http.StatusOK,
|
||||
Message: app.cardWellStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -155,7 +155,7 @@ func (lock *SaltoLockServer) LockSequence() error {
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
// 1. Send ENQ
|
||||
log.Infof("Sending ENQ")
|
||||
log.Infof("LockSequence: sending ENQ")
|
||||
if _, e := conn.Write([]byte{ENQ}); e != nil {
|
||||
return fmt.Errorf("failed to send ENQ: %w", e)
|
||||
}
|
||||
@ -166,7 +166,7 @@ func (lock *SaltoLockServer) LockSequence() error {
|
||||
}
|
||||
|
||||
// 3. Send command frame
|
||||
log.Infof("Sending encoding command: %q", string(lock.command))
|
||||
log.Infof("LockSequence: sending encoding command: %q", string(lock.command))
|
||||
if _, e := conn.Write(lock.command); e != nil {
|
||||
return fmt.Errorf("failed to send command frame: %w", e)
|
||||
}
|
||||
|
||||
20
main.go
20
main.go
@ -18,15 +18,15 @@ import (
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/bootstrap"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/config"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/dispenser"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/handlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/errorhandlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/handlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/lockserver"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/logging"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/printer"
|
||||
)
|
||||
|
||||
const (
|
||||
buildVersion = "1.0.30"
|
||||
buildVersion = "1.1.0"
|
||||
serviceName = "hardlink"
|
||||
)
|
||||
|
||||
@ -38,6 +38,7 @@ func main() {
|
||||
lockserver.Cert = config.Cert
|
||||
lockserver.LockServerURL = config.LockserverUrl
|
||||
dispHandle := &serial.Port{}
|
||||
cardWellStatus := ""
|
||||
|
||||
// Setup logging and get file handle
|
||||
logFile, err := logging.SetupLogging(config.LogDir, serviceName, buildVersion)
|
||||
@ -56,17 +57,12 @@ func main() {
|
||||
}
|
||||
defer dispHandle.Close()
|
||||
|
||||
status, err := dispenser.CheckDispenserStatus(dispHandle)
|
||||
cardWellStatus, err = dispenser.DispenserPrepare(dispHandle)
|
||||
if err != nil {
|
||||
if len(status) == 0 {
|
||||
err = fmt.Errorf("%s; wrong dispenser address: %s", err, config.DispenserAdrr)
|
||||
errorhandlers.FatalError(err)
|
||||
} else {
|
||||
fmt.Println(status)
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
err = fmt.Errorf("%s; wrong dispenser address: %s", err, config.DispenserAdrr)
|
||||
errorhandlers.FatalError(err)
|
||||
}
|
||||
log.Infof("Dispenser initialized on port %s, %s", config.DispenserPort, status)
|
||||
fmt.Println(cardWellStatus)
|
||||
}
|
||||
|
||||
// Test lock-server connection
|
||||
@ -101,7 +97,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Create App and wire routes
|
||||
app := handlers.NewApp(dispHandle, config.LockType, config.EncoderAddress, database, &config)
|
||||
app := handlers.NewApp(dispHandle, config.LockType, config.EncoderAddress, cardWellStatus, database, &config)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
app.RegisterRoutes(mux)
|
||||
|
||||
@ -9,6 +9,8 @@ divided `/starttransaction` endpoint into two separate endpoints:
|
||||
added preauth releaser functionality to release preauthorization payments after a defined time period
|
||||
added db connection check before adding a transaction to the database
|
||||
and reconnection functionality if the connection to the database is lost
|
||||
added `/dispenserstatus` endpoint
|
||||
key card always stays at encoder position
|
||||
|
||||
#### 1.0.30 - 09 January 2026
|
||||
improved logging for preauth releaser
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user