diff --git a/common/common_config.go b/common/common_config.go index b5300ff0b..9cf10a5d0 100644 --- a/common/common_config.go +++ b/common/common_config.go @@ -6,8 +6,9 @@ package common * */ type HostConfig struct { - Host string `json:"host" validate:"required" title:"服务地址"` - Port int `json:"port" validate:"required" title:"服务端口"` + Host string `json:"host" validate:"required" title:"服务地址"` + Port int `json:"port" validate:"required" title:"服务端口"` + Timeout int `json:"timeout"` } /* diff --git a/device/custom_protocol_device.go b/device/custom_protocol_device.go index eb5f5aeeb..1b59d36cf 100644 --- a/device/custom_protocol_device.go +++ b/device/custom_protocol_device.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "net" "time" "github.com/hootrhino/rulex/common" @@ -19,28 +20,14 @@ import ( const __DEFAULT_BUFFER_SIZE = 100 // 传输形式: -// `rawtcp`, `rawudp`, `rs485rawserial`, `rs485rawtcp` -// const rawtcp string = "rawtcp" -// const rawudp string = "rawudp" -// const rs485rawserial string = "rs485rawserial" -// const rs485rawtcp string = "rs485rawtcp" +// `rawtcp`, `rawudp`, `rawserial` +const rawtcp string = "rawtcp" +const rawudp string = "rawudp" +const rawserial string = "rawserial" type _CPDCommonConfig struct { - Transport *string `json:"transport" validate:"required"` // 传输协议 - RetryTime *int `json:"retryTime" validate:"required"` // 几次以后重启,0 表示不重启 -} - -type _CPDProtocol struct { - //--------------------------------------------------------------------- - // 下面都是校验算法相关配置: - // -- 例如对[Byte1,Byte2,Byte3,Byte4,Byte5,Byte6,Byte7]用XOR算法比对 - // 从第一个开始,第五个结束[Byte1,Byte2,Byte3,Byte4,Byte5], 比对值位置在第六个[Byte6] - // 伪代码:XOR(Byte[ChecksumBegin:ChecksumEnd]) == Byte[ChecksumValuePos] - //--------------------------------------------------------------------- - CheckAlgorithm string `json:"checkAlgorithm" validate:"required" default:"NONECHECK"` // 校验算法,目前暂时支持: CRC16, XOR - ChecksumValuePos uint `json:"checksumValuePos" validate:"required"` // 校验值比对位 - ChecksumBegin uint `json:"checksumBegin" validate:"required"` // 校验算法起始位置 - ChecksumEnd uint `json:"checksumEnd" validate:"required"` // 校验算法结束位置 + Transport string `json:"transport" validate:"required"` // 传输协议 + RetryTime int `json:"retryTime" validate:"required"` // 几次以后重启,0 表示不重启 } /* @@ -51,12 +38,14 @@ type _CPDProtocol struct { type _CustomProtocolConfig struct { CommonConfig _CPDCommonConfig `json:"commonConfig" validate:"required"` UartConfig common.CommonUartConfig `json:"uartConfig" validate:"required"` + HostConfig common.HostConfig `json:"hostConfig" validate:"required"` } type CustomProtocolDevice struct { typex.XStatus status typex.DeviceState RuleEngine typex.RuleX - serialPort *serial.Port // 现阶段暂时支持串口 + serialPort *serial.Port // 串口 + tcpcon net.Conn // TCP mainConfig _CustomProtocolConfig errorCount int // 记录最大容错数,默认5次,出错超过5此就重启 } @@ -67,6 +56,7 @@ func NewCustomProtocolDevice(e typex.RuleX) typex.XDevice { mdev.mainConfig = _CustomProtocolConfig{ CommonConfig: _CPDCommonConfig{}, UartConfig: common.CommonUartConfig{}, + HostConfig: common.HostConfig{Timeout: 50}, } return mdev @@ -81,9 +71,9 @@ func (mdev *CustomProtocolDevice) Init(devId string, configMap map[string]interf if !utils.SContains([]string{"N", "E", "O"}, mdev.mainConfig.UartConfig.Parity) { return errors.New("parity value only one of 'N','O','E'") } - if !utils.SContains([]string{`rawtcp`, `rawudp`, `rs485rawserial`, `rs485rawtcp`}, - *mdev.mainConfig.CommonConfig.Transport) { - return errors.New("option only one of 'rawtcp','rawudp','rs485rawserial','rs485rawserial'") + if !utils.SContains([]string{`rawtcp`, `rawudp`, `rawserial`}, + mdev.mainConfig.CommonConfig.Transport) { + return errors.New("option only one of 'rawtcp','rawudp','rawserial','rawserial'") } return nil } @@ -92,8 +82,11 @@ func (mdev *CustomProtocolDevice) Init(devId string, configMap map[string]interf func (mdev *CustomProtocolDevice) Start(cctx typex.CCTX) error { mdev.Ctx = cctx.Ctx mdev.CancelCTX = cctx.CancelCTX + mdev.errorCount = 0 + mdev.status = typex.DEV_DOWN + // 现阶段暂时只支持RS485串口, 以后有需求再支持TCP、UDP - if *mdev.mainConfig.CommonConfig.Transport == "rs485rawserial" { + if mdev.mainConfig.CommonConfig.Transport == "rawserial" { config := serial.Config{ Name: mdev.mainConfig.UartConfig.Uart, Baud: mdev.mainConfig.UartConfig.BaudRate, @@ -107,13 +100,25 @@ func (mdev *CustomProtocolDevice) Start(cctx typex.CCTX) error { glogger.GLogger.Error("serialPort start failed:", err) return err } - mdev.errorCount = 0 mdev.serialPort = serialPort mdev.status = typex.DEV_UP return nil } - return fmt.Errorf("unsupported transport:%s", *mdev.mainConfig.CommonConfig.Transport) + // rawtcp + if mdev.mainConfig.CommonConfig.Transport == "rawtcp" { + tcpcon, err := net.Dial("tcp", + fmt.Sprintf("%s:%d", mdev.mainConfig.HostConfig.Host, + mdev.mainConfig.HostConfig.Port)) + if err != nil { + glogger.GLogger.Error("tcp connection start failed:", err) + return err + } + mdev.tcpcon = tcpcon + mdev.status = typex.DEV_UP + return nil + } + return fmt.Errorf("unsupported transport:%s", mdev.mainConfig.CommonConfig.Transport) } /* @@ -149,14 +154,9 @@ func (mdev *CustomProtocolDevice) OnCtrl(cmd []byte, _ []byte) ([]byte, error) { // 设备当前状态 func (mdev *CustomProtocolDevice) Status() typex.DeviceState { - if *mdev.mainConfig.CommonConfig.RetryTime == 0 { - mdev.status = typex.DEV_UP - } - if *mdev.mainConfig.CommonConfig.RetryTime > 0 { - if mdev.errorCount >= *mdev.mainConfig.CommonConfig.RetryTime { - mdev.CancelCTX() - mdev.status = typex.DEV_DOWN - } + if mdev.errorCount >= mdev.mainConfig.CommonConfig.RetryTime { + mdev.CancelCTX() + mdev.status = typex.DEV_DOWN } return mdev.status } @@ -165,8 +165,16 @@ func (mdev *CustomProtocolDevice) Status() typex.DeviceState { func (mdev *CustomProtocolDevice) Stop() { mdev.CancelCTX() mdev.status = typex.DEV_DOWN - mdev.serialPort.Close() - + if mdev.mainConfig.CommonConfig.Transport == rawtcp { + if mdev.tcpcon != nil { + mdev.tcpcon.Close() + } + } + if mdev.mainConfig.CommonConfig.Transport == rawserial { + if mdev.serialPort != nil { + mdev.serialPort.Close() + } + } } // 设备属性,是一系列属性描述 @@ -213,14 +221,28 @@ func (mdev *CustomProtocolDevice) ctrl(args []byte) ([]byte, error) { result := [__DEFAULT_BUFFER_SIZE]byte{} ctx, cancel := context.WithTimeout(context.Background(), time.Duration(mdev.mainConfig.UartConfig.Timeout)*time.Millisecond) - count, err2 := utils.SliceRequest(ctx, mdev.serialPort, - hexs, result[:], false, - time.Duration(30)*time.Millisecond /*30ms wait*/) + var count int = 0 + var errSliceRequest error = nil + if mdev.mainConfig.CommonConfig.Transport == rawserial { + count, errSliceRequest = utils.SliceRequest(ctx, mdev.serialPort, + hexs, result[:], false, + time.Duration(30)*time.Millisecond /*30ms wait*/) + } + if mdev.mainConfig.CommonConfig.Transport == rawtcp { + mdev.tcpcon.SetReadDeadline( + time.Now().Add((time.Duration(mdev.mainConfig.HostConfig.Timeout) * time.Millisecond)), + ) + count, errSliceRequest = utils.SliceRequest(ctx, mdev.tcpcon, + hexs, result[:], false, + time.Duration(30)*time.Millisecond /*30ms wait*/) + mdev.tcpcon.SetReadDeadline(time.Time{}) + } + cancel() - if err2 != nil { - glogger.GLogger.Error("Custom Protocol Device Request error: ", err2) + if errSliceRequest != nil { + glogger.GLogger.Error("Custom Protocol Device Request error: ", errSliceRequest) mdev.errorCount++ - return nil, err2 + return nil, errSliceRequest } dataMap := map[string]string{} dataMap["in"] = string(args) @@ -228,38 +250,3 @@ func (mdev *CustomProtocolDevice) ctrl(args []byte) ([]byte, error) { bytes, _ := json.Marshal(dataMap) return []byte(bytes), nil } - -// func (mdev *CustomProtocolDevice) checkXOR(b []byte, v int) bool { -// return utils.XOR(b) == v -// } -// func (mdev *CustomProtocolDevice) checkCRC(b []byte, v int) bool { - -// return int(utils.CRC16(b)) == v -// } - -// /* -// * -// * Check hex string -// * -// */ -// func (mdev *CustomProtocolDevice) checkHexs(p _CPDProtocol, result []byte) bool { -// checkOk := false -// if p.CheckAlgorithm == "CRC16" || p.CheckAlgorithm == "crc16" { -// glogger.GLogger.Debug("checkCRC:", result[:p.BufferSize], -// int(result[:p.BufferSize][p.ChecksumValuePos])) -// checkOk = mdev.checkCRC(result[:p.BufferSize], -// int(result[:p.BufferSize][p.ChecksumValuePos])) -// } -// // -// if p.CheckAlgorithm == "XOR" || p.CheckAlgorithm == "xor" { -// glogger.GLogger.Debug("checkCRC:", result[:p.BufferSize], -// int(result[:p.BufferSize][p.ChecksumValuePos])) -// checkOk = mdev.checkXOR(result[:p.BufferSize], -// int(result[:p.BufferSize][p.ChecksumValuePos])) -// } -// // NONECHECK: 不校验 -// if p.CheckAlgorithm == "NONECHECK" { -// checkOk = true -// } -// return checkOk -// } diff --git a/device/custom_protocol_device.md b/device/custom_protocol_device.md index 3f7b3b124..8401aef33 100644 --- a/device/custom_protocol_device.md +++ b/device/custom_protocol_device.md @@ -6,29 +6,50 @@ 协议分静态协议和动态协议,下面是动态协议示例,一般会有至少一个自定义协议,关键字段是 `deviceConfig` ,下面给出一个 JSON 样例: ### 动态协议 - -```json -{ - "name":"GENERIC_PROTOCOL", - "type":"GENERIC_PROTOCOL", - "description":"GENERIC_PROTOCOL", - "config":{ - "commonConfig":{ - "transport":"rs485rawserial", - "retryTime":5, - "frequency":100 - }, - "uartConfig":{ - "timeout":1000, - "baudRate":9600, - "dataBits":8, - "parity":"N", - "stopBits":1, - "uart":"COM5" +动态协议有2种,分别是串口和TCP,用`transport`字段区分。 +- 当 `transport`是 `rawserial` 的时候表示串口 + ```json + { + "name":"GENERIC_PROTOCOL", + "type":"GENERIC_PROTOCOL", + "description":"GENERIC_PROTOCOL", + "config":{ + "commonConfig":{ + "transport":"rawserial", + "retryTime":5, + "frequency":100 + }, + "uartConfig":{ + "timeout":1000, + "baudRate":9600, + "dataBits":8, + "parity":"N", + "stopBits":1, + "uart":"COM5" + } } } -} -``` + ``` +- 当 `transport`是 `rawtcp` 的时候表示自定义TCP + ```json + { + "name":"GENERIC_PROTOCOL", + "type":"GENERIC_PROTOCOL", + "description":"GENERIC_PROTOCOL", + "config":{ + "commonConfig":{ + "transport":"rawtcp", + "retryTime":5, + "frequency":100 + }, + "hostConfig":{ + "host": "192.168.1.1", + "port": 4455, + } + } + } + ``` + ## 字段: @@ -127,4 +148,90 @@ end ```lua local V, err = rulexlib:CDAB("ABCDEF12") -- V = CDAB12EF - ``` \ No newline at end of file + ``` + +## 自定义TCP示例 +### 设备模拟器 +```go +package main + +import ( + "fmt" + "net" +) + +func StartCustomTCPServer() { + listener, err := net.Listen("tcp", ":3399") + if err != nil { + fmt.Println("Error listening:", err) + return + } + fmt.Println("listening:", listener.Addr().String()) + + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting:", err) + continue + } + go handleConnection(conn) + } +} + +func handleConnection(conn net.Conn) { + defer conn.Close() + data := make([]byte, 10) + for { + n, err := conn.Read(data) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Received Request From Custom TCP:", data[:n]) + if n > 0 { + if data[0] == 1 { + conn.Write([]byte{0x01}) + } + if data[0] == 2 { + conn.Write([]byte{0x02, 0x03, 0x04}) + } + if data[0] == 3 { + conn.Write([]byte{0x0A, 0x0B, 0x0C, 0x0D}) + } + if data[0] == 4 { + conn.Write([]byte{0x11, 0x22, 0x33, 0x44, 0x55}) + } + if data[0] == 5 { + conn.Write([]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}) + } + } + } + +} + +func main() { + StartCustomTCPServer() +} + +``` +### RULEX APP Demo +```lua +AppNAME = "Test1" +AppVERSION = "1.0.0" +AppDESCRIPTION = "" +-- +-- Main +-- + +function Main(arg) + while true do + for i = 1, 5, 1 do + local result, err = applib:CtrlDevice('uuid', "0" .. i) + print("|*** CtrlDevice [0x01] result=>", result, err) + applib:Sleep(1000) + end + end + return 0 +end + +``` \ No newline at end of file diff --git a/device/decoder/h264decoder.go b/device/decoder/h264decoder.go new file mode 100644 index 000000000..22dfa6374 --- /dev/null +++ b/device/decoder/h264decoder.go @@ -0,0 +1,140 @@ +package decoder + +import ( + "fmt" + "image" + "unsafe" +) + +// #cgo pkg-config: libavcodec libavutil libswscale +// #include +// #include +// #include +import "C" + +func FrameData(frame *C.AVFrame) **C.uint8_t { + return (**C.uint8_t)(unsafe.Pointer(&frame.data[0])) +} + +func FrameLineSize(frame *C.AVFrame) *C.int { + return (*C.int)(unsafe.Pointer(&frame.linesize[0])) +} + +// h264Decoder is a wrapper around ffmpeg's H264 decoder. +type h264Decoder struct { + codecCtx *C.AVCodecContext + srcFrame *C.AVFrame + swsCtx *C.struct_SwsContext + dstFrame *C.AVFrame + dstFramePtr []uint8 +} + +// newH264Decoder allocates a new h264Decoder. +func NewH264Decoder() (*h264Decoder, error) { + codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H264) + if codec == nil { + return nil, fmt.Errorf("avcodec_find_decoder() failed") + } + + codecCtx := C.avcodec_alloc_context3(codec) + if codecCtx == nil { + return nil, fmt.Errorf("avcodec_alloc_context3() failed") + } + + res := C.avcodec_open2(codecCtx, codec, nil) + if res < 0 { + C.avcodec_close(codecCtx) + return nil, fmt.Errorf("avcodec_open2() failed") + } + + srcFrame := C.av_frame_alloc() + if srcFrame == nil { + C.avcodec_close(codecCtx) + return nil, fmt.Errorf("av_frame_alloc() failed") + } + + return &h264Decoder{ + codecCtx: codecCtx, + srcFrame: srcFrame, + }, nil +} + +// close closes the decoder. +func (d *h264Decoder) Close() { + if d.dstFrame != nil { + C.av_frame_free(&d.dstFrame) + } + + if d.swsCtx != nil { + C.sws_freeContext(d.swsCtx) + } + + C.av_frame_free(&d.srcFrame) + C.avcodec_close(d.codecCtx) +} + +func (d *h264Decoder) Decode(nalu []byte) (image.Image, error) { + nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...) + + // send frame to decoder + var avPacket C.AVPacket + avPacket.data = (*C.uint8_t)(C.CBytes(nalu)) + defer C.free(unsafe.Pointer(avPacket.data)) + avPacket.size = C.int(len(nalu)) + res := C.avcodec_send_packet(d.codecCtx, &avPacket) + if res < 0 { + return nil, nil + } + + // receive frame if available + res = C.avcodec_receive_frame(d.codecCtx, d.srcFrame) + if res < 0 { + return nil, nil + } + + // if frame size has changed, allocate needed objects + if d.dstFrame == nil || d.dstFrame.width != d.srcFrame.width || d.dstFrame.height != d.srcFrame.height { + if d.dstFrame != nil { + C.av_frame_free(&d.dstFrame) + } + + if d.swsCtx != nil { + C.sws_freeContext(d.swsCtx) + } + + d.dstFrame = C.av_frame_alloc() + d.dstFrame.format = C.AV_PIX_FMT_RGBA + d.dstFrame.width = d.srcFrame.width + d.dstFrame.height = d.srcFrame.height + d.dstFrame.color_range = C.AVCOL_RANGE_JPEG + res = C.av_frame_get_buffer(d.dstFrame, 1) + if res < 0 { + return nil, fmt.Errorf("av_frame_get_buffer() err") + } + + d.swsCtx = C.sws_getContext(d.srcFrame.width, d.srcFrame.height, C.AV_PIX_FMT_YUV420P, + d.dstFrame.width, d.dstFrame.height, (int32)(d.dstFrame.format), C.SWS_BILINEAR, nil, nil, nil) + if d.swsCtx == nil { + return nil, fmt.Errorf("sws_getContext() err") + } + + dstFrameSize := C.av_image_get_buffer_size((int32)(d.dstFrame.format), d.dstFrame.width, d.dstFrame.height, 1) + d.dstFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.dstFrame.data[0]))[:dstFrameSize:dstFrameSize] + } + + // convert frame from YUV420 to RGB + res = C.sws_scale(d.swsCtx, frameData(d.srcFrame), frameLineSize(d.srcFrame), + 0, d.srcFrame.height, frameData(d.dstFrame), frameLineSize(d.dstFrame)) + if res < 0 { + return nil, fmt.Errorf("sws_scale() err") + } + + // embed frame into an image.Image + return &image.RGBA{ + Pix: d.dstFramePtr, + Stride: 4 * (int)(d.dstFrame.width), + Rect: image.Rectangle{ + Max: image.Point{(int)(d.dstFrame.width), (int)(d.dstFrame.height)}, + }, + }, nil +} diff --git a/device/generic_camera_stream_windows_amd64.go b/device/generic_camera_stream_windows_amd64.go index 40b0e6587..e4434483a 100644 --- a/device/generic_camera_stream_windows_amd64.go +++ b/device/generic_camera_stream_windows_amd64.go @@ -2,10 +2,39 @@ package device import ( "fmt" + "os/exec" + "time" + + "context" + "net" + "net/http" + "sync" + + "github.com/bluenviron/gortsplib/v3" + "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/hootrhino/rulex/glogger" "github.com/hootrhino/rulex/typex" + "github.com/hootrhino/rulex/utils" + "github.com/pion/rtp" + "gocv.io/x/gocv" ) +/* +* +* 一般来说不会有太多摄像头,默认都是0、1,到4已经能覆盖绝大多数设备 +* + */ +var videoDevMap = map[string]int{ + "video0": 0, + "video1": 1, + "video2": 2, + "video3": 3, + "video4": 4, +} + // RTSP URL格式= rtsp://:@:, type _MainConfig struct { MaxThread int `json:"maxThread"` // 最大连接数, 防止连接过多导致摄像头拉流失败 @@ -21,6 +50,8 @@ type videoCamera struct { typex.XStatus status typex.DeviceState mainConfig _MainConfig + video *gocv.VideoCapture + rtspClient *gortsplib.Client } func NewVideoCamera(e typex.RuleX) typex.XDevice { @@ -28,25 +59,215 @@ func NewVideoCamera(e typex.RuleX) typex.XDevice { videoCamera.RuleEngine = e videoCamera.status = typex.DEV_DOWN videoCamera.mainConfig = _MainConfig{ + MaxThread: 10, + InputMode: "LOCAL", Device: "video0", RtspUrl: "rtsp://127.0.0.1", - InputMode: "LOCAL", OutputMode: "JPEG_STREAM", + OutputAddr: "127.0.0.1:2599", } return videoCamera } +/* +* 教程: +* https://ffmpeg.xianwaizhiyin.net/base-ffmpeg/ffmpeg-install.html +* + */ +func CheckFFMPEG() error { + // Command to check if 'ffmpeg' exists + cmd := exec.Command("ffmpeg", "-version") + output, err := cmd.CombinedOutput() + + if err != nil { + return err + } + if len(output) < 100 { + return fmt.Errorf("ffmpeg not exists") + } + return nil +} + // 初始化 通常用来获取设备的配置 func (vc *videoCamera) Init(devId string, configMap map[string]interface{}) error { - return fmt.Errorf("video camera not support windows") + vc.PointId = devId + if err := utils.BindSourceConfig(configMap, &vc.mainConfig); err != nil { + return err + } + if err := CheckFFMPEG(); err != nil { + return err + } + /* + * + * 检查一下URL + * + */ + if vc.mainConfig.InputMode == "RTSP" { + rtspClient := gortsplib.Client{} + u, err := url.Parse(vc.mainConfig.RtspUrl) + if err != nil { + return err + } + err = rtspClient.Start(u.Scheme, u.Host) + if err != nil { + return err + } + defer rtspClient.Close() + } + /* + * + * Local指的是本地的USB摄像头 + * + */ + if vc.mainConfig.InputMode == "LOCAL" { + if _, ok := videoDevMap[vc.mainConfig.Device]; !ok { + errMsg := fmt.Errorf("video device: %v not exists", vc.mainConfig.Device) + glogger.GLogger.Error(errMsg) + return errMsg + } + video, err := gocv.OpenVideoCapture(videoDevMap[vc.mainConfig.Device]) + if err != nil { + errMsg := fmt.Errorf("video device %v, open error: %v", + vc.mainConfig.Device, err.Error()) + glogger.GLogger.Error(errMsg) + return errMsg + } + defer video.Close() + } + return nil } // 启动, 设备的工作进程 func (vc *videoCamera) Start(cctx typex.CCTX) error { + vc.Ctx = cctx.Ctx + vc.CancelCTX = cctx.CancelCTX + var err error + // + // 从远程摄像头拉流 + // + if vc.mainConfig.InputMode == "RTSP" { + rtspClient := gortsplib.Client{} + u, err := url.Parse(vc.mainConfig.RtspUrl) + if err != nil { + glogger.GLogger.Error(err) + return err + } + // connect to the server + err = rtspClient.Start(u.Scheme, u.Host) + if err != nil { + glogger.GLogger.Error(err) + return err + } + medias, baseURL, _, err := rtspClient.Describe(u) + if err != nil { + glogger.GLogger.Error(err) + return err + } + err = rtspClient.SetupAll(medias, baseURL) + if err != nil { + glogger.GLogger.Error(err) + return err + } + + rtspClient.OnPacketRTPAny(func(medi *media.Media, forma formats.Format, pkt *rtp.Packet) { + fmt.Println(forma.ClockRate()) + }) + + vc.rtspClient = &rtspClient + } + // + // 从本地摄像头拉流 + // + if vc.mainConfig.InputMode == "LOCAL" { + if _, ok := videoDevMap[vc.mainConfig.Device]; !ok { + errMsg := fmt.Errorf("video device: %v not exists", vc.mainConfig.Device) + glogger.GLogger.Error(errMsg) + return errMsg + } + vc.video, err = gocv.OpenVideoCapture(videoDevMap[vc.mainConfig.Device]) + if err != nil { + glogger.GLogger.Error(err) + return err + } + } + if err != nil { + errMsg := fmt.Errorf("video device %v, open error: %v", + vc.mainConfig.Device, err.Error()) + glogger.GLogger.Error(errMsg) + return errMsg + } + if vc.mainConfig.InputMode == "LOCAL" { + go vc.serveHttpJPEGStream() + } + if vc.mainConfig.InputMode == "RTSP" { + go vc.StartRTSPStreamServer() + } vc.status = typex.DEV_UP return nil } +/* +* +* 提供RTSP接口来访问摄像头 +* + */ +func (vc *videoCamera) StartRTSPStreamServer() error { + vc.rtspClient.Play(nil) + return vc.rtspClient.Wait() +} + +/* +* +* 提供HTTP接口来访问摄像头 +* + */ +func (vc *videoCamera) serveHttpJPEGStream() { + defer vc.video.Close() + cvMat := gocv.NewMat() + defer cvMat.Close() + stream := NewMJPEGStream(vc.mainConfig.MaxThread) + go func() { + mux := http.NewServeMux() + mux.HandleFunc("/", stream.ServeHTTP) + serverOne := &http.Server{ + Addr: vc.mainConfig.OutputAddr, + Handler: mux, + BaseContext: func(l net.Listener) context.Context { + return vc.Ctx + }, + } + serverOne.ListenAndServe() + }() + errTimes := 0 + for { + select { + case <-vc.Ctx.Done(): + return + default: + } + if ok := vc.video.Read(&cvMat); !ok { + // 如果连续30帧失败,直接重启 + errTimes++ + if errTimes > 30 { + vc.status = typex.DEV_DOWN + continue + } else { + continue + } + } + if cvMat.Empty() { + continue + } + cvBuf, err := gocv.IMEncode(".png", cvMat) + if err != nil { + glogger.GLogger.Error(err) + continue + } + stream.UpdateJPEG(cvBuf.GetBytes()) + } + +} + func (vc *videoCamera) OnRead(cmd []byte, data []byte) (int, error) { return 0, nil } @@ -72,6 +293,12 @@ func (vc *videoCamera) Status() typex.DeviceState { func (vc *videoCamera) Stop() { vc.status = typex.DEV_STOP vc.CancelCTX() + if vc.video != nil { + vc.video.Close() + } + if vc.rtspClient != nil { + vc.rtspClient.Close() + } } func (vc *videoCamera) Property() []typex.DeviceProperty { @@ -83,8 +310,8 @@ func (vc *videoCamera) Details() *typex.Device { } -func (vc *videoCamera) SetState(_ typex.DeviceState) { - +func (vc *videoCamera) SetState(s typex.DeviceState) { + vc.status = s } func (vc *videoCamera) Driver() typex.XExternalDriver { @@ -94,3 +321,78 @@ func (vc *videoCamera) Driver() typex.XExternalDriver { func (vc *videoCamera) OnDCACall(_ string, _ string, _ interface{}) typex.DCAResult { return typex.DCAResult{} } + +//-------------------------------------------------------------- +// HTTP 图片流 +//-------------------------------------------------------------- + +type mJPEGStream struct { + m map[chan []byte]bool + frame []byte + lock sync.Mutex + FrameInterval time.Duration + maxThread int + currentThread int +} + +// multipart/x-mixed-replace 一种固定写法 +const boundaryWord = "MJPEGBOUNDARY" +const header = "\r\n" + + "--" + boundaryWord + "\r\n" + + "Content-Type: image/JPEG\r\n" + + "Content-Length: %d\r\n" + + "X-Timestamp: 0.000000\r\n" + + "\r\n" + +// serveHttpJPEGStream responds to HTTP requests with the MJPEG stream, implementing the http.Handler interface. +func (s *mJPEGStream) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.currentThread++ + if s.currentThread > s.maxThread { + w.Write([]byte("Exceed MaxThread")) + return + } + w.Header().Add("Content-Type", "multipart/x-mixed-replace;boundary="+boundaryWord) + c := make(chan []byte) + s.lock.Lock() + s.m[c] = true + s.lock.Unlock() + for { + time.Sleep(s.FrameInterval) + b := <-c + _, err := w.Write(b) + if err != nil { + break + } + } + s.lock.Lock() + delete(s.m, c) + s.lock.Unlock() + s.currentThread-- +} + +func (s *mJPEGStream) UpdateJPEG(JPEG []byte) { + header := fmt.Sprintf(header, len(JPEG)) + if len(s.frame) < len(JPEG)+len(header) { + s.frame = make([]byte, (len(JPEG)+len(header))*2) + } + copy(s.frame, header) + copy(s.frame[len(header):], JPEG) + s.lock.Lock() + for c := range s.m { + select { + case c <- s.frame: + default: + } + } + s.lock.Unlock() +} + +func NewMJPEGStream(mt int) *mJPEGStream { + return &mJPEGStream{ + m: make(map[chan []byte]bool), + frame: make([]byte, len(header)), + FrameInterval: 50 * time.Millisecond, + currentThread: 0, + maxThread: mt, + } +} diff --git a/engine/engine.go b/engine/engine.go index ff09ec30f..92c0e25d5 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -407,7 +407,7 @@ func (e *RuleEngine) SnapshotDump() string { e.AllDevices().Range(func(key, value interface{}) bool { Device := value.(*typex.Device) if Device.Device.Driver() != nil { - drivers = append(drivers, Device.Device.Driver()) + devices = append(devices, Device.Device.Driver()) } return true }) diff --git a/engine/load_device.go b/engine/load_device.go index 5b702f302..27a477f16 100644 --- a/engine/load_device.go +++ b/engine/load_device.go @@ -40,7 +40,9 @@ func (e *RuleEngine) RemoveDevice(uuid string) { if dev := e.GetDevice(uuid); dev != nil { if dev.Device != nil { glogger.GLogger.Infof("Device [%v] ready to stop", uuid) - dev.Device.Stop() + if dev.Device.Status() == typex.DEV_UP { + dev.Device.Stop() + } glogger.GLogger.Infof("Device [%v] has been stopped", uuid) e.Devices.Delete(uuid) glogger.GLogger.Infof("Device [%v] has been deleted", uuid) diff --git a/go.mod b/go.mod index 74adcf1da..e793fb297 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/adrianmo/go-nmea v1.8.0 github.com/antonmedv/expr v1.12.5 github.com/bluele/gcache v0.0.2 + github.com/bluenviron/gortsplib/v3 v3.9.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/emirpasic/gods v1.18.1 @@ -33,6 +34,7 @@ require ( github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 github.com/nats-io/nats.go v1.26.0 github.com/patrikeh/go-deep v0.0.0-20230427173908-a2775168ab3d + github.com/pion/rtp v1.8.1 github.com/plgd-dev/go-coap/v2 v2.6.0 github.com/robinson/gos7 v0.0.0-20230421131203-d20ac6ca08cd github.com/rs/zerolog v1.28.0 @@ -52,7 +54,8 @@ require ( go.bug.st/serial v1.5.0 go.mongodb.org/mongo-driver v1.11.6 go.uber.org/zap v1.15.0 - golang.org/x/crypto v0.9.0 + gocv.io/x/gocv v0.33.0 + golang.org/x/crypto v0.11.0 golang.org/x/sys v0.10.0 google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 @@ -63,8 +66,12 @@ require ( ) require ( + github.com/bluenviron/mediacommon v0.7.0 // indirect github.com/juju/errors v0.0.0-20170703010042-c7d06af17c68 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.10 // indirect + github.com/pion/sdp/v3 v3.0.6 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect @@ -143,7 +150,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.5.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index 221fefd42..13a710191 100644 --- a/go.sum +++ b/go.sum @@ -393,6 +393,10 @@ github.com/blastrain/vitess-sqlparser v0.0.0-20201030050434-a139afbb1aba h1:hBK2 github.com/blastrain/vitess-sqlparser v0.0.0-20201030050434-a139afbb1aba/go.mod h1:FGQp+RNQwVmLzDq6HBrYCww9qJQyNwH9Qji/quTQII4= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= +github.com/bluenviron/gortsplib/v3 v3.9.0 h1:aAHV6MhsDtgBF6yKaNBBCdvtSpLB8ne4kyUfLQlN7nM= +github.com/bluenviron/gortsplib/v3 v3.9.0/go.mod h1:5h3Zu7jkzwDknYrf+89q2saab//oioKgM9mgvBEX3pg= +github.com/bluenviron/mediacommon v0.7.0 h1:dJWLLL9oDbAqfK8KuNfnDUQwNbeMAtGeRjZc9Vo95js= +github.com/bluenviron/mediacommon v0.7.0/go.mod h1:wuLJdxcITiSPgY1MvQqrX+qPlKmNfeV9wNvXth5M98I= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -648,6 +652,7 @@ github.com/hootrhino/gopher-lua v1.0.0 h1:6kYVenNQYvWdqf4sd2KXOr4XUPKyDZqrxW0PNI github.com/hootrhino/gopher-lua v1.0.0/go.mod h1:tY0TknOctxfkzkx60g+po3hj7K1R+YdwLYqT4OqAINc= github.com/hootrhino/wmi v0.0.0-20230603082700-cfa077a8cf01 h1:oPtZwF/Th9FuFZH4bv0otm6de/5ewcqfaf6pVI/Xfwc= github.com/hootrhino/wmi v0.0.0-20230603082700-cfa077a8cf01/go.mod h1:RmN9Gg8TiRseWz6DqrfekUqlRWzUtLJonWUwyfTdcu0= +github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs= github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -771,6 +776,7 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/panjf2000/ants/v2 v2.4.3/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrikeh/go-deep v0.0.0-20230427173908-a2775168ab3d h1:uklDHZ8eaoO7TzqTu1bk/ijlkfadd8ogGfit4oIeSik= github.com/patrikeh/go-deep v0.0.0-20230427173908-a2775168ab3d/go.mod h1:W7GtTeZHpwautuPVtKBFp1+df69GkwlOGD2cwvYeYIE= @@ -784,6 +790,14 @@ github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc= +github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= +github.com/pion/rtp v1.8.1 h1:26OxTc6lKg/qLSGir5agLyj0QKaOv8OP5wps2SFnVNQ= +github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= +github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= @@ -862,8 +876,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -888,8 +900,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/suapapa/go_eddystone v1.3.1 h1:mfW3eNoRPpaZ0iARRZtEyudFfNFtytqeCexwXg1wIKE= github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= @@ -986,6 +998,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +gocv.io/x/gocv v0.33.0 h1:WDtaBrq92AKrhepYzEktydDzNSm3t5k7ciawZK4rns8= +gocv.io/x/gocv v0.33.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -1004,8 +1018,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1106,8 +1120,8 @@ golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1240,7 +1254,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20180302201248-b7ef84aaf62a/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/plugin/http_server/dao.go b/plugin/http_server/dao.go index f388634c8..ba36ae75d 100644 --- a/plugin/http_server/dao.go +++ b/plugin/http_server/dao.go @@ -3,6 +3,7 @@ package httpserver import ( "errors" + sqlitedao "github.com/hootrhino/rulex/plugin/http_server/dao/sqlite" "github.com/hootrhino/rulex/plugin/http_server/model" "gorm.io/gorm" @@ -11,7 +12,7 @@ import ( // ----------------------------------------------------------------------------------- func (s *HttpApiServer) GetMRule(uuid string) (*model.MRule, error) { m := new(model.MRule) - if err := s.DB().Where("uuid=?", uuid).First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(m).Error; err != nil { return nil, err } else { return m, nil @@ -19,7 +20,7 @@ func (s *HttpApiServer) GetMRule(uuid string) (*model.MRule, error) { } func (s *HttpApiServer) GetAllMRule() ([]model.MRule, error) { m := []model.MRule{} - if err := s.DB().Find(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Find(&m).Error; err != nil { return nil, err } else { return m, nil @@ -28,7 +29,7 @@ func (s *HttpApiServer) GetAllMRule() ([]model.MRule, error) { func (s *HttpApiServer) GetMRuleWithUUID(uuid string) (*model.MRule, error) { m := new(model.MRule) - if err := s.DB().Where("uuid=?", uuid).First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(m).Error; err != nil { return nil, err } else { return m, nil @@ -36,11 +37,11 @@ func (s *HttpApiServer) GetMRuleWithUUID(uuid string) (*model.MRule, error) { } func (s *HttpApiServer) InsertMRule(r *model.MRule) error { - return s.DB().Table("m_rules").Create(r).Error + return sqlitedao.Sqlite.DB().Table("m_rules").Create(r).Error } func (s *HttpApiServer) DeleteMRule(uuid string) error { - if s.DB().Table("m_rules").Where("uuid=?", uuid).Delete(&model.MRule{}).RowsAffected == 0 { + if sqlitedao.Sqlite.DB().Table("m_rules").Where("uuid=?", uuid).Delete(&model.MRule{}).RowsAffected == 0 { return errors.New("not found:" + uuid) } return nil @@ -48,10 +49,10 @@ func (s *HttpApiServer) DeleteMRule(uuid string) error { func (s *HttpApiServer) UpdateMRule(uuid string, r *model.MRule) error { m := model.MRule{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*r) + sqlitedao.Sqlite.DB().Model(m).Updates(*r) return nil } } @@ -59,7 +60,7 @@ func (s *HttpApiServer) UpdateMRule(uuid string, r *model.MRule) error { // ----------------------------------------------------------------------------------- func (s *HttpApiServer) GetMInEnd(uuid string) (*model.MInEnd, error) { m := new(model.MInEnd) - if err := s.DB().Table("m_in_ends").Where("uuid=?", uuid).First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Table("m_in_ends").Where("uuid=?", uuid).First(m).Error; err != nil { return nil, err } else { return m, nil @@ -67,7 +68,7 @@ func (s *HttpApiServer) GetMInEnd(uuid string) (*model.MInEnd, error) { } func (s *HttpApiServer) GetMInEndWithUUID(uuid string) (*model.MInEnd, error) { m := new(model.MInEnd) - if err := s.DB().Table("m_in_ends").Where("uuid=?", uuid).First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Table("m_in_ends").Where("uuid=?", uuid).First(m).Error; err != nil { return nil, err } else { return m, nil @@ -75,11 +76,11 @@ func (s *HttpApiServer) GetMInEndWithUUID(uuid string) (*model.MInEnd, error) { } func (s *HttpApiServer) InsertMInEnd(i *model.MInEnd) error { - return s.DB().Table("m_in_ends").Create(i).Error + return sqlitedao.Sqlite.DB().Table("m_in_ends").Create(i).Error } func (s *HttpApiServer) DeleteMInEnd(uuid string) error { - if s.DB().Where("uuid=?", uuid).Delete(&model.MInEnd{}).RowsAffected == 0 { + if sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MInEnd{}).RowsAffected == 0 { return errors.New("not found:" + uuid) } return nil @@ -87,10 +88,10 @@ func (s *HttpApiServer) DeleteMInEnd(uuid string) error { func (s *HttpApiServer) UpdateMInEnd(uuid string, i *model.MInEnd) error { m := model.MInEnd{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*i) + sqlitedao.Sqlite.DB().Model(m).Updates(*i) return nil } } @@ -98,7 +99,7 @@ func (s *HttpApiServer) UpdateMInEnd(uuid string, i *model.MInEnd) error { // ----------------------------------------------------------------------------------- func (s *HttpApiServer) GetMOutEnd(id string) (*model.MOutEnd, error) { m := new(model.MOutEnd) - if err := s.DB().First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().First(m).Error; err != nil { return nil, err } else { return m, nil @@ -106,7 +107,7 @@ func (s *HttpApiServer) GetMOutEnd(id string) (*model.MOutEnd, error) { } func (s *HttpApiServer) GetMOutEndWithUUID(uuid string) (*model.MOutEnd, error) { m := new(model.MOutEnd) - if err := s.DB().Where("uuid=?", uuid).First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(m).Error; err != nil { return nil, err } else { return m, nil @@ -114,11 +115,11 @@ func (s *HttpApiServer) GetMOutEndWithUUID(uuid string) (*model.MOutEnd, error) } func (s *HttpApiServer) InsertMOutEnd(o *model.MOutEnd) error { - return s.DB().Table("m_out_ends").Create(o).Error + return sqlitedao.Sqlite.DB().Table("m_out_ends").Create(o).Error } func (s *HttpApiServer) DeleteMOutEnd(uuid string) error { - if s.DB().Where("uuid=?", uuid).Delete(&model.MOutEnd{}).RowsAffected == 0 { + if sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MOutEnd{}).RowsAffected == 0 { return errors.New("not found:" + uuid) } return nil @@ -126,10 +127,10 @@ func (s *HttpApiServer) DeleteMOutEnd(uuid string) error { func (s *HttpApiServer) UpdateMOutEnd(uuid string, o *model.MOutEnd) error { m := model.MOutEnd{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*o) + sqlitedao.Sqlite.DB().Model(m).Updates(*o) return nil } } @@ -139,7 +140,7 @@ func (s *HttpApiServer) UpdateMOutEnd(uuid string, o *model.MOutEnd) error { // ----------------------------------------------------------------------------------- func (s *HttpApiServer) GetMUser(username string, password string) (*model.MUser, error) { m := new(model.MUser) - if err := s.DB().Where("Username=?", username).Where("Password=?", + if err := sqlitedao.Sqlite.DB().Where("Username=?", username).Where("Password=?", password).First(m).Error; err != nil { return nil, err } else { @@ -148,15 +149,15 @@ func (s *HttpApiServer) GetMUser(username string, password string) (*model.MUser } func (s *HttpApiServer) InsertMUser(o *model.MUser) { - s.DB().Table("m_users").Create(o) + sqlitedao.Sqlite.DB().Table("m_users").Create(o) } func (s *HttpApiServer) UpdateMUser(uuid string, o *model.MUser) error { m := model.MUser{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*o) + sqlitedao.Sqlite.DB().Model(m).Updates(*o) return nil } } @@ -164,31 +165,31 @@ func (s *HttpApiServer) UpdateMUser(uuid string, o *model.MUser) error { // ----------------------------------------------------------------------------------- func (s *HttpApiServer) AllMRules() []model.MRule { rules := []model.MRule{} - s.DB().Table("m_rules").Find(&rules) + sqlitedao.Sqlite.DB().Table("m_rules").Find(&rules) return rules } func (s *HttpApiServer) AllMInEnd() []model.MInEnd { inends := []model.MInEnd{} - s.DB().Table("m_in_ends").Find(&inends) + sqlitedao.Sqlite.DB().Table("m_in_ends").Find(&inends) return inends } func (s *HttpApiServer) AllMOutEnd() []model.MOutEnd { outends := []model.MOutEnd{} - s.DB().Table("m_out_ends").Find(&outends) + sqlitedao.Sqlite.DB().Table("m_out_ends").Find(&outends) return outends } func (s *HttpApiServer) AllMUser() []model.MUser { users := []model.MUser{} - s.DB().Find(&users) + sqlitedao.Sqlite.DB().Find(&users) return users } func (s *HttpApiServer) AllDevices() []model.MDevice { devices := []model.MDevice{} - s.DB().Find(&devices) + sqlitedao.Sqlite.DB().Find(&devices) return devices } @@ -197,7 +198,7 @@ func (s *HttpApiServer) AllDevices() []model.MDevice { // 获取设备列表 func (s *HttpApiServer) GetMDeviceWithUUID(uuid string) (*model.MDevice, error) { m := new(model.MDevice) - if err := s.DB().Where("uuid=?", uuid).First(m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(m).Error; err != nil { return nil, err } else { return m, nil @@ -206,7 +207,7 @@ func (s *HttpApiServer) GetMDeviceWithUUID(uuid string) (*model.MDevice, error) // 删除设备 func (s *HttpApiServer) DeleteDevice(uuid string) error { - if s.DB().Where("uuid=?", uuid).Delete(&model.MDevice{}).RowsAffected == 0 { + if sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MDevice{}).RowsAffected == 0 { return errors.New("not found:" + uuid) } return nil @@ -214,16 +215,16 @@ func (s *HttpApiServer) DeleteDevice(uuid string) error { // 创建设备 func (s *HttpApiServer) InsertDevice(o *model.MDevice) error { - return s.DB().Table("m_devices").Create(o).Error + return sqlitedao.Sqlite.DB().Table("m_devices").Create(o).Error } // 更新设备信息 func (s *HttpApiServer) UpdateDevice(uuid string, o *model.MDevice) error { m := model.MDevice{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*o) + sqlitedao.Sqlite.DB().Model(m).Updates(*o) return nil } } @@ -235,12 +236,12 @@ func (s *HttpApiServer) UpdateDevice(uuid string, o *model.MDevice) error { // InsertModbusPointPosition 插入modbus点位表 func (s *HttpApiServer) InsertModbusPointPosition(list []model.MModbusPointPosition) error { m := model.MModbusPointPosition{} - return s.DB().Model(m).Create(list).Error + return sqlitedao.Sqlite.DB().Model(m).Create(list).Error } // DeleteModbusPointAndDevice 删除modbus点位与设备 func (s *HttpApiServer) DeleteModbusPointAndDevice(deviceUuid string) error { - return s.DB().Transaction(func(tx *gorm.DB) (err error) { + return sqlitedao.Sqlite.DB().Transaction(func(tx *gorm.DB) (err error) { err = tx.Where("device_uuid = ?", deviceUuid).Delete(&model.MModbusPointPosition{}).Error if err != nil { @@ -258,10 +259,10 @@ func (s *HttpApiServer) DeleteModbusPointAndDevice(deviceUuid string) error { // UpdateModbusPoint 更新modbus点位 func (s *HttpApiServer) UpdateModbusPoint(mm model.MModbusPointPosition) error { m := model.MDevice{} - if err := s.sqliteDb.Where("id = ?", mm.ID).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("id = ?", mm.ID).First(&m).Error; err != nil { return err } else { - s.sqliteDb.Model(m).Updates(&m) + sqlitedao.Sqlite.DB().Model(m).Updates(&m) return nil } } @@ -269,7 +270,7 @@ func (s *HttpApiServer) UpdateModbusPoint(mm model.MModbusPointPosition) error { // AllModbusPointByDeviceUuid 根据设备UUID查询设备点位 func (s *HttpApiServer) AllModbusPointByDeviceUuid(deviceUuid string) (list []model.MModbusPointPosition, err error) { - err = s.sqliteDb.Where("device_uuid = ?", deviceUuid).Find(&list).Error + err = sqlitedao.Sqlite.DB().Where("device_uuid = ?", deviceUuid).Find(&list).Error return } @@ -280,13 +281,13 @@ func (s *HttpApiServer) AllModbusPointByDeviceUuid(deviceUuid string) (list []mo // 获取Goods列表 func (s *HttpApiServer) AllGoods() []model.MGoods { m := []model.MGoods{} - s.DB().Find(&m) + sqlitedao.Sqlite.DB().Find(&m) return m } func (s *HttpApiServer) GetGoodsWithUUID(uuid string) (*model.MGoods, error) { m := model.MGoods{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return nil, err } else { return &m, nil @@ -295,7 +296,7 @@ func (s *HttpApiServer) GetGoodsWithUUID(uuid string) (*model.MGoods, error) { // 删除Goods func (s *HttpApiServer) DeleteGoods(uuid string) error { - if s.DB().Where("uuid=?", uuid).Delete(&model.MGoods{}).RowsAffected == 0 { + if sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MGoods{}).RowsAffected == 0 { return errors.New("not found:" + uuid) } return nil @@ -303,16 +304,16 @@ func (s *HttpApiServer) DeleteGoods(uuid string) error { // 创建Goods func (s *HttpApiServer) InsertGoods(goods *model.MGoods) error { - return s.DB().Table("m_goods").Create(goods).Error + return sqlitedao.Sqlite.DB().Table("m_goods").Create(goods).Error } // 更新Goods func (s *HttpApiServer) UpdateGoods(uuid string, goods *model.MGoods) error { m := model.MGoods{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*goods) + sqlitedao.Sqlite.DB().Model(m).Updates(*goods) return nil } } @@ -324,13 +325,13 @@ func (s *HttpApiServer) UpdateGoods(uuid string, goods *model.MGoods) error { // 获取App列表 func (s *HttpApiServer) AllApp() []model.MApp { m := []model.MApp{} - s.DB().Find(&m) + sqlitedao.Sqlite.DB().Find(&m) return m } func (s *HttpApiServer) GetMAppWithUUID(uuid string) (*model.MApp, error) { m := model.MApp{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return nil, err } else { return &m, nil @@ -339,21 +340,21 @@ func (s *HttpApiServer) GetMAppWithUUID(uuid string) (*model.MApp, error) { // 删除App func (s *HttpApiServer) DeleteApp(uuid string) error { - return s.DB().Where("uuid=?", uuid).Delete(&model.MApp{}).Error + return sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MApp{}).Error } // 创建App func (s *HttpApiServer) InsertApp(app *model.MApp) error { - return s.DB().Create(app).Error + return sqlitedao.Sqlite.DB().Create(app).Error } // 更新App func (s *HttpApiServer) UpdateApp(app *model.MApp) error { m := model.MApp{} - if err := s.DB().Where("uuid=?", app.UUID).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", app.UUID).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*app) + sqlitedao.Sqlite.DB().Model(m).Updates(*app) return nil } } @@ -361,13 +362,13 @@ func (s *HttpApiServer) UpdateApp(app *model.MApp) error { // 获取AiBase列表 func (s *HttpApiServer) AllAiBase() []model.MAiBase { m := []model.MAiBase{} - s.DB().Find(&m) + sqlitedao.Sqlite.DB().Find(&m) return m } func (s *HttpApiServer) GetAiBaseWithUUID(uuid string) (*model.MAiBase, error) { m := model.MAiBase{} - if err := s.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { return nil, err } else { return &m, nil @@ -376,21 +377,21 @@ func (s *HttpApiServer) GetAiBaseWithUUID(uuid string) (*model.MAiBase, error) { // 删除AiBase func (s *HttpApiServer) DeleteAiBase(uuid string) error { - return s.DB().Where("uuid=?", uuid).Delete(&model.MAiBase{}).Error + return sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MAiBase{}).Error } // 创建AiBase func (s *HttpApiServer) InsertAiBase(AiBase *model.MAiBase) error { - return s.DB().Create(AiBase).Error + return sqlitedao.Sqlite.DB().Create(AiBase).Error } // 更新AiBase func (s *HttpApiServer) UpdateAiBase(AiBase *model.MAiBase) error { m := model.MAiBase{} - if err := s.DB().Where("uuid=?", AiBase.UUID).First(&m).Error; err != nil { + if err := sqlitedao.Sqlite.DB().Where("uuid=?", AiBase.UUID).First(&m).Error; err != nil { return err } else { - s.DB().Model(m).Updates(*AiBase) + sqlitedao.Sqlite.DB().Model(m).Updates(*AiBase) return nil } } diff --git a/plugin/http_server/dao/duckdb/duckdb_dao.go b/plugin/http_server/dao/duckdb/duckdb_dao.go new file mode 100644 index 000000000..04e7ff038 --- /dev/null +++ b/plugin/http_server/dao/duckdb/duckdb_dao.go @@ -0,0 +1 @@ +package duckdb diff --git a/plugin/http_server/dao/sqlite/sqlite_dao.go b/plugin/http_server/dao/sqlite/sqlite_dao.go new file mode 100644 index 000000000..0b9912077 --- /dev/null +++ b/plugin/http_server/dao/sqlite/sqlite_dao.go @@ -0,0 +1,99 @@ +package sqlitedao + +import ( + "os" + "runtime" + + "github.com/hootrhino/rulex/core" + "github.com/hootrhino/rulex/glogger" + dao "github.com/hootrhino/rulex/plugin/http_server/dao" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var Sqlite dao.DAO + +/* +* +* Sqlite 数据持久层 +* + */ +type SqliteDAO struct { + name string // 框架可以根据名称来选择不同的数据库驱动,为以后扩展准备 + db *gorm.DB // Sqlite 驱动 +} + +/* +* +* 新建一个SqliteDAO +* + */ +func Load(dbPath string) { + Sqlite = &SqliteDAO{name: "SqliteDAO"} + Sqlite.Init(dbPath) +} + +/* +* +* 初始化DAO +* + */ +func (s *SqliteDAO) Init(dbPath string) error { + var err error + if core.GlobalConfig.AppDebugMode { + s.db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + SkipDefaultTransaction: false, + }) + } else { + s.db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Error), + SkipDefaultTransaction: false, + }) + } + if err != nil { + glogger.GLogger.Fatal(err) + } + return err +} + +/* +* +* 停止 +* + */ +func (s *SqliteDAO) Stop() { + s.db = nil + runtime.GC() +} + +/* +* +* 返回数据库查询句柄 +* + */ +func (s *SqliteDAO) DB() *gorm.DB { + return s.db +} + +/* +* +* 返回名称 +* + */ +func (s *SqliteDAO) Name() string { + return s.name +} + +/* +* +* 注册数据模型 +* + */ +func (s *SqliteDAO) RegisterModel(dist ...interface{}) { + if err := s.DB().AutoMigrate(dist); err != nil { + glogger.GLogger.Fatal(err) + os.Exit(1) + } +} diff --git a/plugin/http_server/dao/types.go b/plugin/http_server/dao/types.go new file mode 100644 index 000000000..f412a8a48 --- /dev/null +++ b/plugin/http_server/dao/types.go @@ -0,0 +1,16 @@ +package dao + +import "gorm.io/gorm" + +/* +* +* DAO 接口 +* + */ +type DAO interface { + Init(string) error + RegisterModel(dst ...interface{}) + Name() string + DB() *gorm.DB + Stop() +} diff --git a/plugin/http_server/group_api.go b/plugin/http_server/group_api.go new file mode 100644 index 000000000..ba01b035a --- /dev/null +++ b/plugin/http_server/group_api.go @@ -0,0 +1,236 @@ +package httpserver + +import ( + "fmt" + + "github.com/gin-gonic/gin" + common "github.com/hootrhino/rulex/plugin/http_server/common" + "github.com/hootrhino/rulex/plugin/http_server/model" + "github.com/hootrhino/rulex/plugin/http_server/service" + "github.com/hootrhino/rulex/utils" +) + +type MGenericGroupVo struct { + UUID string `json:"uuid" validate:"required"` // 名称 + Name string `json:"name" validate:"required"` // 名称 + Type string `json:"type" validate:"required"` // 组的类型, DEVICE: 设备分组, VISUAL: 大屏分组 + Parent string `json:"parent"` // 上级, 如果是0表示根节点 +} +type MGenericGroupRelationVo struct { + Gid string `json:"gid" validate:"required"` // 分组ID + Rid string `json:"rid" validate:"required"` // 被绑定方 +} + +/* +* +* 新建大屏 +* + */ +func CreateGroup(c *gin.Context, hh *HttpApiServer) { + vvo := MGenericGroupVo{} + if err := c.ShouldBindJSON(&vvo); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + if !utils.SContains([]string{"VISUAL", "DEVICE"}, vvo.Type) { + c.JSON(common.HTTP_OK, common.Error400(fmt.Errorf("invalid type [%s]", vvo.Type))) + } + Model := model.MGenericGroup{ + UUID: utils.GroupUuid(), + Name: vvo.Name, + Type: vvo.Type, + Parent: "0", + } + if err := service.InsertGenericGroup(&Model); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + c.JSON(common.HTTP_OK, common.Ok()) + +} + +/* +* +* 更新大屏 +* + */ +func UpdateGroup(c *gin.Context, hh *HttpApiServer) { + vvo := MGenericGroupVo{} + if err := c.ShouldBindJSON(&vvo); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + Model := model.MGenericGroup{ + UUID: vvo.UUID, + Name: vvo.Name, + Type: vvo.Type, + Parent: "0", + } + if err := service.UpdateGenericGroup(&Model); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + c.JSON(common.HTTP_OK, common.Ok()) +} + +/* +* +* 删除大屏 +* + */ +func DeleteGroup(c *gin.Context, hh *HttpApiServer) { + uuid, _ := c.GetQuery("uuid") + count, err := service.CheckBindResource(uuid) + if err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + // 删除之前需要判断一下是不是有子元 + if count > 0 { + msg := fmt.Errorf("group:%s have binding to other resources", uuid) + c.JSON(common.HTTP_OK, common.Error400(msg)) + return + } + if err := service.DeleteGenericGroup(uuid); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + } + c.JSON(common.HTTP_OK, common.Ok()) + +} + +/* +* +* 大屏列表 +* + */ +func ListGroup(c *gin.Context, hh *HttpApiServer) { + visuals := []MGenericGroupVo{} + for _, vv := range service.AllGenericGroup() { + visuals = append(visuals, MGenericGroupVo{ + UUID: vv.UUID, + Name: vv.Name, + Type: vv.Type, + Parent: vv.Parent, + }) + } + c.JSON(common.HTTP_OK, common.OkWithData(visuals)) + +} + +/* +* +* 大屏详情 +* + */ +func GroupDetail(c *gin.Context, hh *HttpApiServer) { + uuid, _ := c.GetQuery("uuid") + mG, err := service.GetGenericGroupWithUUID(uuid) + if err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + } + vo := MGenericGroupVo{ + UUID: mG.UUID, + Name: mG.Name, + Type: mG.Type, + Parent: mG.Parent, + } + c.JSON(common.HTTP_OK, common.OkWithData(vo)) +} + +/* +* +* 绑定资源 +* + */ +func BindResource(c *gin.Context, hh *HttpApiServer) { + type FormDto struct { + Gid string `json:"gid"` + Rid string `json:"rid"` + } + form := FormDto{} + if err := c.ShouldBindJSON(&form); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + if count, err := service.CheckAlreadyBinding(form.Gid, form.Rid); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } else { + if count > 0 { + msg := fmt.Errorf("[%s] already binding to group [%s]", form.Rid, form.Gid) + c.JSON(common.HTTP_OK, common.Error400(msg)) + return + } + } + if err := service.BindResource(form.Gid, form.Rid); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + c.JSON(common.HTTP_OK, common.Ok()) + +} + +/* +* +* 取消绑定 +* + */ +func UnBindResource(c *gin.Context, hh *HttpApiServer) { + gid, _ := c.GetQuery("gid") + rid, _ := c.GetQuery("rid") + if err := service.UnBindResource(gid, rid); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + c.JSON(common.HTTP_OK, common.Ok()) + +} + +/* +* +* 设备 +* + */ +func FindDeviceByGroup(c *gin.Context, hh *HttpApiServer) { + Type, _ := c.GetQuery("type") + Gid, _ := c.GetQuery("gid") + vv2 := []model.MDevice{} + + if Type == "DEVICE" { + _, MDevices := service.FindByType(Gid, Type) + for _, mG := range MDevices { + vv2 = append(vv2, model.MDevice{ + UUID: mG.UUID, + Name: mG.Name, + Type: mG.Type, + }) + } + c.JSON(common.HTTP_OK, common.OkWithData(vv2)) + return + } + c.JSON(common.HTTP_OK, common.Error400(fmt.Errorf("unsupported group type:%s", Type))) +} + +/* +* +* 大屏 +* + */ +func FindVisualByGroup(c *gin.Context, hh *HttpApiServer) { + Type, _ := c.GetQuery("type") + Gid, _ := c.GetQuery("gid") + vv1 := []model.MVisual{} + if Type == "VISUAL" { + MVisuals, _ := service.FindByType(Gid, Type) + for _, mG := range MVisuals { + vv1 = append(vv1, model.MVisual{ + UUID: mG.UUID, + Name: mG.Name, + Type: mG.Type, + }) + } + c.JSON(common.HTTP_OK, common.OkWithData(vv1)) + return + } + c.JSON(common.HTTP_OK, common.Error400(fmt.Errorf("unsupported group type:%s", Type))) +} diff --git a/plugin/http_server/http_api_server.go b/plugin/http_server/http_api_server.go index a7dd1bb35..212c46943 100644 --- a/plugin/http_server/http_api_server.go +++ b/plugin/http_server/http_api_server.go @@ -5,11 +5,10 @@ import ( "errors" "fmt" "net" - "os" "time" - "github.com/hootrhino/rulex/core" common "github.com/hootrhino/rulex/plugin/http_server/common" + sqlitedao "github.com/hootrhino/rulex/plugin/http_server/dao/sqlite" "github.com/hootrhino/rulex/plugin/http_server/model" "github.com/gin-contrib/static" @@ -25,9 +24,6 @@ import ( "gopkg.in/ini.v1" _ "github.com/mattn/go-sqlite3" - "gorm.io/driver/sqlite" - "gorm.io/gorm" - "gorm.io/gorm/logger" ) const _API_V1_ROOT string = "/api/v1/" @@ -43,13 +39,10 @@ type _serverConfig struct { Port int `ini:"port"` } type HttpApiServer struct { - Port int - Host string - sqliteDb *gorm.DB - dbPath string + uuid string ginEngine *gin.Engine ruleEngine typex.RuleX - uuid string + mainConfig _serverConfig } /* @@ -57,27 +50,8 @@ type HttpApiServer struct { * 初始化数据库 * */ -func (s *HttpApiServer) InitDb(dbPath string) { - var err error - if core.GlobalConfig.AppDebugMode { - s.sqliteDb, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), - SkipDefaultTransaction: false, - }) - } else { - s.sqliteDb, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - SkipDefaultTransaction: false, - }) - } - - if err != nil { - // Sqlite 创建失败应该是致命错误了, 多半是环境出问题,直接给panic了, 不尝试救活 - glogger.GLogger.Fatal(err) - } - // 注册数据库配置表 - // 这么写看起来是很难受, 但是这玩意就是go的哲学啊(大道至简???) - if err := s.DB().AutoMigrate( +func (s *HttpApiServer) registerModel() { + sqlitedao.Sqlite.DB().AutoMigrate( &model.MInEnd{}, &model.MOutEnd{}, &model.MRule{}, @@ -88,18 +62,16 @@ func (s *HttpApiServer) InitDb(dbPath string) { &model.MAiBase{}, &model.MModbusPointPosition{}, &model.MVisual{}, - ); err != nil { - glogger.GLogger.Fatal(err) - os.Exit(1) - } + &model.MGenericGroup{}, + &model.MGenericGroupRelation{}, + &model.MProtocolApp{}, + ) } -func (s *HttpApiServer) DB() *gorm.DB { - return s.sqliteDb -} func NewHttpApiServer() *HttpApiServer { return &HttpApiServer{ - uuid: "HTTP-API-SERVER", + uuid: "HTTP-API-SERVER", + mainConfig: _serverConfig{}, } } @@ -109,28 +81,18 @@ var err1crash = errors.New("http server crash, try to recovery") func (hs *HttpApiServer) Init(config *ini.Section) error { gin.SetMode(gin.ReleaseMode) hs.ginEngine = gin.New() - - var mainConfig _serverConfig - if err := utils.InIMapToStruct(config, &mainConfig); err != nil { + if err := utils.InIMapToStruct(config, &hs.mainConfig); err != nil { return err } - hs.Host = mainConfig.Host - hs.dbPath = mainConfig.DbPath - hs.Port = mainConfig.Port + if hs.mainConfig.DbPath == "" { + sqlitedao.Load(_DEFAULT_DB_PATH) + + } else { + sqlitedao.Load(hs.mainConfig.DbPath) + } + hs.registerModel() hs.configHttpServer() // - // Http server - // - go func(ctx context.Context, port int) { - listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { - glogger.GLogger.Fatalf("httpserver listen error: %s\n", err) - } - if err := hs.ginEngine.RunListener(listener); err != nil { - glogger.GLogger.Fatalf("httpserver listen error: %s\n", err) - } - }(typex.GCTX, hs.Port) - // // WebSocket server // hs.ginEngine.GET("/ws", glogger.WsLogger) @@ -302,23 +264,68 @@ func (hs *HttpApiServer) LoadRoute() { // ---------------------------------------------------------------------------------------------- // APP // ---------------------------------------------------------------------------------------------- - hs.ginEngine.GET(url("app"), hs.addRoute(Apps)) - hs.ginEngine.POST(url("app"), hs.addRoute(CreateApp)) - hs.ginEngine.PUT(url("app"), hs.addRoute(UpdateApp)) - hs.ginEngine.DELETE(url("app"), hs.addRoute(RemoveApp)) - hs.ginEngine.PUT(url("app/start"), hs.addRoute(StartApp)) - hs.ginEngine.PUT(url("app/stop"), hs.addRoute(StopApp)) - hs.ginEngine.GET(url("app/detail"), hs.addRoute(AppDetail)) + appApi := hs.ginEngine.Group(url("/app")) + { + appApi.GET(("/"), hs.addRoute(Apps)) + appApi.POST(("/"), hs.addRoute(CreateApp)) + appApi.PUT(("/"), hs.addRoute(UpdateApp)) + appApi.DELETE(("/"), hs.addRoute(RemoveApp)) + appApi.PUT(("/start"), hs.addRoute(StartApp)) + appApi.PUT(("/stop"), hs.addRoute(StopApp)) + appApi.GET(("/detail"), hs.addRoute(AppDetail)) + } // ---------------------------------------------------------------------------------------------- // AI BASE // ---------------------------------------------------------------------------------------------- - hs.ginEngine.GET(url("aibase"), hs.addRoute(AiBase)) - hs.ginEngine.DELETE(url("aibase"), hs.addRoute(DeleteAiBase)) + aiApi := hs.ginEngine.Group(url("/aibase")) + { + aiApi.GET(("/"), hs.addRoute(AiBase)) + aiApi.DELETE(("/"), hs.addRoute(DeleteAiBase)) + } // ---------------------------------------------------------------------------------------------- // Plugin // ---------------------------------------------------------------------------------------------- - hs.ginEngine.POST(url("plugin/service"), hs.addRoute(PluginService)) - hs.ginEngine.GET(url("plugin/detail"), hs.addRoute(PluginDetail)) + pluginApi := hs.ginEngine.Group(url("/plugin")) + { + pluginApi.POST(("/service"), hs.addRoute(PluginService)) + pluginApi.GET(("/detail"), hs.addRoute(PluginDetail)) + } + + // + // 分组管理 + // + groupApi := hs.ginEngine.Group(url("/group")) + { + groupApi.POST("/create", hs.addRoute(CreateGroup)) + groupApi.DELETE("/delete", hs.addRoute(DeleteGroup)) + groupApi.PUT("/update", hs.addRoute(UpdateGroup)) + groupApi.GET("/list", hs.addRoute(ListGroup)) + groupApi.POST("/bind", hs.addRoute(BindResource)) + groupApi.PUT("/unbind", hs.addRoute(UnBindResource)) + groupApi.GET("/devices", hs.addRoute(FindDeviceByGroup)) + groupApi.GET("/visuals", hs.addRoute(FindVisualByGroup)) + } + + // + // 协议应用管理 + // + protoAppApi := hs.ginEngine.Group(url("/protoapp")) + { + protoAppApi.POST("/create", hs.addRoute(CreateProtocolApp)) + protoAppApi.DELETE("/delete", hs.addRoute(DeleteProtocolApp)) + protoAppApi.PUT("/update", hs.addRoute(UpdateProtocolApp)) + protoAppApi.GET("/list", hs.addRoute(ListProtocolApp)) + } + // + // 大屏应用管理 + // + screenApi := hs.ginEngine.Group(url("/visual")) + { + screenApi.POST("/create", hs.addRoute(CreateVisual)) + screenApi.DELETE("/delete", hs.addRoute(DeleteVisual)) + screenApi.PUT("/update", hs.addRoute(UpdateVisual)) + screenApi.GET("/list", hs.addRoute(ListVisual)) + } } @@ -326,7 +333,7 @@ func (hs *HttpApiServer) LoadRoute() { func (hs *HttpApiServer) Start(r typex.RuleX) error { hs.ruleEngine = r hs.LoadRoute() - glogger.GLogger.Infof("Http server started on http://0.0.0.0:%v", hs.Port) + glogger.GLogger.Infof("Http server started on :%v", hs.mainConfig.Port) return nil } @@ -358,7 +365,6 @@ func (*HttpApiServer) Service(arg typex.ServiceArg) typex.ServiceResult { // Add api route func (hs *HttpApiServer) addRoute(f func(*gin.Context, *HttpApiServer)) func(*gin.Context) { - return func(c *gin.Context) { f(c, hs) } @@ -380,11 +386,18 @@ func (hs *HttpApiServer) configHttpServer() { hs.ginEngine.NoRoute(func(c *gin.Context) { c.Redirect(302, "/") }) - if hs.dbPath == "" { - hs.InitDb(_DEFAULT_DB_PATH) - } else { - hs.InitDb(hs.dbPath) - } + // + // Http server + // + go func(ctx context.Context, port int) { + listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + glogger.GLogger.Fatalf("httpserver listen error: %s\n", err) + } + if err := hs.ginEngine.RunListener(listener); err != nil { + glogger.GLogger.Fatalf("httpserver listen error: %s\n", err) + } + }(typex.GCTX, hs.mainConfig.Port) } /* diff --git a/plugin/http_server/model/model.go b/plugin/http_server/model/model.go index 48b222da7..be9656415 100644 --- a/plugin/http_server/model/model.go +++ b/plugin/http_server/model/model.go @@ -177,6 +177,9 @@ type MModbusPointPosition struct { Quality uint16 `json:"quality" gorm:"not null"` } +//-------------------------------------------------------------------------------------------------- +// 0.6.0 +//-------------------------------------------------------------------------------------------------- /* * * 大屏 @@ -189,3 +192,41 @@ type MVisual struct { Type string `gorm:"not null"` // 类型 Content string `gorm:"not null"` // 大屏的内容 } + +/* +* +* 通用分组 +* + */ +type MGenericGroup struct { + RulexModel + UUID string `gorm:"not null"` // 名称 + Name string `gorm:"not null"` // 名称 + Type string `gorm:"not null"` // 组的类型, DEVICE: 设备分组 + Parent string `gorm:"not null"` // 上级, 如果是0表示根节点 +} + +/* +* +* 分组表的绑定关系表 +* + */ +type MGenericGroupRelation struct { + RulexModel + UUID string `gorm:"not null"` + Gid string `gorm:"not null"` // 分组ID + Rid string `gorm:"not null"` // 被绑定方 +} + +/* +* +* 应用商店里面的各类协议脚本 +* + */ +type MProtocolApp struct { + RulexModel + UUID string `gorm:"not null"` // 名称 + Name string `gorm:"not null"` // 名称 + Type string `gorm:"not null"` // 类型: IN OUT DEVICE APP + Content string `gorm:"not null"` // 协议包的内容 +} diff --git a/plugin/http_server/protocolapp_api.go b/plugin/http_server/protocolapp_api.go new file mode 100644 index 000000000..253c4dc96 --- /dev/null +++ b/plugin/http_server/protocolapp_api.go @@ -0,0 +1,43 @@ +package httpserver + +import "github.com/gin-gonic/gin" + +/* +* +* 新建大屏 +* + */ +func CreateProtocolApp(c *gin.Context, hh *HttpApiServer) { +} + +/* +* +* 更新大屏 +* + */ +func UpdateProtocolApp(c *gin.Context, hh *HttpApiServer) { +} + +/* +* +* 删除大屏 +* + */ +func DeleteProtocolApp(c *gin.Context, hh *HttpApiServer) { +} + +/* +* +* 大屏列表 +* + */ +func ListProtocolApp(c *gin.Context, hh *HttpApiServer) { +} + +/* +* +* 大屏详情 +* + */ +func ProtocolAppDetail(c *gin.Context, hh *HttpApiServer) { +} diff --git a/plugin/http_server/service/group_service.go b/plugin/http_server/service/group_service.go new file mode 100644 index 000000000..9b373fe6d --- /dev/null +++ b/plugin/http_server/service/group_service.go @@ -0,0 +1,140 @@ +package service + +import ( + sqlitedao "github.com/hootrhino/rulex/plugin/http_server/dao/sqlite" + "github.com/hootrhino/rulex/plugin/http_server/model" +) + +// 获取GenericGroup列表 +func AllGenericGroup() []model.MGenericGroup { + m := []model.MGenericGroup{} + sqlitedao.Sqlite.DB().Find(&m) + return m +} + +/* +* + - 根据分组类型查询:DEVICE, VISUAL + +* +*/ +func FindByType(uuid, t string) ([]model.MVisual, []model.MDevice) { + sql := ` +WHERE uuid IN ( + SELECT m_generic_group_relations.rid + FROM m_generic_groups + LEFT JOIN + m_generic_group_relations ON (m_generic_groups.uuid = m_generic_group_relations.gid) + WHERE type = ? AND gid = ? +);` + if t == "VISUAL" { + m := []model.MVisual{} + sqlitedao.Sqlite.DB().Raw(`SELECT * FROM m_visuals `+sql, t, uuid).Find(&m) + return m, nil + } + if t == "DEVICE" { + m := []model.MDevice{} + sqlitedao.Sqlite.DB().Raw(`SELECT * FROM m_devices `+sql, t, uuid).Find(&m) + return nil, m + } + return nil, nil +} + +func GetGenericGroupWithUUID(uuid string) (*model.MGenericGroup, error) { + m := model.MGenericGroup{} + if err := sqlitedao.Sqlite.DB(). + Where("uuid=?", uuid). + First(&m).Error; err != nil { + return nil, err + } else { + return &m, nil + } +} + +// 删除GenericGroup +func DeleteGenericGroup(uuid string) error { + return sqlitedao.Sqlite.DB(). + Where("uuid=?", uuid). + Delete(&model.MGenericGroup{}).Error +} + +// 创建GenericGroup +func InsertGenericGroup(GenericGroup *model.MGenericGroup) error { + return sqlitedao.Sqlite.DB().Create(GenericGroup).Error +} + +// 更新GenericGroup +func UpdateGenericGroup(GenericGroup *model.MGenericGroup) error { + m := model.MGenericGroup{} + if err := sqlitedao.Sqlite.DB(). + Where("uuid=?", GenericGroup.UUID). + First(&m).Error; err != nil { + return err + } else { + sqlitedao.Sqlite.DB().Model(m).Updates(*GenericGroup) + return nil + } +} + +/* +* +* 将别的东西加入到分组里面 +* + */ +func BindResource(gid, rid string) error { + m := model.MGenericGroup{} + if err := sqlitedao.Sqlite.DB().Where("uuid=?", gid).First(&m).Error; err != nil { + return err + } + Relation := model.MGenericGroupRelation{ + Gid: m.UUID, + Rid: rid, + } + if err := sqlitedao.Sqlite.DB().Save(&Relation).Error; err != nil { + return err + } + return nil +} + +/* +* +* 取消分组绑定 +* + */ +func UnBindResource(gid, rid string) error { + model := model.MGenericGroupRelation{ + Gid: gid, + Rid: rid, + } + return sqlitedao.Sqlite.DB().Delete(&model).Error +} + +/* +* +* 检查是否绑定 +* + */ +func CheckBindResource(gid string) (uint, error) { + sql := `SELECT count(*) FROM m_generic_group_relations WHERE gid = ?;` + count := 0 + err := sqlitedao.Sqlite.DB().Raw(sql, gid).Find(&count).Error + if err != nil { + return 0, err + } + return uint(count), nil +} + +/* +* +* 检查是否重复了 +* + */ +func CheckAlreadyBinding(gid, rid string) (uint, error) { + sql := `SELECT count(*) FROM m_generic_group_relations WHERE gid = ? and rid = ?;` + count := 0 + err := sqlitedao.Sqlite.DB().Raw(sql, gid, rid).Find(&count).Error + if err != nil { + return 0, err + } + return uint(count), nil +} diff --git a/plugin/http_server/service/visual_screen_service.go b/plugin/http_server/service/visual_screen_service.go new file mode 100644 index 000000000..081722868 --- /dev/null +++ b/plugin/http_server/service/visual_screen_service.go @@ -0,0 +1,43 @@ +package service + +import ( + sqlitedao "github.com/hootrhino/rulex/plugin/http_server/dao/sqlite" + "github.com/hootrhino/rulex/plugin/http_server/model" +) + +// 获取Visual列表 +func AllVisual() []model.MVisual { + m := []model.MVisual{} + sqlitedao.Sqlite.DB().Find(&m) + return m + +} +func GetVisualWithUUID(uuid string) (*model.MVisual, error) { + m := model.MVisual{} + if err := sqlitedao.Sqlite.DB().Where("uuid=?", uuid).First(&m).Error; err != nil { + return nil, err + } else { + return &m, nil + } +} + +// 删除Visual +func DeleteVisual(uuid string) error { + return sqlitedao.Sqlite.DB().Where("uuid=?", uuid).Delete(&model.MVisual{}).Error +} + +// 创建Visual +func InsertVisual(Visual model.MVisual) error { + return sqlitedao.Sqlite.DB().Create(&Visual).Error +} + +// 更新Visual +func UpdateVisual(Visual model.MVisual) error { + m := model.MVisual{} + if err := sqlitedao.Sqlite.DB().Where("uuid=?", Visual.UUID).First(&m).Error; err != nil { + return err + } else { + sqlitedao.Sqlite.DB().Model(m).Updates(Visual) + return nil + } +} diff --git a/plugin/http_server/system_config_api.go b/plugin/http_server/system_config_api.go new file mode 100644 index 000000000..f946a87f7 --- /dev/null +++ b/plugin/http_server/system_config_api.go @@ -0,0 +1,81 @@ +package httpserver + +import "github.com/gin-gonic/gin" + +// 网络配置结构体 +type NetConfig struct { + DHCP string `json:"dhcp"` + Ip []string `json:"ip"` + Gateway string `json:"gateway"` + Names []string `json:"names"` + Version int `json:"version"` +} + +// 读取Ip状态(静态/动态) yaml +type T struct { + Network struct { + Ethernets struct { + Eth struct { + DHCP string `yaml:"dhcp4"` + Ip []string `yaml:"addresses"` + Gateway string `yaml:"gateway4"` + Names struct { + Ip []string `yaml:"addresses"` + } `yaml:"names"` + } `yaml:"eth0"` + } `yaml:"ethernets"` + Version int `json:"version"` + } `yaml:"network"` +} + +// 读取WIFI状态(静态/动态) yaml +type WT struct { + Network struct { + Ethernets struct { + Eth struct { + DHCP string `yaml:"dhcp4"` + Ip []string `yaml:"addresses"` + Gateway string `yaml:"gateway4"` + Names struct { + Ip []string `yaml:"addresses"` + } `yaml:"names"` + } `yaml:"wlan0"` + } `yaml:"ethernets"` + Version int `json:"version"` + } `yaml:"network"` +} + +// 主要是针对WIFI、时区、IP地址设置 + +/* +* +* WIFI +* + */ +func SetWifi(c *gin.Context, hh *HttpApiServer) { + type Form struct { + } + +} + +/* +* +* 设置时间、时区 +* + */ +func SetTime(c *gin.Context, hh *HttpApiServer) { + type Form struct { + } + +} + +/* +* +* 设置静态网络IP等 +* + */ +func SetStaticNetwork(c *gin.Context, hh *HttpApiServer) { + type Form struct { + } + +} diff --git a/plugin/http_server/user_api.go b/plugin/http_server/user_api.go index 8929fa955..a525a07af 100644 --- a/plugin/http_server/user_api.go +++ b/plugin/http_server/user_api.go @@ -60,7 +60,7 @@ func CreateUser(c *gin.Context, hh *HttpApiServer) { Password: md5Hash(form.Password), Description: form.Description, }) - c.JSON(common.HTTP_OK, common.Ok()) + c.JSON(common.HTTP_OK, common.Error400(err)) return } c.JSON(common.HTTP_OK, common.Error("user already exists:"+form.Username)) diff --git a/plugin/http_server/visual_api.go b/plugin/http_server/visual_api.go index 4dc4f2a18..683219d75 100644 --- a/plugin/http_server/visual_api.go +++ b/plugin/http_server/visual_api.go @@ -1,13 +1,44 @@ package httpserver -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + common "github.com/hootrhino/rulex/plugin/http_server/common" + "github.com/hootrhino/rulex/plugin/http_server/model" + "github.com/hootrhino/rulex/plugin/http_server/service" + "github.com/hootrhino/rulex/utils" +) + +type VisualVo struct { + UUID string `json:"uuid" validate:"required"` // 名称 + Name string `json:"name" validate:"required"` // 名称 + Type string `json:"type" validate:"required"` // 类型 + Content string `json:"content" validate:"required"` // 大屏的内容 +} /* * * 新建大屏 * */ + func CreateVisual(c *gin.Context, hh *HttpApiServer) { + vvo := VisualVo{} + if err := c.ShouldBindJSON(&vvo); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + MVisual := model.MVisual{ + UUID: utils.VisualUuid(), + Name: vvo.Name, + Type: vvo.Type, + Content: vvo.Content, + } + if err := service.InsertVisual(MVisual); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + c.JSON(common.HTTP_OK, common.Ok()) + } /* @@ -16,6 +47,23 @@ func CreateVisual(c *gin.Context, hh *HttpApiServer) { * */ func UpdateVisual(c *gin.Context, hh *HttpApiServer) { + vvo := VisualVo{} + if err := c.ShouldBindJSON(&vvo); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + MVisual := model.MVisual{ + UUID: vvo.UUID, + Name: vvo.Name, + Type: vvo.Type, + Content: vvo.Content, + } + if err := service.UpdateVisual(MVisual); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + return + } + c.JSON(common.HTTP_OK, common.Ok()) + } /* @@ -24,6 +72,12 @@ func UpdateVisual(c *gin.Context, hh *HttpApiServer) { * */ func DeleteVisual(c *gin.Context, hh *HttpApiServer) { + uuid, _ := c.GetQuery("uuid") + if err := service.DeleteVisual(uuid); err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + } + c.JSON(common.HTTP_OK, common.Ok()) + } /* @@ -32,6 +86,17 @@ func DeleteVisual(c *gin.Context, hh *HttpApiServer) { * */ func ListVisual(c *gin.Context, hh *HttpApiServer) { + visuals := []VisualVo{} + for _, vv := range service.AllVisual() { + visuals = append(visuals, VisualVo{ + UUID: vv.UUID, + Name: vv.Name, + Type: vv.Type, + Content: vv.Content, + }) + } + c.JSON(common.HTTP_OK, common.OkWithData(visuals)) + } /* @@ -40,4 +105,16 @@ func ListVisual(c *gin.Context, hh *HttpApiServer) { * */ func VisualDetail(c *gin.Context, hh *HttpApiServer) { + uuid, _ := c.GetQuery("uuid") + mVisual, err := service.GetVisualWithUUID(uuid) + if err != nil { + c.JSON(common.HTTP_OK, common.Error400(err)) + } + vo := VisualVo{ + UUID: mVisual.UUID, + Name: mVisual.Name, + Type: mVisual.Type, + Content: mVisual.Content, + } + c.JSON(common.HTTP_OK, common.OkWithData(vo)) } diff --git a/rulexlib/device_lib.go b/rulexlib/device_lib.go index 0b25ac6db..89f2732f3 100644 --- a/rulexlib/device_lib.go +++ b/rulexlib/device_lib.go @@ -23,7 +23,7 @@ func ReadDevice(rx typex.RuleX) func(*lua.LState) int { cmd := l.ToString(3) Device := rx.GetDevice(devUUID) if Device != nil { - if Device.State == typex.DEV_UP { + if Device.Device.Status() == typex.DEV_UP { n, err := Device.Device.OnRead([]byte(cmd), deviceReadBuffer) if err != nil { glogger.GLogger.Error(err) @@ -40,10 +40,12 @@ func ReadDevice(rx typex.RuleX) func(*lua.LState) int { l.Push(lua.LString("device down:" + devUUID)) return 2 } + } else { + l.Push(lua.LNil) + l.Push(lua.LString("device not exists:" + devUUID)) + return 2 } - l.Push(lua.LNil) - l.Push(lua.LString("device not exists:" + devUUID)) - return 2 + } } @@ -60,7 +62,7 @@ func WriteDevice(rx typex.RuleX) func(*lua.LState) int { data := l.ToString(4) Device := rx.GetDevice(devUUID) if Device != nil { - if Device.State == typex.DEV_UP { + if Device.Device.Status() == typex.DEV_UP { n, err := Device.Device.OnWrite([]byte(cmd), []byte(data)) if err != nil { glogger.GLogger.Error(err) @@ -97,7 +99,7 @@ func CtrlDevice(rx typex.RuleX) func(*lua.LState) int { data := l.ToString(4) Device := rx.GetDevice(devUUID) if Device != nil { - if Device.State == typex.DEV_UP { + if Device.Device.Status() == typex.DEV_UP { result, err := Device.Device.OnCtrl([]byte(cmd), []byte(data)) if err != nil { glogger.GLogger.Error(err) diff --git a/test/aibase_test1.go b/test/aibase_test1.go index 9d19ca784..6766475e4 100644 --- a/test/aibase_test1.go +++ b/test/aibase_test1.go @@ -26,7 +26,7 @@ func Test_AIBASE_ANN_MNIST(t *testing.T) { }) ctx, cancelF := typex.NewCCTX() if err := engine.LoadInEndWithCtx(grpcInend, ctx, cancelF); err != nil { - t.common.Error("grpcInend load failed:", err) + t.Error("grpcInend load failed:", err) } rule := typex.NewRule(engine, "uuid", @@ -55,7 +55,7 @@ func Test_AIBASE_ANN_MNIST(t *testing.T) { }`, `function Failed(error) print("[LUA Failed Callback]", error) end`) if err := engine.LoadRule(rule); err != nil { - t.common.Error(err) + t.Error(err) } conn, err := grpc.Dial("127.0.0.1:2581", grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -69,7 +69,7 @@ func Test_AIBASE_ANN_MNIST(t *testing.T) { Value: `{"value":"0298010d"}`, }) if err != nil { - t.common.Error(err) + t.Error(err) } t.Logf("Rulex Rpc Call Result ====>>: %v --%v", resp.GetMessage(), i) diff --git a/test/apps/custom_tcp.lua b/test/apps/custom_tcp.lua new file mode 100644 index 000000000..cd0c64f58 --- /dev/null +++ b/test/apps/custom_tcp.lua @@ -0,0 +1,17 @@ +AppNAME = "Test1" +AppVERSION = "1.0.0" +AppDESCRIPTION = "" +-- +-- Main +-- + +function Main(arg) + while true do + for i = 1, 5, 1 do + local result, err = applib:CtrlDevice('DEVICEda7ea0bdcf364ca7b7dda5e0cca647d7', "0" .. i) + print("|*** CtrlDevice [0x01] result=>", result, err) + applib:Sleep(50) + end + end + return 0 +end diff --git a/test/appstack_httpapi_test.go b/test/appstack_httpapi_test.go index 2b75def4c..bdecb1785 100644 --- a/test/appstack_httpapi_test.go +++ b/test/appstack_httpapi_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/go-playground/assert/v2" - httpserver "github.com/hootrhino/rulex/plugin/http_server" + "github.com/hootrhino/rulex/plugin/http_server/model" ) /* @@ -39,8 +39,8 @@ func UT_createApp(t *testing.T) string { } t.Log("UT_createApp: ", string(output)) // - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 1, len(mApp)) t.Log(mApp[0].UUID) @@ -65,8 +65,8 @@ func UT_updateApp(t *testing.T, uuid string) { t.Fatal(err) } t.Log("UT_updateApp: ", string(output)) - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 1, len(mApp)) t.Log(mApp[0].UUID) @@ -82,8 +82,8 @@ func UT_deleteApp(t *testing.T, uuid string) { } t.Log("UT_deleteApp: ", string(output)) // - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 0, len(mApp)) } diff --git a/test/bench_test.go b/test/bench_test.go index ada9bbeb5..e1bdc6d9b 100644 --- a/test/bench_test.go +++ b/test/bench_test.go @@ -28,7 +28,7 @@ func TestRunLuaBench(t *testing.T) { coroutine, _ := luaVM.NewThread() state, err2, _ := luaVM.Resume(coroutine, f.(*lua.LFunction), lua.LNumber(1), lua.LNumber(1)) if state == lua.ResumeError { - t.common.Error(err2) + t.Error(err2) } } t.Log("luaVM.Resume:", time.Now().UnixNano()-t2, "ns") diff --git a/test/custom_tcp_server_test.go b/test/custom_tcp_server_test.go new file mode 100644 index 000000000..fa64460bf --- /dev/null +++ b/test/custom_tcp_server_test.go @@ -0,0 +1,175 @@ +package test + +import ( + "fmt" + "net" + "testing" + "time" + + httpserver "github.com/hootrhino/rulex/plugin/http_server" + "github.com/hootrhino/rulex/typex" +) + +/* +* + - 自定义协议服务,一共测试5个短报文,前面表示序号,后面表示数据,无任何意义 + - 当Lua调用接口请求的时候,会返回这些报文 + // * 0x01:01 + // * 0x02:02 03 04 + // * 0x03:0A 0B 0C 0D + // * 0x04:11 22 33 44 55 + // * 0x05:AA BB CC DD EE FF + +* +*/ +func StartCustomTCPServer() { + listener, err := net.Listen("tcp", ":3399") + if err != nil { + fmt.Println("Error listening:", err) + return + } + fmt.Println("listening:", listener.Addr().String()) + + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting:", err) + continue + } + go handleConnection(conn) + } +} + +func handleConnection(conn net.Conn) { + defer conn.Close() + data := make([]byte, 10) + for { + n, err := conn.Read(data) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Received Request From Custom TCP:", data[:n]) + if n > 0 { + if data[0] == 1 { + conn.Write([]byte{0x01}) + } + if data[0] == 2 { + conn.Write([]byte{0x02, 0x03, 0x04}) + } + if data[0] == 3 { + conn.Write([]byte{0x0A, 0x0B, 0x0C, 0x0D}) + } + if data[0] == 4 { + conn.Write([]byte{0x11, 0x22, 0x33, 0x44, 0x55}) + } + if data[0] == 5 { + conn.Write([]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}) + } + } + } + +} + +/* +* +* 模拟请求 +* + */ +func CustomTCPRequestEmu() { + conn, err := net.Dial("tcp", "127.0.0.1:3399") + if err != nil { + fmt.Println("Error connecting:", err) + return + } + defer conn.Close() + data := make([]byte, 10) + + conn.Write([]byte{1}) + fmt.Println("Send>>>>:", 1) + conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + conn.Read(data) + fmt.Println("Read>>>>:", data) + // + conn.Write([]byte{2}) + fmt.Println("Send>>>>:", 2) + conn.Read(data) + fmt.Println("Read>>>>:", data) + // + conn.Write([]byte{3}) + fmt.Println("Send>>>>:", 3) + conn.Read(data) + fmt.Println("Read>>>>:", data) + // + conn.Write([]byte{4}) + fmt.Println("Send>>>>:", 4) + conn.Read(data) + fmt.Println("Read>>>>:", data) + // + conn.Write([]byte{5}) + fmt.Println("Send>>>>:", 5) + conn.Read(data) + fmt.Println("Read>>>>:", data) +} + +// // go test -timeout 30s -run ^TestCustomTCP github.com/hootrhino/rulex/test -v -count=1 +// func TestCustomTCP(t *testing.T) { +// go StartCustomTCPServer() +// time.Sleep(1000 * time.Millisecond) +// CustomTCPRequestEmu() +// } + +/* +* +* Test_data_to_http +* + */ +func TestCustomTCP(t *testing.T) { + engine := RunTestEngine() + engine.Start() + // go StartCustomTCPServer() + hh := httpserver.NewHttpApiServer() + + // HttpApiServer loaded default + if err := engine.LoadPlugin("plugin.http_server", hh); err != nil { + t.Fatal("Rule load failed:", err) + } + dev1 := typex.NewDevice(typex.GENERIC_PROTOCOL, + "UART", "UART", map[string]interface{}{ + "commonConfig": map[string]interface{}{ + "transport": "rawtcp", + "retryTime": 5, + "frequency": 100, + }, + "hostConfig": map[string]interface{}{ + "host": "127.0.0.1", + "port": 3399, + }, + "uartConfig": map[string]interface{}{ + "baudRate": 9600, + "dataBits": 8, + "parity": "N", + "stopBits": 1, + "uart": "COM3", + "timeout": 2000, + }, + }) + dev1.UUID = "Test1" + ctx1, cancel := typex.NewCCTX() + if err := engine.LoadDeviceWithCtx(dev1, ctx1, cancel); err != nil { + t.Fatal("Test1 load failed:", err) + } + + if err := engine.LoadApp(typex.NewApplication( + "Test1", "Name", "Version", "./apps/custom_tcp.lua")); err != nil { + t.Fatal("app Load failed:", err) + return + } + if err2 := engine.StartApp("Test1"); err2 != nil { + t.Fatal("app Load failed:", err2) + return + } + t.Log(engine.SnapshotDump()) + time.Sleep(10 * time.Second) + engine.Stop() +} diff --git a/test/device_485ther_gw_test.go b/test/device_485ther_gw_test.go index 9b315c05b..c35da377f 100644 --- a/test/device_485ther_gw_test.go +++ b/test/device_485ther_gw_test.go @@ -58,7 +58,7 @@ func Test_modbus_485_sensor_gateway(t *testing.T) { RTU485Device.UUID = "RTU485Device1" ctx, cancelF := typex.NewCCTX() if err := engine.LoadDeviceWithCtx(RTU485Device, ctx, cancelF); err != nil { - t.common.Error("RTU485Device load failed:", err) + t.Error("RTU485Device load failed:", err) } mqttOutEnd := typex.NewOutEnd( "MQTT", @@ -76,7 +76,7 @@ func Test_modbus_485_sensor_gateway(t *testing.T) { mqttOutEnd.UUID = "mqttOutEnd-iothub" ctx1, cancelF1 := typex.NewCCTX() if err := engine.LoadOutEndWithCtx(mqttOutEnd, ctx1, cancelF1); err != nil { - t.common.Error("mqttOutEnd load failed:", err) + t.Error("mqttOutEnd load failed:", err) } rule := typex.NewRule(engine, "uuid", @@ -105,7 +105,7 @@ end} `, `function Failed(error) print("[LUA Failed Callback]", error) end`) if err := engine.LoadRule(rule); err != nil { - t.common.Error(err) + t.Error(err) } time.Sleep(25 * time.Second) engine.Stop() diff --git a/test/device_generic_camera_stream_test.go b/test/device_generic_camera_stream_test.go index 9a83ae9d9..a2af22dbc 100644 --- a/test/device_generic_camera_stream_test.go +++ b/test/device_generic_camera_stream_test.go @@ -12,12 +12,12 @@ import ( /* * -* 摄像头拉流 +* 本地摄像头拉流 * */ -// go test -timeout 30s -run ^Test_Generic_camera github.com/hootrhino/rulex/test -v -count=1 +// go test -timeout 30s -run ^Test_Generic_Local_camera github.com/hootrhino/rulex/test -v -count=1 -func Test_Generic_camera(t *testing.T) { +func Test_Generic_Local_camera(t *testing.T) { engine := RunTestEngine() engine.Start() @@ -43,3 +43,36 @@ func Test_Generic_camera(t *testing.T) { time.Sleep(25 * time.Second) engine.Stop() } + +/* +* +* RTSP 拉流 +* + */ +// go test -timeout 30s -run ^Test_Generic_RTSP_camera github.com/hootrhino/rulex/test -v -count=1 + +func Test_Generic_RTSP_camera(t *testing.T) { + engine := RunTestEngine() + engine.Start() + + hh := httpserver.NewHttpApiServer() + if err := engine.LoadPlugin("plugin.http_server", hh); err != nil { + glogger.GLogger.Fatal("Rule load failed:", err) + t.Fatal(err) + } + GENERIC_CAMERA := typex.NewDevice(typex.GENERIC_CAMERA, + "GENERIC_CAMERA", "GENERIC_CAMERA", map[string]interface{}{ + "maxThread": 10, + "inputMode": "RTSP", + "device": "video0", + "rtspUrl": "rtsp://192.168.0.101:554/av0_0", + "outputMode": "JPEG_STREAM", + "outputAddr": "0.0.0.0:2599", + }) + ctx, cancelF := typex.NewCCTX() + if err := engine.LoadDeviceWithCtx(GENERIC_CAMERA, ctx, cancelF); err != nil { + t.Fatal(err) + } + time.Sleep(25 * time.Second) + engine.Stop() +} diff --git a/test/device_s1200plc_test.go b/test/device_s1200plc_test.go index c73db3c51..c0aefca82 100644 --- a/test/device_s1200plc_test.go +++ b/test/device_s1200plc_test.go @@ -128,7 +128,7 @@ func Test_RULEX_WITH_S1200PLC(t *testing.T) { S1200PLC.UUID = "S1200PLC" ctx, cancelF := typex.NewCCTX() if err := engine.LoadDeviceWithCtx(S1200PLC, ctx, cancelF); err != nil { - t.common.Error("S1200PLC load failed:", err) + t.Error("S1200PLC load failed:", err) } // // 透传到内部EMQX @@ -147,7 +147,7 @@ func Test_RULEX_WITH_S1200PLC(t *testing.T) { EMQX_BROKER.UUID = "EMQX_BROKER" ctx1, cancelF1 := typex.NewCCTX() if err := engine.LoadOutEndWithCtx(EMQX_BROKER, ctx1, cancelF1); err != nil { - t.common.Error("mqttOutEnd load failed:", err) + t.Error("mqttOutEnd load failed:", err) } // // 加载一个规则 rule1 := typex.NewRule(engine, @@ -165,7 +165,7 @@ func Test_RULEX_WITH_S1200PLC(t *testing.T) { end }`, `function Failed(error) print("[EMQX_BROKER Failed Callback]", error) end`) if err := engine.LoadRule(rule1); err != nil { - t.common.Error(err) + t.Error(err) } glogger.GLogger.Warn("Received stop signal:", s) diff --git a/test/device_th_485_sensor_data_parse_test.go b/test/device_th_485_sensor_data_parse_test.go index 57a1522c2..4e40debba 100644 --- a/test/device_th_485_sensor_data_parse_test.go +++ b/test/device_th_485_sensor_data_parse_test.go @@ -35,7 +35,7 @@ func Test_modbus_485_sensor_data_parse(t *testing.T) { }) ctx, cancelF := typex.NewCCTX() if err := engine.LoadInEndWithCtx(grpcInend, ctx, cancelF); err != nil { - t.common.Error("grpcInend load failed:", err) + t.Error("grpcInend load failed:", err) } rule := typex.NewRule(engine, "uuid", @@ -66,7 +66,7 @@ func Test_modbus_485_sensor_data_parse(t *testing.T) { }`, `function Failed(error) print("[LUA Failed Callback]", error) end`) if err := engine.LoadRule(rule); err != nil { - t.common.Error(err) + t.Error(err) } conn, err := grpc.Dial("127.0.0.1:2581", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { @@ -88,7 +88,7 @@ func Test_modbus_485_sensor_data_parse(t *testing.T) { `, }) if err != nil { - t.common.Error(err) + t.Error(err) } t.Logf("Rulex Rpc Call Result ====>>: %v --%v", resp.GetMessage(), i) diff --git a/test/http_api_device_snmp_curd_test.go b/test/http_api_device_snmp_curd_test.go index 47fafc93c..2c468ce07 100644 --- a/test/http_api_device_snmp_curd_test.go +++ b/test/http_api_device_snmp_curd_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/go-playground/assert/v2" - httpserver "github.com/hootrhino/rulex/plugin/http_server" + "github.com/hootrhino/rulex/plugin/http_server/model" ) /* @@ -39,15 +39,15 @@ func UT_createDevice(t *testing.T) string { } t.Log("UT_createDevice: ", string(output)) // - LoadDB() - mdevice := []httpserver.MDevice{} - unitTestDB.Raw("SELECT * FROM m_devices").Find(&mdevice) - assert.Equal(t, 1, len(mdevice)) - t.Log(mdevice[0].UUID) - assert.Equal(t, mdevice[0].Name, "GENERIC_SNMP") - assert.Equal(t, mdevice[0].Description, "GENERIC_SNMP") - assert.Equal(t, mdevice[0].Type, "GENERIC_SNMP") - return mdevice[0].UUID + LoadUnitTestDB() + mDevice := []model.MDevice{} + unitTestDB.Raw("SELECT * FROM m_devices").Find(&mDevice) + assert.Equal(t, 1, len(mDevice)) + t.Log(mDevice[0].UUID) + assert.Equal(t, mDevice[0].Name, "GENERIC_SNMP") + assert.Equal(t, mDevice[0].Description, "GENERIC_SNMP") + assert.Equal(t, mDevice[0].Type, "GENERIC_SNMP") + return mDevice[0].UUID } func UT_updateDevice(t *testing.T, uuid string) { // 通过接口创建一个设备 @@ -59,14 +59,14 @@ func UT_updateDevice(t *testing.T, uuid string) { t.Fatal(err) } t.Log("UT_updateDevice: ", string(output)) - LoadDB() - mdevice := []httpserver.MDevice{} - unitTestDB.Raw("SELECT * FROM m_devices").Find(&mdevice) - assert.Equal(t, 1, len(mdevice)) - t.Log(mdevice[0].UUID) - assert.Equal(t, mdevice[0].Name, "GENERIC_SNMP_NEW") - assert.Equal(t, mdevice[0].Description, "GENERIC_SNMP_NEW") - assert.Equal(t, mdevice[0].Type, "GENERIC_SNMP") + LoadUnitTestDB() + mDevice := []model.MDevice{} + unitTestDB.Raw("SELECT * FROM m_devices").Find(&mDevice) + assert.Equal(t, 1, len(mDevice)) + t.Log(mDevice[0].UUID) + assert.Equal(t, mDevice[0].Name, "GENERIC_SNMP_NEW") + assert.Equal(t, mDevice[0].Description, "GENERIC_SNMP_NEW") + assert.Equal(t, mDevice[0].Type, "GENERIC_SNMP") } func UT_deleteDevice(t *testing.T, uuid string) { // 删除一个设备 @@ -76,8 +76,8 @@ func UT_deleteDevice(t *testing.T, uuid string) { } t.Log("UT_deleteDevice: ", string(output)) // - LoadDB() - mdevice := []httpserver.MDevice{} - unitTestDB.Raw("SELECT * FROM m_devices").Find(&mdevice) - assert.Equal(t, 0, len(mdevice)) + LoadUnitTestDB() + mDevice := []model.MDevice{} + unitTestDB.Raw("SELECT * FROM m_devices").Find(&mDevice) + assert.Equal(t, 0, len(mDevice)) } diff --git a/test/init_data_test.go b/test/init_data_test.go index c8b974ffc..c9fe51d05 100644 --- a/test/init_data_test.go +++ b/test/init_data_test.go @@ -8,6 +8,7 @@ import ( "github.com/hootrhino/rulex/engine" "github.com/hootrhino/rulex/glogger" httpserver "github.com/hootrhino/rulex/plugin/http_server" + "github.com/hootrhino/rulex/plugin/http_server/model" "github.com/hootrhino/rulex/typex" ) @@ -25,7 +26,7 @@ func TestInitData(t *testing.T) { "port": "2581", }) b1, _ := json.Marshal(grpcInend.Config) - hh.InsertMInEnd(&httpserver.MInEnd{ + hh.InsertMInEnd(&model.MInEnd{ UUID: grpcInend.UUID, Type: grpcInend.Type.String(), Name: grpcInend.Name, @@ -37,7 +38,7 @@ func TestInitData(t *testing.T) { "port": "2582", }) b2, _ := json.Marshal(coapInend.Config) - hh.InsertMInEnd(&httpserver.MInEnd{ + hh.InsertMInEnd(&model.MInEnd{ UUID: coapInend.UUID, Type: coapInend.Type.String(), Name: coapInend.Name, @@ -49,7 +50,7 @@ func TestInitData(t *testing.T) { "port": "2583", }) b3, _ := json.Marshal(httpInend.Config) - hh.InsertMInEnd(&httpserver.MInEnd{ + hh.InsertMInEnd(&model.MInEnd{ UUID: httpInend.UUID, Type: httpInend.Type.String(), Name: httpInend.Name, @@ -62,7 +63,7 @@ func TestInitData(t *testing.T) { "port": "2584", }) b4, _ := json.Marshal(udpInend.Config) - hh.InsertMInEnd(&httpserver.MInEnd{ + hh.InsertMInEnd(&model.MInEnd{ UUID: udpInend.UUID, Type: udpInend.Type.String(), Name: udpInend.Name, @@ -85,7 +86,7 @@ func TestInitData(t *testing.T) { end }`, `function Failed(error) print("[LUA Failed]OK", error) end`) - hh.InsertMRule(&httpserver.MRule{ + hh.InsertMRule(&model.MRule{ Name: rule.Name, Description: rule.Description, FromSource: rule.FromSource, diff --git a/test/rulex_lua_lib_test.go b/test/rulex_lua_lib_test.go index 9ebfa0be1..a90f7df58 100644 --- a/test/rulex_lua_lib_test.go +++ b/test/rulex_lua_lib_test.go @@ -23,7 +23,7 @@ func Test_rulex_base_lib(t *testing.T) { }) ctx, cancelF := typex.NewCCTX() // ,ctx, cancelF if err := engine.LoadInEndWithCtx(grpcInend, ctx, cancelF); err != nil { - t.common.Error("grpcInend load failed:", err) + t.Error("grpcInend load failed:", err) } // // Load Rule @@ -58,11 +58,11 @@ func Test_rulex_base_lib(t *testing.T) { }`, `function Failed(error) print("[Failed Callback]", error) end`) if err := engine.LoadRule(rule); err != nil { - t.common.Error(err) + t.Error(err) } conn, err := grpc.Dial("127.0.0.1:2581", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - t.common.Error(err) + t.Error(err) } client := rulexrpc.NewRulexRpcClient(conn) @@ -75,7 +75,7 @@ func Test_rulex_base_lib(t *testing.T) { ]`}) if err != nil { - t.common.Error(err) + t.Error(err) } t.Logf("Rulex Rpc Call Result ====>>: %v", resp.GetMessage()) diff --git a/test/siemens_s7_test.go b/test/siemens_s7_test.go index 779fd1403..f14127693 100644 --- a/test/siemens_s7_test.go +++ b/test/siemens_s7_test.go @@ -14,7 +14,7 @@ func Test_server(t *testing.T) { server.SetDB(10, []uint16{11, 22, 33, 44, 55, 66, 77, 88, 99, 100}) err := server.Listen("0.0.0.0:1800", 0, 1) if err != nil { - t.common.Error(err) + t.Error(err) return } client(t) @@ -26,13 +26,13 @@ func client(t *testing.T) { defer handler.Close() if err := handler.Connect(); err != nil { - t.common.Error(err) + t.Error(err) return } client := gos7.NewClient(handler) dataBuf := make([]byte, 10) if err := client.AGReadDB(10, 0, 10, dataBuf); err != nil { - t.common.Error(err) + t.Error(err) return } t.Log("client.AGReadDB =>", dataBuf) @@ -49,13 +49,13 @@ func Test_readDB(t *testing.T) { defer handler.Close() if err := handler.Connect(); err != nil { - t.common.Error(err) + t.Error(err) return } client := gos7.NewClient(handler) dataBuf := make([]byte, 10) if err := client.AGReadDB(10, 0, 10, dataBuf); err != nil { - t.common.Error(err) + t.Error(err) return } t.Log("client.AGReadDB =>", dataBuf) diff --git a/test/sqlite_utils.go b/test/sqlite_utils.go index 1b0c356ac..4484f4d67 100644 --- a/test/sqlite_utils.go +++ b/test/sqlite_utils.go @@ -12,7 +12,7 @@ import ( */ var unitTestDB *gorm.DB -func LoadDB() { +func LoadUnitTestDB() { var err error unitTestDB, err = gorm.Open(sqlite.Open("unitest.db"), &gorm.Config{}) if err != nil { diff --git a/test/target_data_tohttp_test.go b/test/target_data_tohttp_test.go index cceed9bb6..a951893e7 100644 --- a/test/target_data_tohttp_test.go +++ b/test/target_data_tohttp_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-playground/assert/v2" httpserver "github.com/hootrhino/rulex/plugin/http_server" + "github.com/hootrhino/rulex/plugin/http_server/model" "github.com/hootrhino/rulex/typex" ) @@ -87,8 +88,8 @@ func _createTestApp(t *testing.T) string { } t.Log("UT_createApp: ", string(output)) // - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 1, len(mApp)) t.Log(mApp[0].UUID) @@ -108,8 +109,8 @@ func _updateTestApp(t *testing.T, uuid string) { t.Fatal(err) } t.Log("UT_updateApp: ", string(output)) - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 1, len(mApp)) t.Log("APP UUID ==> ", mApp[0].UUID) @@ -128,8 +129,8 @@ func _deleteTestApp(t *testing.T, uuid string) { } t.Log("UT_deleteApp: ", string(output)) // - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 0, len(mApp)) } diff --git a/test/target_data_toudp_test.go b/test/target_data_toudp_test.go index 5a3853671..e484bebb8 100644 --- a/test/target_data_toudp_test.go +++ b/test/target_data_toudp_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-playground/assert/v2" httpserver "github.com/hootrhino/rulex/plugin/http_server" + "github.com/hootrhino/rulex/plugin/http_server/model" "github.com/hootrhino/rulex/typex" ) @@ -101,8 +102,8 @@ func _createTestApp_1(t *testing.T) string { } t.Log("UT_createApp: ", string(output)) // - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 1, len(mApp)) t.Log(mApp[0].UUID) @@ -122,8 +123,8 @@ func _updateTestApp_1(t *testing.T, uuid string) { t.Fatal(err) } t.Log("UT_updateApp: ", string(output)) - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 1, len(mApp)) t.Log("APP UUID ==> ", mApp[0].UUID) @@ -142,8 +143,8 @@ func _deleteTestApp_1(t *testing.T, uuid string) { } t.Log("UT_deleteApp: ", string(output)) // - LoadDB() - mApp := []httpserver.MApp{} + LoadUnitTestDB() + mApp := []model.MApp{} unitTestDB.Raw("SELECT * FROM m_apps").Find(&mApp) assert.Equal(t, 0, len(mApp)) } diff --git a/test/yk8_relay_controller_test.go b/test/yk8_relay_controller_test.go index 2ebc4f152..3cea09f72 100644 --- a/test/yk8_relay_controller_test.go +++ b/test/yk8_relay_controller_test.go @@ -68,14 +68,14 @@ func TestRTU_YK081(t *testing.T) { handler.Logger = log.New(os.Stdout, "rtu: ", log.LstdFlags) if err := handler.Connect(); err != nil { - t.common.Error(err) + t.Error(err) return } defer handler.Close() client := modbus.NewClient(handler) if results, err := client.ReadCoils(0x00, 0x08); err != nil { - t.common.Error(err) + t.Error(err) return } else { t.Log("===> ", results) diff --git a/typex/version.go b/typex/version.go index f3dc240d0..1087b2e2d 100644 --- a/typex/version.go +++ b/typex/version.go @@ -12,7 +12,7 @@ type Version struct { var DefaultVersion = Version{ Version: `v0.5.2`, - ReleaseTime: "2023-07-19 11:31:25", + ReleaseTime: "2023-08-08 15:05:37", } var Banner = ` ** Welcome to RULEX framework world <'_'> diff --git a/utils/uuid_util.go b/utils/uuid_util.go index e9b65057f..4c1ef091a 100644 --- a/utils/uuid_util.go +++ b/utils/uuid_util.go @@ -26,6 +26,12 @@ func DeviceUuid() string { func PluginUuid() string { return MakeUUID("PLUGIN") } +func VisualUuid() string { + return MakeUUID("VISUAL") +} +func GroupUuid() string { + return MakeUUID("GROUP") +} func AppUuid() string { return MakeUUID("APP") }