terry 8 місяців тому
батько
коміт
0380e991ac
10 змінених файлів з 301 додано та 60 видалено
  1. 9 6
      config.yaml
  2. 4 6
      lc/IDevice.go
  3. 106 0
      lc/camera_event.go
  4. 8 0
      lc/model/screen.go
  5. 28 0
      lc/model/speaker.go
  6. 11 12
      lc/radio_event.go
  7. 46 16
      lc/server.go
  8. 75 0
      lc/speaker.go
  9. 1 9
      main.go
  10. 13 11
      util/config.go

+ 9 - 6
config.yaml

@@ -1,17 +1,20 @@
+service:
+  support_radio: false
+  support_camera: true
+  support_speaker: true
+  support_led: false
 hikServer:
-  addr : ":8850"
-  path : "/event"
+  addr: ":8850"
+  path: "/event"
 radios:
-  -
-    port: "/dev/ttymxc4"
+  - port: "/dev/ttymxc4"
     name: "对向"
     branch: 1
   - port: "/dev/ttymxc6"
     name: "同向"
     branch: 0
 outputDevices:
-  -
-    name: "主路1输出设备"
+  - name: "主路1输出设备"
     screen_ip: "192.168.1.200"
     screen_port: "5005"
     branch: 0

+ 4 - 6
lc/IDevice.go

@@ -1,9 +1,5 @@
 package lc
 
-import (
-	"lc-smartX/util"
-)
-
 type Notifier interface {
 	Notify()
 }
@@ -41,12 +37,14 @@ type IDevice interface {
 }
 
 type IntersectionDevice struct {
-	Info   util.OutputDevice
-	Screen Screener
+	Info    interface{}
+	Screen  Screener
+	Speaker Loudspeaker
 }
 
 func (id *IntersectionDevice) Call() {
 	id.Screen.Display(1)
+	id.Speaker.Speak()
 }
 func (id *IntersectionDevice) Rollback() {
 	id.Screen.Display(0)

+ 106 - 0
lc/camera_event.go

@@ -0,0 +1,106 @@
+package lc
+
+import (
+	"encoding/xml"
+	"github.com/sirupsen/logrus"
+	"io"
+	"io/ioutil"
+	"lc-smartX/lc/model"
+	"lc-smartX/util"
+	"lc-smartX/util/gopool"
+	"log"
+	"mime/multipart"
+	"net/http"
+	"strings"
+	"time"
+)
+
+func StartCameraEventServer() *CameraServer {
+	server := &CameraServer{Cameras: util.Config.Cameras, Notifiers: make(map[string]Notifier, 4)}
+	server.start()
+	return server
+}
+
+type CameraServer struct {
+	Cameras   []model.CameraInfo
+	Notifiers map[string]Notifier
+}
+
+func (s *CameraServer) start() {
+	http.HandleFunc(util.Config.HikServer.Path, s.Handler)
+	logrus.Fatal("事件监听服务启动失败:", util.Config.HikServer.Addr, ", error:", http.ListenAndServe(util.Config.HikServer.Addr, nil))
+}
+
+func (s *CameraServer) RegisterCallback(branch byte, notifier Notifier) {
+	for _, camera := range s.Cameras {
+		//关联主路led屏和支路摄像头;关联支路led屏和主路摄像头
+		if branch == 0 && camera.Branch == 1 || branch == 1 && camera.Branch == 0 {
+			s.Notifiers[camera.IP] = notifier
+		}
+	}
+}
+
+func (s *CameraServer) Callback(ip string) {
+	notifier, ok := s.Notifiers[ip]
+	if !ok {
+		logrus.Errorf("回调函数注册表没有该ip:%s", ip)
+		return
+	}
+	notifier.Notify()
+	logrus.Debugf("camera [%s] Callback", ip)
+}
+
+func (s *CameraServer) Handler(w http.ResponseWriter, r *http.Request) {
+	gopool.Go(func() {
+		//监听主机应答固定,直接先应答
+		w.WriteHeader(200)
+		w.Header().Add("Date", time.Now().String())
+		w.Header().Add("Connection", "keep-alive")
+	})
+	//1.
+	contentType := r.Header.Get("Content-Type")
+	if strings.Contains(contentType, "application/xml") {
+		bytes, err := io.ReadAll(r.Body)
+		if err != nil {
+			return
+		}
+		var event model.EventNotificationAlert
+		err = xml.Unmarshal(bytes, &event)
+		if err != nil {
+			return
+		}
+		//发送事件通知
+		s.Callback(event.IpAddress)
+	} else if strings.Contains(contentType, "multipart/form-data") {
+		s.HandleMultipart(r)
+	}
+}
+
+// 处理多文件事件
+func (s *CameraServer) HandleMultipart(r *http.Request) {
+	multipartReader := multipart.NewReader(r.Body, "boundary")
+	// 循环读取每个 part
+	var event model.EventNotificationAlert
+	for {
+		part, err := multipartReader.NextPart()
+		//defer part.Close()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Println("Failed to read part:", err)
+			return
+		}
+		if part.FormName() != "linedetectionImage" || !strings.Contains(part.FormName(), "Image") {
+			//不含图片的xml部分数据
+			xmlData, err := ioutil.ReadAll(part)
+			if err != nil {
+				return
+			}
+			xml.Unmarshal(xmlData, &event)
+			continue
+		}
+	}
+	//发送事件通知
+	s.Callback(event.IpAddress)
+}

+ 8 - 0
lc/model/screen.go

@@ -0,0 +1,8 @@
+package model
+
+type ScreenInfo struct {
+	Name   string `yaml:"name"`
+	Ip     string `yaml:"ip"`
+	Port   string `yaml:"port"`
+	Branch byte   `yaml:"branch"`
+}

+ 28 - 0
lc/model/speaker.go

@@ -0,0 +1,28 @@
+package model
+
+type SpeakerInfo struct {
+	Name   string `yaml:"name"`
+	Ip     string `yaml:"ip"`
+	Port   string `yaml:"port"`
+	Branch byte   `yaml:"branch"`
+}
+
+type PlayReq struct {
+	Url    string //要播放的⽹络⾳频 http/https/rtsp等⾳频地址
+	Text   string //要播放的⽂本内容
+	Vcn    string // 发⾳⼈ xiaofeng xiaoyan
+	Speed  int    //发⾳速度 0-100 默认50
+	Volume int    //⾳量 0-100 默认50
+	Rdn    string //数字发⾳ 0 数值优先, 1 完全数值, 2 完全字符串, 3 字符串优先 默认2
+	Rcn    string //数字1 的中⽂发⾳ 0:表示发⾳为yao 1:表示发⾳为yi 默认0
+	Reg    byte   //英⽂发⾳ 0:⾃动识别英语单词; 1:逐个字⺟发⾳
+	Sync   bool   //true: 同步模式,语⾳播放完毕后再响应; false:即时响应(不等待播放完成)
+	Queue  bool   //true: 队列模式,如果当前有语⾳在播放,则加到队列排队播放
+	Loop   LoopInfo
+}
+
+type LoopInfo struct {
+	Duration int // 循环(重复)播放时⻓(秒)选填
+	Times    int // 循环(重复)播放次数(次)选填
+	Gap      int // 循环(重复)播放中的间歇时间(秒)
+}

+ 11 - 12
lc/radio_event.go

@@ -7,29 +7,28 @@ import (
 	"lc-smartX/util"
 )
 
-func StartEventServer() {
-	RadioEventServer = &RadioServer{Radios: util.Config.Radios, Notifiers: make(map[string]Notifier, 4)}
-	RadioEventServer.start()
+func StartRadioEventServer() *RadioServer {
+	s := &RadioServer{Radios: util.Config.Radios, Notifiers: make(map[string]Notifier, 4)}
+	s.start()
+	return s
 }
 
-var RadioEventServer *RadioServer
-
 type RadioServer struct {
 	Radios    []model.RadioInfo
 	Notifiers map[string]Notifier //485通道名
 }
 
 func (s *RadioServer) start() {
-	for _, radio := range RadioEventServer.Radios {
-		go OpenSerial(radio.Port)
+	for _, radio := range s.Radios {
+		go s.OpenSerial(radio.Port)
 	}
 }
 
-func RegisterCallback(branch byte, notifier Notifier) {
-	for _, radio := range RadioEventServer.Radios {
+func (s *RadioServer) RegisterCallback(branch byte, notifier Notifier) {
+	for _, radio := range s.Radios {
 		//关联主路led屏和支路雷达;关联支路led屏和主路雷达
 		if branch == 0 && radio.Branch == 1 || branch == 1 && radio.Branch == 0 {
-			RadioEventServer.Notifiers[radio.Port] = notifier
+			s.Notifiers[radio.Port] = notifier
 		}
 	}
 }
@@ -43,7 +42,7 @@ func (s *RadioServer) Callback(port string) {
 	notifier.Notify()
 }
 
-func OpenSerial(portName string) {
+func (s *RadioServer) OpenSerial(portName string) {
 	// 配置串口参数
 	options := serial.OpenOptions{
 		PortName:        portName, // /dev/ttymxc4 6 3
@@ -76,7 +75,7 @@ func OpenSerial(portName string) {
 			result = true
 		}
 		if result {
-			RadioEventServer.Callback(portName)
+			s.Callback(portName)
 		}
 	}
 }

+ 46 - 16
lc/server.go

@@ -10,18 +10,29 @@ type SmartXServer interface {
 	Serve()
 }
 
-func StartSmartXServer(s SmartXServer) {
-	s.Serve()
+type IntersectionServer struct {
+	RadioEventServer  *RadioServer
+	CameraEventServer *CameraServer
+	MainState         byte
+	SubState          byte
+	MainDevices       []IDevice
+	SubDevices        []IDevice
+	ReTicker          *time.Ticker
+	Main              *time.Ticker
+	Sub               *time.Ticker
 }
 
-type IntersectionServer struct {
-	MainState   byte
-	SubState    byte
-	MainDevices []IDevice
-	SubDevices  []IDevice
-	ReTicker    *time.Ticker
-	Main        *time.Ticker
-	Sub         *time.Ticker
+func StartIntersectionServer() {
+	is := &IntersectionServer{
+		Main:     time.NewTicker(5 * time.Second),  //主路状态回滚
+		Sub:      time.NewTicker(5 * time.Second),  //支路状态回滚
+		ReTicker: time.NewTicker(30 * time.Second), //重连
+	}
+	is.RadioEventServer = StartRadioEventServer()
+	is.CameraEventServer = StartCameraEventServer()
+	//等事件服务先启动
+	time.Sleep(1 * time.Second)
+	is.Serve()
 }
 
 type MainNotifier struct{ s *IntersectionServer }
@@ -51,20 +62,39 @@ func (sub SubNotifier) Notify() {
 }
 
 func (is *IntersectionServer) Serve() {
-	RegisterCallback(1, &SubNotifier{is})
-	RegisterCallback(0, &MainNotifier{is})
+	if util.Config.Server.SupportCamera {
+		is.CameraEventServer.RegisterCallback(1, &SubNotifier{is})
+		is.CameraEventServer.RegisterCallback(0, &MainNotifier{is})
+	}
+	if util.Config.Server.SupportRadio {
+		is.RadioEventServer.RegisterCallback(1, &SubNotifier{is})
+		is.RadioEventServer.RegisterCallback(0, &MainNotifier{is})
+	}
+
 	//先创建响应设备
-	for _, v := range util.Config.OutputDevices {
+	for _, c := range util.Config.Screens {
+		iDevice := &IntersectionDevice{
+			Info:   c,
+			Screen: NewScreen(c.Name, c.Ip, c.Port),
+		}
+		if c.Branch == 1 {
+			is.MainDevices = append(is.MainDevices, iDevice)
+		} else {
+			is.SubDevices = append(is.SubDevices, iDevice)
+		}
+	}
+	for _, c := range util.Config.Speakers {
 		iDevice := &IntersectionDevice{
-			Info:   v,
-			Screen: NewScreen(v.Name, v.ScreenIp, v.ScreenPort),
+			Info:    c,
+			Speaker: NewIpCast(c.Name, c.Ip),
 		}
-		if iDevice.Info.Branch == 1 {
+		if c.Branch == 1 {
 			is.MainDevices = append(is.MainDevices, iDevice)
 		} else {
 			is.SubDevices = append(is.SubDevices, iDevice)
 		}
 	}
+
 	for {
 		select {
 		case <-is.Main.C: //检查主路状态->支路输出设备回到初始状态

+ 75 - 0
lc/speaker.go

@@ -0,0 +1,75 @@
+package lc
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"lc-smartX/lc/model"
+	"net/http"
+)
+
+// Loudspeaker 扬声器接口
+type Loudspeaker interface {
+	Speak(txt string)
+}
+
+type IpCast struct {
+	Name      string
+	Ip        string
+	liveState bool
+}
+
+func NewIpCast(name, ip string) *IpCast {
+	s := &IpCast{
+		Name:      name,
+		Ip:        ip,
+		liveState: false,
+	}
+	s.Reconnect()
+	return s
+}
+
+func (ip *IpCast) Speak(txt string) {
+	data := &model.PlayReq{
+		Text:   txt,
+		Vcn:    "xiaoyan",
+		Speed:  50, // todo 配置文件
+		Volume: 10, // todo 配置文件
+		Rdn:    "2",
+		Rcn:    "1",
+		Reg:    0,
+		Sync:   false,
+		Queue:  false,
+	}
+	data.Loop.Times = 1
+
+	body, err := json.Marshal(data)
+	if err != nil {
+		logrus.Errorf("IpCast Marshal err : %s", err.Error())
+		return
+	}
+
+	c := http.DefaultClient
+	req, _ := http.NewRequest("POST", fmt.Sprintf("http://%s/v1/speech", ip.Ip), bytes.NewReader(body))
+	rsp, err := c.Do(req)
+	if err != nil {
+		logrus.Errorf("IpCast Speak err : %s", err.Error())
+		return
+	}
+}
+
+func (ip *IpCast) Reconnect() {
+	c := http.DefaultClient
+	req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/v1/check_alive", ip.Ip), nil)
+	rsp, err := c.Do(req)
+	if err != nil {
+		logrus.Errorf("IpCast Reconnect err : %s", err.Error())
+		return
+	}
+	if rsp.StatusCode == http.StatusOK {
+		ip.liveState = true
+	}
+}
+
+func (ip IpCast) CorrectTime() {}

+ 1 - 9
main.go

@@ -20,13 +20,5 @@ import (
 
 func main() {
 	gopool.SetCap(64)
-	gopool.Go(lc.StartEventServer)
-	//等事件服务先启动
-	time.Sleep(1 * time.Second)
-	is := &lc.IntersectionServer{
-		Main:     time.NewTicker(5 * time.Second),  //主路状态回滚
-		Sub:      time.NewTicker(5 * time.Second),  //支路状态回滚
-		ReTicker: time.NewTicker(19 * time.Second), //重连
-	}
-	lc.StartSmartXServer(is)
+	lc.StartIntersectionServer()
 }

+ 13 - 11
util/config.go

@@ -21,20 +21,22 @@ var Config = func() config {
 }()
 
 type config struct {
-	HikServer     hikServer          `yaml:"hikServer"`
-	Cameras       []model.CameraInfo `yaml:"cameras"`
-	Radios        []model.RadioInfo  `yaml:"radios"`
-	OutputDevices []OutputDevice     `yaml:"outputDevices"`
+	HikServer hikServer           `yaml:"hikServer"`
+	Cameras   []model.CameraInfo  `yaml:"cameras"`
+	Radios    []model.RadioInfo   `yaml:"radios"`
+	Screens   []model.ScreenInfo  `yaml:"screens"`
+	Speakers  []model.SpeakerInfo `yaml:"speakers"`
+	Server    service             `yaml:"service"`
+}
+
+type service struct {
+	SupportRadio   bool `yaml:"support_radio"`
+	SupportCamera  bool `yaml:"support_camera"`
+	SupportSpeaker bool `yaml:"support_speaker"`
+	SupportLed     bool `yaml:"support_led"`
 }
 
 type hikServer struct {
 	Addr string `yaml:"addr"`
 	Path string `yaml:"path"`
 }
-
-type OutputDevice struct {
-	Name       string `yaml:"name"`
-	ScreenIp   string `yaml:"screen_ip"`
-	ScreenPort string `yaml:"screen_port"`
-	Branch     byte   `yaml:"branch"`
-}