hardlink/lockserver/saltolockserver.go

104 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package lockserver
import (
"fmt"
"net"
"time"
// "strings"
log "github.com/sirupsen/logrus"
)
const (
STX = 0x02 // Start of Text
ETX = 0x03 // End of Text
ENQ = 0x05 // Enquiry from host
ACK = 0x06 // Positive response
NAK = 0x15 // Negative response
)
func calculateLRC(data []byte) byte {
lrc := byte(0x00)
for _, b := range data {
lrc ^= b
}
return lrc
}
// simple join that avoids importing strings just for this.
func stringJoin(parts []string, sep string) string {
if len(parts) == 0 {
return ""
}
out := parts[0]
for _, p := range parts[1:] {
out += sep + p
}
return out
}
// 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 {
// format helper: hhmmDDMMYY
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)
}
start := fmtStamp(checkIn)
expiry := fmtStamp(checkOut)
// the 12 fields between STX and ETX
fields := []string{
"CN", lock.encoderAddr, "E", lockId, "", "", "", "", "", "", start, expiry,
}
body := "|" + stringJoin(fields, "|") + "|" // leading/trailing pipes, so the ETX ends the last field
// wrap with STX/ETX
msg := append([]byte{STX}, []byte(body)...)
msg = append(msg, ETX)
// compute LRC over everything *after* STX up to and including ETX
lrc := calculateLRC(msg[1:]) // skip STX
msg = append(msg, lrc)
lock.command = msg
return nil
}
// Checks heart beat of the Assa Abloy lock server and perform key encoding
func (lock *SaltoLockServer) LockSequence(conn net.Conn) error {
const funcName = "SaltoLockServer.LockSequence"
// Step 1: ENQ to check availability
respStr, err := sendAndReceive(conn, []byte{ENQ})
if err != nil {
return fmt.Errorf("[%s] failed sending ENQ: %w", funcName, err)
}
resp := []byte(respStr)
if len(resp) != 1 || resp[0] != ACK {
return fmt.Errorf("[%s] expected ACK (0x06) after ENQ, got: %q (hex: % X)", funcName, respStr, resp)
}
// Step 2: Send actual command
respStr, err = sendAndReceive(conn, lock.command)
if err != nil {
return fmt.Errorf("[%s] failed sending command: %w", funcName, err)
}
resp = []byte(respStr)
if len(resp) == 1 && resp[0] == NAK {
return fmt.Errorf("[%s] command rejected by lock server (NAK)", funcName)
}
log.Infof("Encoding response: %q", respStr)
return nil
}