From eff59c389fd046a75bb67b45e2e52dcc712413d9 Mon Sep 17 00:00:00 2001
From: zhangzengfei <zhangzengfei@smartai.com>
Date: 星期五, 13 九月 2024 01:19:38 +0800
Subject: [PATCH] add rfid

---
 config/config.go |   10 +
 go.sum           |    2 
 rfid/rw.go       |  154 +++++++++++++++++++++++++
 rfid/proto.go    |    5 
 rfid/crc.go      |   18 +++
 go.mod           |    1 
 service/nvcs.go  |  162 +++++++++++++++++++++++---
 service/rfid.go  |    5 
 8 files changed, 336 insertions(+), 21 deletions(-)

diff --git a/config/config.go b/config/config.go
index f4609ee..31ebd4d 100644
--- a/config/config.go
+++ b/config/config.go
@@ -60,11 +60,19 @@
 
 // 姊帶璁惧
 type nvcs struct {
+	Mac         string `mapstructure:"mac"`
 	Model       string `mapstructure:"model"` // 鍨嬪彿
 	Port        string `mapstructure:"port"`  // 绔彛
 	OSD         string `mapstructure:"osd"`
 	RunState    bool   `mapstructure:"run-state"`
 	WaitRunTime int    `mapstructure:"wait-run-time"`
+}
+
+type rfid struct {
+	DevName  string `mapstructure:"dev"`
+	Baud     int    `mapstructure:"baud"`
+	EPC      string `mapstructure:"epc"`
+	Position uint8  `mapstructure:"postion"`
 }
 
 type rateLimit struct {
@@ -85,6 +93,7 @@
 var NVCSConf = &nvcs{}
 var ImageConf = &image{}
 var SysTimeConf = &sysTime{}
+var RFIDConf = &rfid{}
 
 // Init is an exported method that takes the environment starts the viper
 // (external lib) and returns the configuration struct.
@@ -115,6 +124,7 @@
 	v.UnmarshalKey("rate-limit", RateLimitConf)
 	v.UnmarshalKey("client", ClientConf)
 	v.UnmarshalKey("nvcs", NVCSConf)
+	v.UnmarshalKey("rfid", RFIDConf)
 	v.UnmarshalKey("image", ImageConf)
 
 	if LogConf.Level == "" {
diff --git a/go.mod b/go.mod
index dc122a9..5e9b101 100644
--- a/go.mod
+++ b/go.mod
@@ -48,6 +48,7 @@
 	github.com/spf13/cast v1.6.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
+	github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
diff --git a/go.sum b/go.sum
index 03cfcb9..5e868cd 100644
--- a/go.sum
+++ b/go.sum
@@ -113,6 +113,8 @@
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
diff --git a/rfid/crc.go b/rfid/crc.go
new file mode 100644
index 0000000..ec15d99
--- /dev/null
+++ b/rfid/crc.go
@@ -0,0 +1,18 @@
+package rfid
+
+func CRC16XMODEM(data []byte) uint16 {
+	var crc uint16 = 0x0000 // 鍒濆鍖� CRC 鍊�
+	polynomial := uint16(0x1021)
+
+	for _, b := range data {
+		crc ^= uint16(b) << 8
+		for i := 0; i < 8; i++ {
+			if crc&0x8000 != 0 {
+				crc = (crc << 1) ^ polynomial
+			} else {
+				crc <<= 1
+			}
+		}
+	}
+	return crc
+}
diff --git a/rfid/proto.go b/rfid/proto.go
new file mode 100644
index 0000000..cbd59f8
--- /dev/null
+++ b/rfid/proto.go
@@ -0,0 +1,5 @@
+package rfid
+
+const (
+	ControlWordEPCReadResponse6C uint32 = 0x00011200
+)
diff --git a/rfid/rw.go b/rfid/rw.go
new file mode 100644
index 0000000..fd2fc7b
--- /dev/null
+++ b/rfid/rw.go
@@ -0,0 +1,154 @@
+package rfid
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"time"
+
+	"github.com/tarm/serial"
+)
+
+func NewReader(devName string, baud, readDuration int) *Reader {
+	return &Reader{
+		DevName:      devName,
+		Baud:         baud,
+		ReadDuration: readDuration,
+	}
+}
+
+type Reader struct {
+	DevName      string
+	Baud         int
+	ReadDuration int
+	DevPort      *serial.Port
+}
+
+func (r *Reader) OpenSerial() (err error) {
+	config := &serial.Config{
+		Name:        r.DevName,
+		Baud:        r.Baud,
+		ReadTimeout: 2 * time.Second,
+	}
+
+	r.DevPort, err = serial.OpenPort(config)
+
+	return err
+}
+
+func (r *Reader) CloseSerial() error {
+	return r.DevPort.Close()
+}
+
+// AutoReadStart 寮�鍚嚜鍔ㄨ鍙�, 杩斿洖鍐欏叆鐨勬寚浠ら暱搴﹀拰閿欒
+func (r *Reader) StartAutoRead() error {
+	cmd := "5A0001010B00100001000B0001021000050000000101005EE2"
+
+	data, _ := hex.DecodeString(cmd)
+
+	_, err := r.DevPort.Write(data)
+	if err != nil {
+		return nil
+	}
+
+	// todo parse response
+	r.ReadResponse()
+
+	return err
+}
+
+// StopAutoRead 鍋滄璇诲彇
+func (r *Reader) StopAutoRead() error {
+	cmd := "5A000102FF0000885A"
+
+	data, _ := hex.DecodeString(cmd)
+
+	_, err := r.DevPort.Write(data)
+	if err != nil {
+		return nil
+	}
+
+	// todo parse response
+	r.ReadResponse()
+
+	return err
+}
+
+func (r *Reader) ReadResponse() (int, error) {
+	buf := make([]byte, 1024) // 鏍规嵁鍗忚鏈�澶ф暟鎹暱搴﹁皟鏁寸紦鍐插尯
+	return r.DevPort.Read(buf)
+}
+
+func (r *Reader) ScanSpecificEPC(target string, minCount int) (bool, error) {
+	err := r.StartAutoRead()
+	if err != nil {
+		return false, err
+	}
+
+	defer r.StopAutoRead()
+
+	stop := time.After(time.Duration(r.ReadDuration) * time.Second)
+
+	// 鏍规嵁鍗忚鏈�澶ф暟鎹暱搴﹁皟鏁寸紦鍐插尯
+	buf := make([]byte, 1024)
+	scanCount := 0
+	for {
+		select {
+		case <-stop:
+			fmt.Println("璇诲彇宸茶秴鏃�")
+			return false, nil
+		default:
+			for i := 0; i < 1024; i++ {
+				buf[i] = 0 // 娓呴浂鎴栧叾浠栧鐞�
+			}
+
+			n, err := r.DevPort.Read(buf)
+			if err != nil {
+				return false, err
+			}
+
+			if n == 0 || n < 8 {
+				continue // 濡傛灉娌℃湁璇诲彇鍒版暟鎹�
+			}
+
+			// 妫�鏌ュ抚澶�
+			if buf[0] != 0x5A {
+				continue // 蹇界暐閿欒甯�
+			}
+			fmt.Printf("Recive message %x\n", buf[:n]) // 鎵撳嵃鍗忚鎺у埗瀛楄繘琛岃皟璇�
+
+			// 鏍¢獙CRC
+			//fmt.Printf("Crc %x\n",buf[n-2 : n])
+			receivedCrc := binary.BigEndian.Uint16(buf[n-2 : n])
+			computedCrc := CRC16XMODEM(buf[1 : n-2])
+			if receivedCrc != (computedCrc & 0xFFFF) {
+				fmt.Println("CRC check failed")
+				continue
+			}
+
+			// 瑙f瀽鍗忚鎺у埗瀛� (浠呭湪闇�瑕佹椂浣跨敤)
+			//fmt.Printf("Control Word: %x\n", buf[1:5]) // 鎵撳嵃鍗忚鎺у埗瀛楄繘琛岃皟璇�
+
+			controlWord := binary.BigEndian.Uint32(buf[1:5])
+			if controlWord != ControlWordEPCReadResponse6C {
+				fmt.Printf("Control Word: %d, rec word %d\n", ControlWordEPCReadResponse6C, controlWord)
+				continue
+			}
+
+			// 瑙f瀽EPC鏁版嵁闀垮害
+			epcLength := binary.BigEndian.Uint16(buf[7:9])
+			//fmt.Printf("EPC length %d, EPC %x \n", epcLength, buf[9:9+epcLength])
+
+			// 鍥炶皟浼犻�乪pc鏁版嵁
+			//callBack(buf[9 : 9+epcLength])
+			epcData := fmt.Sprintf("%x", buf[9:9+epcLength])
+			if epcData == target {
+				scanCount++
+				if scanCount > minCount {
+					return true, nil
+				}
+			}
+			fmt.Printf("read epc %s, target epc: %s", epcData, target)
+		}
+	}
+}
diff --git a/service/nvcs.go b/service/nvcs.go
index d5b1f41..3e21a84 100644
--- a/service/nvcs.go
+++ b/service/nvcs.go
@@ -2,6 +2,8 @@
 
 import (
 	"bytes"
+	"encoding/binary"
+	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
@@ -13,14 +15,39 @@
 	"gat1400Exchange/config"
 	"gat1400Exchange/models"
 	"gat1400Exchange/pkg/logger"
+	"gat1400Exchange/rfid"
 	"gat1400Exchange/util"
 
 	"github.com/gin-gonic/gin"
+	"github.com/google/uuid"
 	"golang.org/x/text/encoding/simplifiedchinese"
 	"golang.org/x/text/transform"
 )
 
-type ElevatorStatus struct {
+type A1SetFloorFrame struct {
+	Header     [16]byte // Fixed 16-byte header
+	Command    byte     // 1 byte command
+	MAC        [6]byte  // 6 bytes MAC address
+	DataLength uint16   // 2 bytes data length
+	Data       []byte   // Data area, length defined by DataLength
+	Checksum   uint16   // 2 bytes CRC16 checksum
+}
+
+// Convert frame to byte slice
+func (f *A1SetFloorFrame) toBytes(includeChecksum bool) []byte {
+	buf := new(bytes.Buffer)
+	binary.Write(buf, binary.LittleEndian, f.Header)
+	binary.Write(buf, binary.LittleEndian, f.Command)
+	binary.Write(buf, binary.LittleEndian, f.MAC)
+	binary.Write(buf, binary.LittleEndian, f.DataLength)
+	buf.Write(f.Data)
+	if includeChecksum {
+		binary.Write(buf, binary.LittleEndian, f.Checksum)
+	}
+	return buf.Bytes()
+}
+
+type A1ElevatorStatus struct {
 	TotalFloors int    `json:"TotalFloors"`
 	Floor       int    `json:"Floor"`
 	FloorName   string `json:"FloorName"`
@@ -28,15 +55,15 @@
 	Speed       string `json:"Speed"`
 }
 
-type Elevator struct {
-	Name   string         `json:"Name"`
-	IP     string         `json:"IP"`
-	Status ElevatorStatus `json:"Status"`
-	Alarm  []interface{}  `json:"Alarm"` // You might want to define a specific type for alarms
+type A1Elevator struct {
+	Name   string           `json:"Name"`
+	IP     string           `json:"IP"`
+	Status A1ElevatorStatus `json:"Status"`
+	Alarm  []interface{}    `json:"Alarm"` // You might want to define a specific type for alarms
 }
 
 type A1ElevatorData struct {
-	Elevator []Elevator `json:"Elevator"`
+	Elevator []A1Elevator `json:"Elevator"`
 }
 
 const (
@@ -44,6 +71,97 @@
 	RunUp
 	RunDown
 )
+
+var ElevatorRunState int
+var RunningCorrectTaskId string
+
+func A1CorrectFloor() {
+	if RunningCorrectTaskId != "" || config.RFIDConf.EPC == "" {
+		return
+	}
+
+	taskId := uuid.New().String()
+	RunningCorrectTaskId = taskId
+
+	rfidReader := rfid.NewReader(config.RFIDConf.DevName, 115200, 5)
+	defer rfidReader.CloseSerial()
+
+	err := rfidReader.OpenSerial()
+	if err != nil {
+		logger.Error(err.Error())
+		return
+	}
+
+	isFind, err := rfidReader.ScanSpecificEPC(config.RFIDConf.EPC, 5)
+	if isFind && RunningCorrectTaskId == taskId {
+		frame := NewA1SetFloorFrame(config.NVCSConf.Mac, config.RFIDConf.Position)
+		address := "192.168.10.253:50000"
+		err := A1SendFrame(frame, address)
+		if err != nil {
+			logger.Debug("The floor has been calibrated.")
+		} else {
+			logger.Warn(err.Error())
+		}
+	}
+}
+
+func calculateCRC16(data []byte) uint16 {
+	var crc16 uint16 = 0xFFFF
+
+	for i := 0; i < len(data); i++ {
+		crc16 ^= uint16(data[i])
+		for j := 0; j < 8; j++ {
+			if crc16&0x0001 != 0 {
+				crc16 >>= 1
+				crc16 ^= 0xA001
+			} else {
+				crc16 >>= 1
+			}
+		}
+	}
+
+	return crc16
+}
+
+// Create a new frame based on provided data
+func NewA1SetFloorFrame(macAddr string, floor uint8) *A1SetFloorFrame {
+	b, err := hex.DecodeString(macAddr)
+	if err != nil {
+		return nil
+	}
+
+	if len(b) != 6 {
+		return nil
+	}
+
+	var mac [6]byte
+	copy(mac[:], b)
+
+	//b, err = hex.DecodeString(floor)
+	frame := &A1SetFloorFrame{
+		Header:     [16]byte{0x45, 0x4c, 0x45, 0x56, 0x41, 0x54, 0x4f, 0x52, 0x2d, 0x53, 0x45, 0x4e, 0x53, 0x4f, 0x52, 0x00},
+		Command:    0x0c,
+		MAC:        mac,
+		DataLength: 1,
+		Data:       []byte{floor},
+	}
+
+	frame.Checksum = calculateCRC16(frame.toBytes(false)) // Calculate CRC without including the checksum itself
+
+	return frame
+}
+
+func A1SendFrame(frame *A1SetFloorFrame, address string) error {
+	conn, err := net.Dial("udp", address)
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	// Send frame
+	_, err = conn.Write(frame.toBytes(true))
+	return err
+}
 
 // 瀵规帴缃戠粶瑙嗛瀛楃鍙犲姞鍣�,鎺ユ敹udp鍙戦�佺殑妤煎眰淇℃伅, 鏇存柊device鍦板潃
 func NVCSA1UDPServer() {
@@ -96,36 +214,38 @@
 			continue
 		}
 
-		var runState string
-		var iRunSate int
+		elevator := data.Elevator[0]
 
 		// 璁板綍鐢垫杩愯鐘舵��
-		iRunSate = data.Elevator[0].Status.RunDir
+		ElevatorRunState = elevator.Status.RunDir
+
+		var runStateStr string
 		if config.NVCSConf.RunState {
-			if data.Elevator[0].Status.RunDir == RunUp {
-				runState = "涓�"
-			} else if data.Elevator[0].Status.RunDir == RunDown {
-				runState = "涓�"
+			if elevator.Status.RunDir == RunUp {
+				runStateStr = "涓�"
+			} else if elevator.Status.RunDir == RunDown {
+				runStateStr = "涓�"
 			}
 		}
 
 		if !config.NVCSConf.RunState {
-			runState = ""
+			runStateStr = ""
 		}
 
 		// 璁剧疆osd  鏍煎紡 "1F涓� 鍥� 鏋�"
 		if config.NVCSConf.OSD != "" {
-			floorText := fmt.Sprintf("%s%s %s", data.Elevator[0].Status.FloorName, runState, config.NVCSConf.OSD)
+			floorText := fmt.Sprintf("%s%s %s", data.Elevator[0].Status.FloorName, runStateStr, config.NVCSConf.OSD)
 
 			// 璋冪敤hik api 灏嗘枃瀛楁坊鍔犲埌osd鐨勫乏涓嬭
 			AddFloorToOSD(floorText)
 		}
 
-		if data.Elevator[0].Status.RunDir > 0 {
-			continue
+		// correct floor when elevator stopped.
+		if ElevatorRunState == 0 {
+			A1CorrectFloor()
+		} else {
+			RunningCorrectTaskId = ""
 		}
-
-		elevator := data.Elevator[0]
 
 		// 绋嬪簭閮ㄧ讲鍦ㄨ澶囩, 瀛楃鍙犲姞鍣ㄤ笂鎶ョ殑鍚嶇О鍏佽涓虹┖. 鍦ㄤ簯绔�, 鍚嶇О蹇呴』涓庢憚鍍忔満鐩稿悓
 		if elevator.Name == "" {
@@ -135,7 +255,7 @@
 		var d = models.Positions{
 			DeviceId:   elevator.Name,
 			Pos:        elevator.Status.FloorName,
-			RunDir:     iRunSate,
+			RunDir:     elevator.Status.RunDir,
 			CreateTime: time.Now().Unix(),
 			TimeString: time.Now().Format("2006-01-02 15:04:05"),
 		}
diff --git a/service/rfid.go b/service/rfid.go
new file mode 100644
index 0000000..4c546fc
--- /dev/null
+++ b/service/rfid.go
@@ -0,0 +1,5 @@
+package service
+
+type RFIDReader struct {
+	TagDetected bool // 鏄惁妫�娴嬪埌RFID鏍囩
+}

--
Gitblit v1.8.0