Compare commits
No commits in common. "4a255c06ed72ad16d1a049066ec67b9e18f2e40b" and "7f6262b470ac1a87c7eef52b24d437580dd12c5b" have entirely different histories.
4a255c06ed
...
7f6262b470
@ -3,8 +3,6 @@ package dispenser
|
||||
import (
|
||||
// "encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// "log"
|
||||
"time"
|
||||
|
||||
@ -71,7 +69,9 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func logStatus(statusBytes []byte) {
|
||||
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
|
||||
@ -84,65 +84,29 @@ func logStatus(statusBytes []byte) {
|
||||
{pos: 4, value: statusBytes[3], mapper: statusPos3},
|
||||
}
|
||||
|
||||
var result strings.Builder
|
||||
result := ""
|
||||
for _, p := range posStatus {
|
||||
statusMsg, exists := p.mapper[p.value]
|
||||
if !exists {
|
||||
statusMsg = fmt.Sprintf("Unknown status 0x%X;", p.value)
|
||||
statusMsg = "Unknown status"
|
||||
}
|
||||
if p.value != 0x30 {
|
||||
result.WriteString(statusMsg + "; ")
|
||||
result += fmt.Sprintf("Status: %s; ", statusMsg)
|
||||
}
|
||||
if p.pos == 4 && p.value == 0x38 {
|
||||
return result, fmt.Errorf("Card well empty")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("unexpected response status: % X", statusResp)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,130 +171,47 @@ func InitializeDispenser() (*serial.Port, error) {
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func DispenserPrepare(port *serial.Port) (string, error) {
|
||||
func DispenserSequence(port *serial.Port) (string, error) {
|
||||
const funcName = "dispenserSequence"
|
||||
stockStatus := ""
|
||||
var result string
|
||||
|
||||
// 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, nil
|
||||
return status, fmt.Errorf("[%s] error checking dispenser status: %v", funcName, err)
|
||||
}
|
||||
result += status
|
||||
|
||||
if isAtEncoderPosition(status) {
|
||||
return stockStatus, nil
|
||||
}
|
||||
// Send card to encoder position
|
||||
err = CardToEncoderPosition(port)
|
||||
status, err = CardToEncoderPosition(port)
|
||||
if err != nil {
|
||||
return stockStatus, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err)
|
||||
return status, fmt.Errorf("[%s] error sending card to encoder position: %v", funcName, err)
|
||||
}
|
||||
result += "; " + status
|
||||
|
||||
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
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// if dispenser is not responding, I should repeat the command
|
||||
func CheckDispenserStatus(port *serial.Port) ([]byte, error) {
|
||||
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 nil, fmt.Errorf("error sending check command: %v", err)
|
||||
return "", fmt.Errorf("error sending check command: %v", err)
|
||||
}
|
||||
if len(statusResp) == 0 {
|
||||
return nil, fmt.Errorf("no response from dispenser")
|
||||
return "", fmt.Errorf("no response from dispenser")
|
||||
}
|
||||
err = checkACK(statusResp)
|
||||
status, err := checkStatus(statusResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return status, err
|
||||
}
|
||||
result += "; " + status
|
||||
|
||||
// Send ENQ+ADDR to prompt device to execute the command.
|
||||
statusResp, err = sendAndReceive(port, enq, delay)
|
||||
@ -338,15 +219,17 @@ func CheckDispenserStatus(port *serial.Port) ([]byte, error) {
|
||||
log.Errorf("error sending ENQ: %v", err)
|
||||
}
|
||||
if len(statusResp) == 0 {
|
||||
return nil, fmt.Errorf("no response from dispenser")
|
||||
return "", fmt.Errorf("no response from dispenser")
|
||||
}
|
||||
if len(statusResp) < 13 {
|
||||
return nil, fmt.Errorf("incomplete status response from dispenser: % X", statusResp)
|
||||
status, err = checkStatus(statusResp)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
return statusResp[7:11], nil // Return status bytes
|
||||
result += status
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CardToEncoderPosition(port *serial.Port) error {
|
||||
func CardToEncoderPosition(port *serial.Port) (string, error) {
|
||||
const funcName = "cartToEncoderPosition"
|
||||
enq := append([]byte{ENQ}, Address...)
|
||||
|
||||
@ -355,22 +238,30 @@ func CardToEncoderPosition(port *serial.Port) 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 = checkACK(statusResp)
|
||||
_, err = checkStatus(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 nil
|
||||
return "", fmt.Errorf("error sending ENQ to prompt device: %v", err)
|
||||
}
|
||||
|
||||
func CardOutOfMouth(port *serial.Port) error {
|
||||
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...)
|
||||
|
||||
@ -379,17 +270,25 @@ func CardOutOfMouth(port *serial.Port) 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 = checkACK(statusResp)
|
||||
_, err = checkStatus(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)
|
||||
}
|
||||
return nil
|
||||
|
||||
time.Sleep(delay)
|
||||
|
||||
//Check card position status
|
||||
status, err := CheckDispenserStatus(port)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
@ -1,25 +1,13 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/db"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type preauthSpoolRecord struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
CheckoutDate string `json:"checkoutDate"` // keep as received
|
||||
Fields map[string]string `json:"fields"` // ChipDNA result.Fields
|
||||
}
|
||||
|
||||
func (app *App) getDB(ctx context.Context) (*sql.DB, error) {
|
||||
app.dbMu.Lock()
|
||||
defer app.dbMu.Unlock()
|
||||
@ -52,6 +40,7 @@ func (app *App) getDB(ctx context.Context) (*sql.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Optional ping (InitMSSQL already pings, but this keeps semantics explicit)
|
||||
pingCtx, cancel2 := context.WithTimeout(dialCtx, 1*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
@ -63,144 +52,3 @@ func (app *App) getDB(ctx context.Context) (*sql.DB, error) {
|
||||
app.db = dbConn
|
||||
return app.db, nil
|
||||
}
|
||||
|
||||
func (app *App) spoolPath() string {
|
||||
// keep it near logs; adjust if you prefer a dedicated dir
|
||||
// ensure LogDir ends with separator in your config loader
|
||||
return filepath.Join(app.cfg.LogDir, "preauth_spool.ndjson")
|
||||
}
|
||||
|
||||
// persistPreauth tries DB first; if DB is down or insert fails, it spools to file.
|
||||
// It never returns an error to the caller (so your HTTP flow stays simple),
|
||||
// but it logs failures.
|
||||
func (app *App) persistPreauth(ctx context.Context, fields map[string]string, checkoutDate string) {
|
||||
// First, try DB (with your reconnect logic inside getDB)
|
||||
dbConn, err := app.getDB(ctx)
|
||||
if err == nil && dbConn != nil {
|
||||
if err := db.InsertPreauth(ctx, dbConn, fields, checkoutDate); err == nil {
|
||||
// opportunistic drain once DB is alive
|
||||
go app.drainPreauthSpool(context.Background())
|
||||
return
|
||||
} else {
|
||||
log.WithError(err).Warn("DB insert failed; will spool preauth")
|
||||
}
|
||||
} else {
|
||||
log.WithError(err).Warn("DB unavailable; will spool preauth")
|
||||
}
|
||||
|
||||
// Fallback: spool to file
|
||||
rec := preauthSpoolRecord{
|
||||
CreatedAt: time.Now().UTC(),
|
||||
CheckoutDate: checkoutDate,
|
||||
Fields: fields,
|
||||
}
|
||||
if spErr := app.spoolPreauth(rec); spErr != nil {
|
||||
log.WithError(spErr).Error("failed to spool preauth")
|
||||
}
|
||||
}
|
||||
|
||||
// append one line JSON (NDJSON)
|
||||
func (app *App) spoolPreauth(rec preauthSpoolRecord) error {
|
||||
p := app.spoolPath()
|
||||
|
||||
f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open spool file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b, err := json.Marshal(rec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal spool record: %w", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(append(b, '\n')); err != nil {
|
||||
return fmt.Errorf("write spool record: %w", err)
|
||||
}
|
||||
|
||||
return f.Sync() // ensure it's on disk
|
||||
}
|
||||
|
||||
// Drain spool into DB.
|
||||
// Strategy: read all lines, insert each; keep failures in a temp file; then replace original.
|
||||
func (app *App) drainPreauthSpool(ctx context.Context) {
|
||||
dbConn, err := app.getDB(ctx)
|
||||
if err != nil {
|
||||
return // still down, nothing to do
|
||||
}
|
||||
|
||||
spool := app.spoolPath()
|
||||
in, err := os.Open(spool)
|
||||
if err != nil {
|
||||
// no spool is fine
|
||||
return
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
tmp := spool + ".tmp"
|
||||
out, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("drain spool: open tmp failed")
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
sc := bufio.NewScanner(in)
|
||||
// allow long lines if receipts ever sneak in (shouldn't, but safe)
|
||||
buf := make([]byte, 0, 64*1024)
|
||||
sc.Buffer(buf, 2*1024*1024)
|
||||
|
||||
var (
|
||||
okCount int
|
||||
failCount int
|
||||
)
|
||||
|
||||
for sc.Scan() {
|
||||
line := sc.Bytes()
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var rec preauthSpoolRecord
|
||||
if err := json.Unmarshal(line, &rec); err != nil {
|
||||
// malformed line: keep it so we don't lose evidence
|
||||
_, _ = out.Write(append(line, '\n'))
|
||||
failCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// attempt insert
|
||||
if err := db.InsertPreauth(ctx, dbConn, rec.Fields, rec.CheckoutDate); err != nil {
|
||||
// DB still flaky or data issue: keep it for later retry
|
||||
_, _ = out.Write(append(line, '\n'))
|
||||
failCount++
|
||||
continue
|
||||
}
|
||||
|
||||
okCount++
|
||||
}
|
||||
|
||||
if err := sc.Err(); err != nil {
|
||||
log.WithError(err).Warn("drain spool: scanner error")
|
||||
// best effort; do not replace spool
|
||||
return
|
||||
}
|
||||
|
||||
_ = out.Sync()
|
||||
|
||||
// Replace original spool with temp (atomic on Windows is best-effort; still OK here)
|
||||
_ = in.Close()
|
||||
_ = out.Close()
|
||||
|
||||
if err := os.Rename(tmp, spool); err != nil {
|
||||
log.WithError(err).Warn("drain spool: rename failed")
|
||||
return
|
||||
}
|
||||
|
||||
if okCount > 0 || failCount > 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"inserted": okCount,
|
||||
"remaining": failCount,
|
||||
}).Info("preauth spool drained")
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
@ -16,6 +15,7 @@ import (
|
||||
|
||||
"gitea.futuresens.co.uk/futuresens/cmstypes"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/config"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/db"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/dispenser"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/errorhandlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/lockserver"
|
||||
@ -33,17 +33,15 @@ type App struct {
|
||||
db *sql.DB
|
||||
cfg *config.ConfigRec
|
||||
dbMu sync.Mutex
|
||||
cardWellStatus string
|
||||
}
|
||||
|
||||
func NewApp(dispPort *serial.Port, lockType, encoderAddress, cardWellStatus string, db *sql.DB, cfg *config.ConfigRec) *App {
|
||||
func NewApp(dispPort *serial.Port, lockType, encoderAddress 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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +50,6 @@ 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) {
|
||||
@ -140,7 +137,14 @@ func (app *App) takePreauthorization(w http.ResponseWriter, r *http.Request) {
|
||||
theResponse.Status = result.Status
|
||||
theResponse.Data, save = payment.BuildPreauthRedirectURL(result.Fields)
|
||||
if save {
|
||||
go app.persistPreauth(context.Background(), result.Fields, theRequest.CheckoutDate)
|
||||
dbConn, err := app.getDB(r.Context())
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("DB unavailable; preauth not stored")
|
||||
} else {
|
||||
if err := db.InsertPreauth(r.Context(), dbConn, result.Fields, theRequest.CheckoutDate); err != nil {
|
||||
log.WithError(err).Warn("Failed to store preauth in DB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeTransactionResult(w, http.StatusOK, theResponse)
|
||||
@ -281,10 +285,18 @@ func (app *App) issueDoorCard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if app.cardWellStatus, err = dispenser.DispenserStart(app.dispPort); err != nil {
|
||||
logging.Error(serviceName, err.Error(), "Dispense error", string(op), "", "", 0)
|
||||
// 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")
|
||||
}
|
||||
return
|
||||
} else {
|
||||
log.Info(status)
|
||||
}
|
||||
|
||||
// build lock server command
|
||||
@ -294,16 +306,18 @@ 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 app.cardWellStatus, err = dispenser.DispenserFinal(app.dispPort); err != nil {
|
||||
if status, err := dispenser.CardOutOfMouth(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
|
||||
@ -365,12 +379,3 @@ 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("LockSequence: sending ENQ")
|
||||
log.Infof("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("LockSequence: sending encoding command: %q", string(lock.command))
|
||||
log.Infof("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)
|
||||
}
|
||||
|
||||
16
main.go
16
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/errorhandlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/handlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/errorhandlers"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/lockserver"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/logging"
|
||||
"gitea.futuresens.co.uk/futuresens/hardlink/printer"
|
||||
)
|
||||
|
||||
const (
|
||||
buildVersion = "1.1.0"
|
||||
buildVersion = "1.0.30"
|
||||
serviceName = "hardlink"
|
||||
)
|
||||
|
||||
@ -38,7 +38,6 @@ 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)
|
||||
@ -57,12 +56,17 @@ func main() {
|
||||
}
|
||||
defer dispHandle.Close()
|
||||
|
||||
cardWellStatus, err = dispenser.DispenserPrepare(dispHandle)
|
||||
status, err := dispenser.CheckDispenserStatus(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())
|
||||
}
|
||||
fmt.Println(cardWellStatus)
|
||||
}
|
||||
log.Infof("Dispenser initialized on port %s, %s", config.DispenserPort, status)
|
||||
}
|
||||
|
||||
// Test lock-server connection
|
||||
@ -97,7 +101,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Create App and wire routes
|
||||
app := handlers.NewApp(dispHandle, config.LockType, config.EncoderAddress, cardWellStatus, database, &config)
|
||||
app := handlers.NewApp(dispHandle, config.LockType, config.EncoderAddress, database, &config)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
app.RegisterRoutes(mux)
|
||||
|
||||
@ -2,16 +2,6 @@
|
||||
|
||||
builtVersion is a const in main.go
|
||||
|
||||
#### 1.1.0 - 26 January 2026
|
||||
divided `/starttransaction` endpoint into two separate endpoints:
|
||||
`/takepreauth` to request preauthorization payment
|
||||
`/takepayment` to request taking payment
|
||||
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