added encoding keycard copy for Salto locks

This commit is contained in:
yurii 2025-07-24 15:12:06 +01:00
parent b303721a92
commit 5ce9fdcf0b
6 changed files with 34 additions and 20 deletions

View File

@ -10,11 +10,11 @@ import (
)
// Build key encoding request command for the Assa Abloy lock server.
func (lock *AssaLockServer) BuildCommand(lockId string, checkIn, checkOut time.Time) error {
func (lock *AssaLockServer) BuildCommand(doorReq DoorCardRequest, checkIn, checkOut time.Time) error {
ci := checkIn.Format("200601021504")
co := checkOut.Format("200601021504")
lock.command = fmt.Sprintf("CCA;EA%s;GR%s;CO%s;CI%s;AM1;\r\n", lock.encoderAddr, lockId, co, ci)
lock.command = fmt.Sprintf("CCA;EA%s;GR%s;CO%s;CI%s;AM1;\r\n", lock.encoderAddr, doorReq.RoomField, co, ci)
return nil
}

View File

@ -12,6 +12,14 @@ import (
log "github.com/sirupsen/logrus"
)
// DoorCardRequest is the JSON payload for /issue-door-card.
type DoorCardRequest struct {
RoomField string `json:"roomField"`
CheckinTime string `json:"checkinTime"`
CheckoutTime string `json:"checkoutTime"`
FollowStr string `json:"followStr"`
}
const (
AssaAbloy = "assaabloy"
Omnitec = "omnitec"
@ -20,7 +28,7 @@ const (
type (
LockServer interface {
BuildCommand(lockId string, checkIn, checkOut time.Time) error
BuildCommand(doorReq DoorCardRequest, checkIn, checkOut time.Time) error
LockSequence(conn net.Conn) error
}

View File

@ -13,7 +13,7 @@ import (
)
// Build key encoding request command for the Omnitec lock server.
func (lock *OmniLockServer) BuildCommand(lockId string, checkIn, checkOut time.Time) error {
func (lock *OmniLockServer) BuildCommand(doorReq DoorCardRequest, checkIn, checkOut time.Time) error {
const funcName = "OmniLockServer.BuildCommand"
hostname, err := os.Hostname()
if err != nil {
@ -21,7 +21,7 @@ func (lock *OmniLockServer) BuildCommand(lockId string, checkIn, checkOut time.T
}
// Format lockId as 4-digit zero-padded string
idInt, err := strconv.Atoi(lockId)
idInt, err := strconv.Atoi(doorReq.RoomField)
if err != nil {
return fmt.Errorf("[%s] failed to convert lockId to integer: %v", funcName, err)
}

View File

@ -41,8 +41,9 @@ func stringJoin(parts []string, sep string) string {
// BuildCommand builds a Salto cardissuance command in the form:
// STX|CN|<encoder addres>|E|<room>| | | | | |<start>|<expiry>|ETX|<LRC>
// where <start> and <expiry> are hhmmDDMMYY, and LRC is the XOR of all bytes
func (lock *SaltoLockServer) BuildCommand(lockId string, checkIn, checkOut time.Time) error {
func (lock *SaltoLockServer) BuildCommand(doorReq DoorCardRequest, checkIn, checkOut time.Time) error {
// format helper: hhmmDDMMYY
var command string
fmtStamp := func(t time.Time) string {
return fmt.Sprintf("%02d%02d%02d%02d%02d",
t.Hour(), t.Minute(), t.Day(), int(t.Month()), t.Year()%100)
@ -51,9 +52,18 @@ func (lock *SaltoLockServer) BuildCommand(lockId string, checkIn, checkOut time.
start := fmtStamp(checkIn)
expiry := fmtStamp(checkOut)
switch doorReq.FollowStr {
case "0":
command = "CN" // encode keycard
case "1":
command = "CC" // encode keycard copy
default:
command = "CN"
}
// the 12 fields between STX and ETX
fields := []string{
"CN", lock.encoderAddr, "E", lockId, "", "", "", "", "", "", start, expiry,
command, lock.encoderAddr, "E", doorReq.RoomField, "", "", "", "", "", "", start, expiry,
}
body := "|" + stringJoin(fields, "|") + "|" // leading/trailing pipes, so the ETX ends the last field

14
main.go
View File

@ -30,7 +30,7 @@ import (
)
const (
buildVersion = "1.0.4"
buildVersion = "1.0.5"
serviceName = "hardlink"
customLayout = "2006-01-02 15:04:05 -0700"
transactionUrl = "http://127.0.0.1:18181/start-transaction/"
@ -49,13 +49,6 @@ type configRec struct {
isPayment bool `yaml:"isPayment"`
}
// DoorCardRequest is the JSON payload for /issue-door-card.
type DoorCardRequest struct {
RoomField string `json:"roomField"`
CheckinTime string `json:"checkinTime"`
CheckoutTime string `json:"checkoutTime"`
FollowStr string `json:"followStr"`
}
// App holds shared resources.
type App struct {
@ -179,6 +172,7 @@ func main() {
}
// Create App and wire routes
// dispHandle := &serial.Port{} // Placeholder, replace with actual dispenser handle
app := newApp(dispHandle, lockConn, config)
mux := http.NewServeMux()
@ -323,7 +317,7 @@ func (app *App) startTransaction(w http.ResponseWriter, r *http.Request) {
func (app *App) issueDoorCard(w http.ResponseWriter, r *http.Request) {
const op = logging.Op("issueDoorCard")
var (
doorReq DoorCardRequest
doorReq lockserver.DoorCardRequest
theResponse cmstypes.StatusRec
)
@ -384,7 +378,7 @@ func (app *App) issueDoorCard(w http.ResponseWriter, r *http.Request) {
}
// build lock server command
app.lockserver.BuildCommand(doorReq.RoomField, checkIn, checkOut)
app.lockserver.BuildCommand(doorReq, checkIn, checkOut)
// lock server sequence
err = app.lockserver.LockSequence(app.lockConn)

View File

@ -2,9 +2,11 @@
builtVersion is a const in main.go
#### 1.0.1 - 22 July 2024
added salto lock server and implemented workflow for Salto
#### 1.0.5 - 24 July 2024
added encoding keycard copy for Salto locks
#### 1.0.4 - 22 July 2024
added salto lock server and implemented workflow for Salto
#### 1.0.0 - 30 June 2024
added creditcall payment method