zhangzengfei
2024-09-13 eff59c389fd046a75bb67b45e2e52dcc712413d9
add rfid
4个文件已添加
4个文件已修改
357 ■■■■■ 已修改文件
config/config.go 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
go.mod 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
go.sum 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rfid/crc.go 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rfid/proto.go 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rfid/rw.go 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/nvcs.go 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/rfid.go 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 == "" {
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
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=
rfid/crc.go
New file
@@ -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
}
rfid/proto.go
New file
@@ -0,0 +1,5 @@
package rfid
const (
    ControlWordEPCReadResponse6C uint32 = 0x00011200
)
rfid/rw.go
New file
@@ -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
            }
            // 解析协议控制字 (仅在需要时使用)
            //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
            }
            // 解析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])
            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)
        }
    }
}
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"),
        }
service/rfid.go
New file
@@ -0,0 +1,5 @@
package service
type RFIDReader struct {
    TagDetected bool // 是否检测到RFID标签
}