zhangzengfei
2024-10-20 2fc94ba7b517125a7ed9771b48bb5d383021bc91
优化楼层数据获取
2个文件已删除
8个文件已添加
1 文件已重命名
5个文件已修改
1415 ■■■■■ 已修改文件
controller/nvcsCtl.go 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
main.go 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/a1.go 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/a2.go 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/a3.go 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/cache.go 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/nvcs.go 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/osd.go 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/queue.go 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nvcs/rfid.go 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
repository/captureRepo.go 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rfid/client.go 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rfid/proto.go 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rfid/rw.go 170 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/nvcs.go 526 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/rfid.go 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
controller/nvcsCtl.go
@@ -1,7 +1,7 @@
package controller
import (
    "gat1400Exchange/service"
    "gat1400Exchange/nvcs"
    "github.com/gin-gonic/gin"
    "net/http"
)
@@ -18,9 +18,9 @@
func (n NVCSController) RunStatus(c *gin.Context) {
    var runStatus = make(map[string]string, 0)
    runStatus["floor"] = service.ElevatorRunFloor
    runStatus["floor"] = nvcs.ElevatorRunFloor
    switch service.ElevatorRunState {
    switch nvcs.ElevatorRunState {
    case 0:
        runStatus["status"] = "停"
    case 1:
main.go
@@ -3,6 +3,7 @@
import (
    "context"
    "fmt"
    "gat1400Exchange/nvcs"
    "net/http"
    "os"
    "os/signal"
@@ -54,7 +55,7 @@
    go client.Init1400Client(ctx)
    // 启动网络视频字符叠加器服务
    go service.StartNVCSServer()
    go nvcs.StartNVCSServer()
    go service.InitSubscribeNotificationTasks()
nvcs/a1.go
New file
@@ -0,0 +1,228 @@
package nvcs
import (
    "bytes"
    "encoding/binary"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net"
    "time"
    "gat1400Exchange/config"
    "gat1400Exchange/pkg/logger"
    "gat1400Exchange/rfid"
    "github.com/google/uuid"
    "golang.org/x/text/encoding/simplifiedchinese"
    "golang.org/x/text/transform"
)
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"`
    RunDir      int    `json:"RunDir"`
    Speed       string `json:"Speed"`
}
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 []a1Elevator `json:"Elevator"`
}
var gRunningCorrectTaskId string
func a1CorrectFloor() {
    if gRunningCorrectTaskId != "" || config.RFIDConf.EPC == "" {
        return
    }
    taskId := uuid.New().String()
    gRunningCorrectTaskId = 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 && gRunningCorrectTaskId == taskId {
        frame := newA1SetFloorFrame(config.NVCSConf.Mac, config.RFIDConf.Position)
        address := "192.168.10.253:50000"
        err := a1SendFrame(frame, address)
        if err != nil {
            logger.Warn(err.Error())
        } else {
            logger.Debug("The floor has been calibrated.")
        }
    }
}
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 a1UDPServer() {
    // 指定监听的端口
    port := config.ServeConf.Port
    // 创建一个UDP地址
    address, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
    if err != nil {
        logger.Error("Error resolving address:", err)
        return
    }
    // 创建一个UDP连接
    conn, err := net.ListenUDP("udp", address)
    if err != nil {
        logger.Error("Error listening:", err)
        return
    }
    defer conn.Close()
    logger.Info("UDP server listening on port %s...", port)
    // 无限循环等待接收数据
    for {
        // 创建一个缓冲区来存储接收的数据
        buffer := make([]byte, 256)
        // 从连接中读取数据
        numBytes, clientAddr, err := conn.ReadFromUDP(buffer)
        if err != nil {
            logger.Warn("Error reading from UDP connection:%s", err.Error())
            continue
        }
        // 设备上传的中文数据为GBK编码, 转换为utf8
        decoder := simplifiedchinese.GBK.NewDecoder()
        reader := transform.NewReader(bytes.NewReader(buffer[:numBytes]), decoder)
        decodedBytes, err := ioutil.ReadAll(reader)
        var data a1ElevatorData
        err = json.Unmarshal(decodedBytes, &data)
        if err != nil {
            logger.Warn("ElevatorData unmarshal error:%s", err.Error())
            continue
        }
        logger.Debug("Received %d bytes from %s, %+v", numBytes, clientAddr, data)
        if len(data.Elevator) == 0 {
            continue
        }
        elevator := data.Elevator[0]
        // 程序部署在设备端, 字符叠加器上报的名称允许为空. 在云端, 名称必须与摄像机相同
        if elevator.Name == "" {
            elevator.Name = "1"
        }
        var runState = elevatorRunData{
            Device:    elevator.Name,
            Timestamp: time.Now().Unix(),
            Floor:     elevator.Status.FloorName,
            RunState:  elevator.Status.RunDir,
        }
        // correct floor when elevator stopped.
        //if elevator.Status.RunDir == 0 {
        //    go A1CorrectFloor()
        //} else {
        //    RunningCorrectTaskId = ""
        //}
        queue.enqueue(runState)
    }
}
nvcs/a2.go
New file
@@ -0,0 +1,122 @@
package nvcs
import (
    "encoding/json"
    "fmt"
    "net/http"
    "strings"
    "time"
    "gat1400Exchange/config"
    "gat1400Exchange/pkg/logger"
    "gat1400Exchange/util"
    "github.com/gin-gonic/gin"
)
/*
A2 款 数据上报格式
    {
        "id": "10c8a1b0051607361c",
        "State": {
        "Floor": "-1",
        "Floor_flag": "已校准",
        "JZ_flag": "已校准,",
        "JZ_i": 7,
        "Pressure": "99766",
        "Speed": "0.000",
        "Status": "停止1",
        "TFloor": 7,
        "T_acc": "0.062",
        "X_acc": "1.175",
        "Y_acc": "-1.129",
        "Z_acc": "8.344"
    }
*/
type a2ElevatorData struct {
    Id    string `json:"id"`
    State struct {
        Floor  string `json:"Floor"`
        Status string `json:"Status"`
        TFloor int64  `json:"TFloor"`
        Speed  string `json:"Speed"`
    } `json:"State"`
}
type a2ElevatorConfig struct {
    FloorData []string `json:"floordata"`
}
var a2TopFloor string
var a2BottomFloor string
func getA2ElevatorConfig() {
    url := "http://192.168.10.253/cgi-bin/liftnum.cgi"
    payload := []byte("{\"display\":1}")
    header := map[string]string{
        "Cookie": "eyJuYW1lIjoiYWRtaW4iLCAicGFzZCI6ImFkbWluMTIzIn0=",
    }
    rsp, err := util.HttpPost(url, header, payload)
    if err != nil {
        logger.Warn("Get A2 floor data failure,%s", err.Error())
        return
    }
    var configData a2ElevatorConfig
    err = json.Unmarshal(rsp, &configData)
    if err != nil {
        logger.Warn("Unmarshal A2 floor data failure,%s", err.Error())
        return
    }
    if len(configData.FloorData) > 0 {
        a2BottomFloor, a2TopFloor = configData.FloorData[0], configData.FloorData[len(configData.FloorData)-1]
    }
    logger.Info("A2 floor config total:%d, bottomFloor:%s, topFloor:%s", len(configData.FloorData), a2BottomFloor, a2TopFloor)
}
func a2WebServer() {
    // 先获取总楼层数, 记录最高层
    //GetA2ElevatorConfig()
    r := gin.Default()
    r.POST("/", func(c *gin.Context) {
        var req a2ElevatorData
        var iRunState int
        err := c.BindJSON(&req)
        if err != nil {
            c.JSON(http.StatusBadRequest, nil)
            return
        }
        logger.Debug("Received A2 report data %+v", req)
        // 记录电梯运行状态
        if strings.Contains(req.State.Status, "上") {
            iRunState = RunUp
        } else if strings.Contains(req.State.Status, "下") {
            iRunState = RunDown
        }
        var runState = elevatorRunData{
            Device:    req.Id,
            Timestamp: time.Now().Unix(),
            Floor:     req.State.Floor,
            RunState:  iRunState,
        }
        queue.enqueue(runState)
        c.JSON(http.StatusOK, "ok")
    })
    err := r.Run(fmt.Sprintf(":%s", config.NVCSConf.Port))
    if err != nil {
        logger.Warn("Start NVCS WebServer error, %s", err.Error())
    }
}
nvcs/a3.go
New file
@@ -0,0 +1,57 @@
package nvcs
import (
    "fmt"
    "net/http"
    "time"
    "gat1400Exchange/config"
    "gat1400Exchange/pkg/logger"
    "github.com/gin-gonic/gin"
)
/*
485平层传感器 + 485转以太网模块
*/
type a3ElevatorData struct {
    Id     string `json:"id"`
    Time   string `json:"time"`
    Total  int    `json:"total"`
    Status int    `json:"status"`
    Floor  int    `json:"floor"`
}
func a3WebServer() {
    r := gin.Default()
    r.POST("/", func(c *gin.Context) {
        var req a3ElevatorData
        err := c.BindJSON(&req)
        if err != nil {
            c.JSON(http.StatusBadRequest, nil)
            return
        }
        logger.Debug("Received A3 report data %+v", req)
        floor := fmt.Sprintf("%dF", req.Floor)
        var runState = elevatorRunData{
            Device:    req.Id,
            Timestamp: time.Now().Unix(),
            Floor:     floor,
            RunState:  req.Status,
        }
        queue.enqueue(runState)
        c.JSON(http.StatusOK, "ok")
    })
    err := r.Run(fmt.Sprintf(":%s", config.NVCSConf.Port))
    if err != nil {
        logger.Warn("Start NVCS WebServer error, %s", err.Error())
    }
}
nvcs/cache.go
New file
@@ -0,0 +1,85 @@
package nvcs
import (
    "fmt"
    "time"
    "gat1400Exchange/config"
    "gat1400Exchange/models"
    "gat1400Exchange/pkg/logger"
)
type simpleCache struct {
    data       map[int64]elevatorRunData
    expiration time.Duration
}
func newCache(expiration time.Duration) *simpleCache {
    return &simpleCache{
        data:       make(map[int64]elevatorRunData),
        expiration: expiration,
    }
}
// 存储数据到缓存中
func (c *simpleCache) store(data elevatorRunData) {
    if config.RFIDConf.ReadFloor && gRFIDFloor != data.Floor {
        if data.RunState == RunStop {
            logger.Warn("A floor error has occurred rfid epc %s, nvcs floor %s", gRFIDFloor, data.Floor)
        }
        data.Floor = gRFIDFloor
    }
    // 如果楼层相同,并且数据在1秒内,则忽略
    if existingData, ok := c.data[data.Timestamp]; ok {
        if existingData.Floor == data.Floor {
            return
        }
    }
    // 数据库保存一份
    go func() {
        var d = models.Positions{
            DeviceId:   data.Device,
            Pos:        data.Floor,
            RunDir:     data.RunState,
            CreateTime: time.Now().Unix(),
            TimeString: time.Now().Format("2006-01-02 15:04:05"),
        }
        err := d.Save()
        if err != nil {
            logger.Warn("Device position update error:%s", err.Error())
        }
    }()
    // 写OSD
    var runStateStr string
    if config.NVCSConf.RunState {
        if data.RunState == RunUp {
            runStateStr = "上"
        } else if data.RunState == RunDown {
            runStateStr = "下"
        }
    }
    // 设置osd  格式 "1F上 固 枪"
    if config.NVCSConf.OSD != "" {
        floorText := fmt.Sprintf("%s%s %s", data.Floor, runStateStr, config.NVCSConf.OSD)
        // 调用hik api 将文字添加到osd的左下角
        go addFloorToOSD(floorText)
    }
    c.data[data.Timestamp] = data
}
// 删除过期数据
func (c *simpleCache) cleanExpired() {
    for t := range c.data {
        if time.Since(time.Unix(t, 0)) > c.expiration {
            delete(c.data, t)
        }
    }
}
nvcs/nvcs.go
New file
@@ -0,0 +1,55 @@
package nvcs
import (
    "context"
    "gat1400Exchange/config"
    "time"
)
const (
    RunStop = iota
    RunUp
    RunDown
)
// 数据结构
type elevatorRunData struct {
    Device    string
    Timestamp int64
    Floor     string
    RunState  int
}
var queue = newChQueue(100)
var cache = newCache(5 * time.Minute)
func StartNVCSServer(ctx context.Context) {
    go listenQueue()
    if config.RFIDConf.ReadFloor {
        go readRFID(ctx)
    }
    if config.NVCSConf.Model == "A1" {
        go a1UDPServer()
    }
    if config.NVCSConf.Model == "A2" {
        go a2WebServer()
    }
    if config.NVCSConf.Model == "A3" {
        go a3WebServer()
    }
}
func listenQueue() {
    for {
        data := queue.dequeue()
        cache.store(data)
        // 清理过期数据
        cache.cleanExpired()
    }
}
nvcs/osd.go
File was renamed from service/osd.go
@@ -1,24 +1,26 @@
package service
package nvcs
import (
    "encoding/xml"
    "gat1400Exchange/pkg/logger"
    dac "github.com/xinsnake/go-http-digest-auth-client"
    "io/ioutil"
    "gat1400Exchange/pkg/logger"
    dac "github.com/xinsnake/go-http-digest-auth-client"
)
// 设置其他字符的第一个, 位置固定位7
// 摄像机信息暂时固定, 避免泄露
const (
    HikISAPIOverlaySetUrl = "/ISAPI/System/Video/inputs/channels/1/overlays/text/7"
    CameraWebAddr         = "http://192.168.10.11:40080"
    CameraWebUser         = "admin"
    CameraWebPassword     = "a1234567"
    hikISAPIOverlay7SetUrl = "/ISAPI/System/Video/inputs/channels/1/overlays/text/7"
    cameraWebAddr          = "http://192.168.10.11:40080"
    cameraWebUser          = "admin"
    cameraWebPassword      = "a1234567"
)
var overlayText TextOverlay
var overlayText textOverlay
type TextOverlay struct {
type textOverlay struct {
    Id               int64  `xml:"id"`
    Enabled          bool   `xml:"enabled"`
    PositionX        int64  `xml:"positionX"`
@@ -28,12 +30,24 @@
    IsPersistentText bool   `xml:"isPersistentText"`
}
func AddFloorToOSD(osdText string) {
    overlay7Url := CameraWebAddr + HikISAPIOverlaySetUrl
func hikISAPIRequest(username, password, method, url, body string) ([]byte, error) {
    dr := dac.NewRequest(username, password, method, url, body)
    resp, err := dr.Execute()
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return ioutil.ReadAll(resp.Body)
}
func addFloorToOSD(osdText string) {
    overlay7Url := cameraWebAddr + hikISAPIOverlay7SetUrl
    // 获取左下角第一个字符的位置
    if overlayText.DisplayText == "" {
        rsp, err := ISAPIRequest(CameraWebUser, CameraWebPassword, "GET", overlay7Url, "")
        rsp, err := hikISAPIRequest(cameraWebUser, cameraWebPassword, "GET", overlay7Url, "")
        if err != nil {
            logger.Warn("Get osd info failure")
            return
@@ -48,21 +62,9 @@
    overlayText.DisplayText = osdText
    body, _ := xml.Marshal(overlayText)
    _, err := ISAPIRequest(CameraWebUser, CameraWebPassword, "PUT", overlay7Url, string(body))
    _, err := hikISAPIRequest(cameraWebUser, cameraWebPassword, "PUT", overlay7Url, string(body))
    if err != nil {
        logger.Warn("Camera osd set failure!!")
        return
    }
}
func ISAPIRequest(username, password, method, url, body string) ([]byte, error) {
    dr := dac.NewRequest(username, password, method, url, body)
    resp, err := dr.Execute()
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return ioutil.ReadAll(resp.Body)
}
nvcs/queue.go
New file
@@ -0,0 +1,20 @@
package nvcs
type chQueue struct {
    data chan elevatorRunData
}
func newChQueue(size int) *chQueue {
    return &chQueue{
        data: make(chan elevatorRunData, size),
    }
}
func (q *chQueue) enqueue(data elevatorRunData) {
    q.data <- data
}
func (q *chQueue) dequeue() elevatorRunData {
    data := <-q.data
    return data
}
nvcs/rfid.go
New file
@@ -0,0 +1,49 @@
package nvcs
import (
    "context"
    "time"
    "gat1400Exchange/config"
    "gat1400Exchange/pkg/logger"
    "gat1400Exchange/rfid"
)
var gRFIDFloor string
func readRFID(ctx context.Context) {
    rfidReader := rfid.NewReader(config.RFIDConf.DevName, 115200, 5)
    defer rfidReader.CloseSerial()
    err := rfidReader.OpenSerial()
    if err != nil {
        logger.Error("open rfid. %s", err.Error())
        return
    }
    go func() {
        err := rfidReader.ReadEPCData(ctx)
        if err != nil {
            logger.Error("rfid read. %s", err.Error())
        }
    }()
    for {
        select {
        case <-ctx.Done():
            return
        default:
            if rfidReader.Alive {
                floor, _ := rfid.Parse2Floor(rfidReader.EPCData)
                if floor != gRFIDFloor {
                    gRFIDFloor = floor
                    logger.Debug("rfid read epc floor %s", gRFIDFloor)
                }
            }
            time.Sleep(200 * time.Millisecond)
        }
    }
}
repository/captureRepo.go
@@ -3,6 +3,7 @@
import (
    "encoding/base64"
    "encoding/json"
    "gat1400Exchange/nvcs"
    "time"
    "gat1400Exchange/client"
@@ -10,7 +11,6 @@
    "gat1400Exchange/models"
    "gat1400Exchange/pkg"
    "gat1400Exchange/pkg/logger"
    "gat1400Exchange/service"
    "gat1400Exchange/util"
    "gat1400Exchange/vo"
)
@@ -224,11 +224,11 @@
            var dbPos models.Positions
            if err := dbPos.FindMovePosition(faceAppearTime.Unix()+5, floor); err == nil {
                switch dbPos.RunDir {
                case service.RunUp:
                case nvcs.RunUp:
                    runDir = "in"
                case service.RunDown:
                case nvcs.RunDown:
                    runDir = "out"
                case service.RunStop:
                case nvcs.RunStop:
                    runDir = ""
                }
@@ -291,11 +291,11 @@
            var dbPos models.Positions
            if err := dbPos.FindMovePosition(faceAppearTime.Unix()+5, floor); err == nil {
                switch dbPos.RunDir {
                case service.RunUp:
                case nvcs.RunUp:
                    runDir = "1"
                case service.RunDown:
                case nvcs.RunDown:
                    runDir = "2"
                case service.RunStop:
                case nvcs.RunStop:
                    runDir = "0"
                }
rfid/client.go
New file
@@ -0,0 +1,21 @@
package rfid
import (
    "time"
    "github.com/tarm/serial"
)
func OpenSerial(devName string, baud, timeout int) (*serial.Port, error){
    config := &serial.Config{
        Name: devName,
        Baud: baud,
        ReadTimeout: time.Duration(timeout) * time.Second,
    }
    return serial.OpenPort(config)
}
func CloseSerial(port *serial.Port) error {
    return port.Close()
}
rfid/proto.go
@@ -9,6 +9,8 @@
const (
    ControlWordEPCReadResponse6C uint32 = 0x00011200
    ControlWordConnectionStatus  uint32 = 0x00011112
    ControlWordDeviceInfo        uint32 = 0x00010100
)
func Parse2Floor(input string) (string, error) {
rfid/rw.go
@@ -13,18 +13,21 @@
func NewReader(devName string, baud, readDuration int) *Reader {
    return &Reader{
        DevName:      devName,
        Baud:         baud,
        ReadDuration: readDuration,
        DevName:       devName,
        Baud:          baud,
        ReadTimeout:   readDuration,
        Alive:         true,
        HeartbeatTime: time.Now().Unix(),
    }
}
type Reader struct {
    DevName       string
    Baud          int
    ReadDuration  int
    EPCData       string
    ReadTimestamp int64
    ReadTimeout   int
    Alive         bool
    HeartbeatTime int64
    DevPort       *serial.Port
}
@@ -81,7 +84,7 @@
func (r *Reader) ReadResponse() (int, error) {
    buf := make([]byte, 1024) // 根据协议最大数据长度调整缓冲区
    n, err := r.DevPort.Read(buf)
    fmt.Printf("Recive message %x\n", buf[:n])
    fmt.Printf("Recive message %X\n", buf[:n])
    return n, err
}
@@ -94,7 +97,7 @@
    defer r.StopAutoRead()
    stop := time.After(time.Duration(r.ReadDuration) * time.Second)
    stop := time.After(time.Duration(r.ReadTimeout) * time.Second)
    // 根据协议最大数据长度调整缓冲区
    buf := make([]byte, 1024)
@@ -121,7 +124,7 @@
            if buf[0] != 0x5A {
                continue // 忽略错误帧
            }
            fmt.Printf("Recive message %x\n", buf[:n]) // 打印协议控制字进行调试
            fmt.Printf("Recive message %X\n", buf[:n]) // 打印协议控制字进行调试
            // 校验CRC
            //fmt.Printf("Crc %x\n",buf[n-2 : n])
@@ -147,7 +150,7 @@
            // 回调传送epc数据
            //callBack(buf[9 : 9+epcLength])
            epcData := fmt.Sprintf("%x", buf[9:9+epcLength])
            epcData := fmt.Sprintf("%X", buf[9:9+epcLength])
            if epcData == target {
                scanCount++
                if scanCount > minCount {
@@ -165,6 +168,20 @@
    if err != nil {
        return err
    }
    go func() {
        for {
            err := r.SendAck()
            if err != nil {
                fmt.Println("send ack.", err.Error())
            }
            time.Sleep(30 * time.Second)
            if time.Now().Unix()-r.HeartbeatTime > 120 {
                r.Alive = false
            }
        }
    }()
    // 根据协议最大数据长度调整缓冲区
    buf := make([]byte, 1024)
@@ -190,10 +207,10 @@
            if buf[0] != 0x5A {
                continue // 忽略错误帧
            }
            fmt.Printf("Read message %x\n", buf[:n]) // 打印协议控制字进行调试
            fmt.Printf("Read 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) {
@@ -201,25 +218,26 @@
                continue
            }
            // 解析协议控制字 (仅在需要时使用)
            //fmt.Printf("Control Word: %x\n", buf[1:5]) // 打印协议控制字进行调试
            r.HeartbeatTime = time.Now().Unix()
            r.Alive = true
            // 解析协议控制字
            controlWord := binary.BigEndian.Uint32(buf[1:5])
            if controlWord != ControlWordEPCReadResponse6C {
                fmt.Printf("Control Word: %d, rec word %d\n", ControlWordEPCReadResponse6C, controlWord)
                continue
            switch controlWord {
            case ControlWordDeviceInfo:
                parseDeviceInfo(buf)
            case ControlWordEPCReadResponse6C:
                // 解析EPC数据长度
                epcLength := binary.BigEndian.Uint16(buf[7:9])
                r.EPCData = fmt.Sprintf("%X", buf[9:9+epcLength])
                // 回调传送epc数据
                //callBack(buf[9 : 9+epcLength])
                fmt.Printf("Read epc %s\n", r.EPCData)
            }
            // 解析EPC数据长度
            epcLength := binary.BigEndian.Uint16(buf[7:9])
            //fmt.Printf("EPC length %d, EPC %x \n", epcLength, buf[9:9+epcLength])
            // 回调传送epc数据
            //callBack(buf[9 : 9+epcLength])
            r.EPCData = fmt.Sprintf("%X", buf[9:9+epcLength])
            r.ReadTimestamp = time.Now().Unix()
            fmt.Printf("read epc %s\n", r.EPCData)
        }
    }
}
@@ -230,6 +248,41 @@
    _, err := r.DevPort.Write(data)
    if err != nil {
        return err
    }
    // todo parse response
    r.ReadResponse()
    return nil
}
func (r *Reader) SendAck() error {
    cmd := "5A000101000000DCE5"
    //cmd := "5A000111120004000000015FFB"
    data, _ := hex.DecodeString(cmd)
    _, err := r.DevPort.Write(data)
    return err
}
func (r *Reader) SetPower(val int) error {
    if val < 10 || val > 33 {
        return fmt.Errorf("value out of range, must be between 0 and 255")
    }
    frame := []byte{0x5A, 0x00, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01}
    frame = append(frame, byte(val))
    crc := CRC16XMODEM(frame[1:])
    crcBytes := make([]byte, 2)
    binary.BigEndian.PutUint16(crcBytes, crc)
    frame = append(frame, crcBytes...)
    fmt.Printf("set power %d, cmd %2X\n", val, frame)
    _, err := r.DevPort.Write(frame)
    if err != nil {
        return nil
    }
@@ -239,18 +292,71 @@
    return err
}
func (r *Reader) SetPower20() error {
    cmd := "5A0001020100020132D230"
func (r *Reader) SetBuzzer(enable bool) error {
    cmd := "5A0001011E000100653D"
    if !enable {
        cmd = "5A0001011E000101751C"
    }
    data, _ := hex.DecodeString(cmd)
    _, err := r.DevPort.Write(data)
    if err != nil {
        return nil
        return err
    }
    // todo parse response
    r.ReadResponse()
    return err
    return nil
}
func parseDeviceInfo(bytes []byte) {
    // 初始化解析的偏移量
    offset := 1
    // 解析协议控制字
    controlWord := bytes[offset : offset+4]
    offset += 4
    // 解析数据长度
    dataLength := int(bytes[offset])*256 + int(bytes[offset+1])
    offset += 2
    // 解析读写器序列号
    serialNumberLen := int(bytes[offset])*256 + int(bytes[offset+1])
    offset += 2
    serialNumber := bytes[offset : offset+serialNumberLen]
    offset += serialNumberLen
    // 解析上电时间(4字节)
    upTimeBytes := bytes[offset : offset+4]
    offset += 4
    // 解析基带编译时间长度(2字节)
    baseBandTimeLen := int(bytes[offset])*256 + int(bytes[offset+1])
    offset += 2
    // 解析基带编译时间(根据长度字段解析)
    baseBandTime := bytes[offset : offset+baseBandTimeLen]
    offset += baseBandTimeLen
    // 解析应用软件版本(4字节)
    appVer := bytes[offset : offset+4]
    offset += 4
    // 将字节数组转换为可读字符串
    serialNumberStr := string(serialNumber)
    baseBandTimeStr := string(baseBandTime)
    appVerStr := fmt.Sprintf("%d.%d.%d.%d", appVer[0], appVer[1], appVer[2], appVer[3])
    // 解析上电时间(将字节数组转为整数)
    upTime := int(upTimeBytes[0])<<24 + int(upTimeBytes[1])<<16 + int(upTimeBytes[2])<<8 + int(upTimeBytes[3])
    // 打印结果
    fmt.Printf("协议控制字: %X\n", controlWord)
    fmt.Printf("数据长度: %d\n", dataLength)
    fmt.Printf("读写器序列号: %s\n", serialNumberStr)
    fmt.Printf("读写器上电时间: %d秒\n", upTime)
    fmt.Printf("基带编译时间: %s\n", baseBandTimeStr)
    fmt.Printf("应用软件版本: %s\n", appVerStr)
}
service/nvcs.go
File was deleted
service/rfid.go
File was deleted