LingxinMeng vor 10 Monaten
Ursprung
Commit
42efa2fc00
84 geänderte Dateien mit 4846 neuen und 22 gelöschten Zeilen
  1. 0 0
      aarch64/pjibot_delivery/README.md
  2. 0 0
      aarch64/pjibot_delivery/common/config/c_cloud.go
  3. 0 0
      aarch64/pjibot_delivery/common/config/c_killrpcserver.go
  4. 0 0
      aarch64/pjibot_delivery/common/config/c_local.go
  5. 0 0
      aarch64/pjibot_delivery/common/config/c_oss.go
  6. 0 0
      aarch64/pjibot_delivery/common/config/c_platform.go
  7. 0 0
      aarch64/pjibot_delivery/common/config/c_resource.go
  8. 0 0
      aarch64/pjibot_delivery/common/config/c_ros.go
  9. 0 0
      aarch64/pjibot_delivery/common/config/sh/start-control.sh
  10. 0 0
      aarch64/pjibot_delivery/common/config/sh/start-master.sh
  11. 0 0
      aarch64/pjibot_delivery/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml
  12. 0 0
      aarch64/pjibot_delivery/common/config/yaml/配送机器人默认配置文件-local-config.yaml
  13. 90 0
      aarch64/pjibot_delivery/common/service/disk_clean.go
  14. 118 0
      aarch64/pjibot_delivery/common/service/kill_self.go
  15. 36 0
      aarch64/pjibot_delivery/common/service/rosbag_clean.go
  16. 131 0
      aarch64/pjibot_delivery/common/service/rosbag_record.go
  17. 160 0
      aarch64/pjibot_delivery/common/service/rosbag_upload.go
  18. 0 0
      aarch64/pjibot_delivery/common/variable/application.go
  19. 103 0
      aarch64/pjibot_delivery/control/main.go
  20. 62 0
      aarch64/pjibot_delivery/master/main.go
  21. 140 0
      aarch64/pjibot_delivery/master/package/config/master_trigger_cfg.go
  22. 96 0
      aarch64/pjibot_delivery/master/package/service/collect_one_msg.go
  23. 68 0
      aarch64/pjibot_delivery/master/package/service/move_bag_and_send_window.go
  24. 156 0
      aarch64/pjibot_delivery/master/package/service/produce_window.go
  25. 33 0
      aarch64/pjibot_guide/README.md
  26. 335 0
      aarch64/pjibot_guide/common/config/c_cloud.go
  27. 21 0
      aarch64/pjibot_guide/common/config/c_killrpcserver.go
  28. 62 0
      aarch64/pjibot_guide/common/config/c_local.go
  29. 52 0
      aarch64/pjibot_guide/common/config/c_oss.go
  30. 193 0
      aarch64/pjibot_guide/common/config/c_platform.go
  31. 35 0
      aarch64/pjibot_guide/common/config/c_resource.go
  32. 39 0
      aarch64/pjibot_guide/common/config/c_ros.go
  33. 3 0
      aarch64/pjibot_guide/common/config/sh/start-control.sh
  34. 3 0
      aarch64/pjibot_guide/common/config/sh/start-master.sh
  35. 0 0
      aarch64/pjibot_guide/common/config/yaml/巡检机器人默认配置文件-cloud-config.yaml
  36. 0 0
      aarch64/pjibot_guide/common/config/yaml/巡检机器人默认配置文件-local-config.yaml
  37. 0 0
      aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件单摄像头-cloud-config.yaml
  38. 0 0
      aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件单摄像头-local-config.yaml
  39. 0 0
      aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件双摄像头-cloud-config.yaml
  40. 0 0
      aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件双摄像头-local-config.yaml
  41. 159 0
      aarch64/pjibot_guide/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml
  42. 19 0
      aarch64/pjibot_guide/common/config/yaml/配送机器人默认配置文件-local-config.yaml
  43. 2 2
      aarch64/pjibot_guide/common/service/disk_clean.go
  44. 1 1
      aarch64/pjibot_guide/common/service/kill_self.go
  45. 1 1
      aarch64/pjibot_guide/common/service/rosbag_clean.go
  46. 1 1
      aarch64/pjibot_guide/common/service/rosbag_record.go
  47. 2 2
      aarch64/pjibot_guide/common/service/rosbag_upload.go
  48. 6 0
      aarch64/pjibot_guide/common/variable/application.go
  49. 3 3
      aarch64/pjibot_guide/control/main.go
  50. 5 5
      aarch64/pjibot_guide/master/main.go
  51. 1 1
      aarch64/pjibot_guide/master/package/config/master_trigger_cfg.go
  52. 1 1
      aarch64/pjibot_guide/master/package/service/collect_one_msg.go
  53. 2 2
      aarch64/pjibot_guide/master/package/service/move_bag_and_send_window.go
  54. 3 3
      aarch64/pjibot_guide/master/package/service/produce_window.go
  55. 33 0
      aarch64/pjibot_patrol/README.md
  56. 335 0
      aarch64/pjibot_patrol/common/config/c_cloud.go
  57. 21 0
      aarch64/pjibot_patrol/common/config/c_killrpcserver.go
  58. 62 0
      aarch64/pjibot_patrol/common/config/c_local.go
  59. 52 0
      aarch64/pjibot_patrol/common/config/c_oss.go
  60. 193 0
      aarch64/pjibot_patrol/common/config/c_platform.go
  61. 35 0
      aarch64/pjibot_patrol/common/config/c_resource.go
  62. 39 0
      aarch64/pjibot_patrol/common/config/c_ros.go
  63. 3 0
      aarch64/pjibot_patrol/common/config/sh/start-control.sh
  64. 3 0
      aarch64/pjibot_patrol/common/config/sh/start-master.sh
  65. 159 0
      aarch64/pjibot_patrol/common/config/yaml/巡检机器人默认配置文件-cloud-config.yaml
  66. 19 0
      aarch64/pjibot_patrol/common/config/yaml/巡检机器人默认配置文件-local-config.yaml
  67. 167 0
      aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件单摄像头-cloud-config.yaml
  68. 18 0
      aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件单摄像头-local-config.yaml
  69. 203 0
      aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件双摄像头-cloud-config.yaml
  70. 18 0
      aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件双摄像头-local-config.yaml
  71. 159 0
      aarch64/pjibot_patrol/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml
  72. 19 0
      aarch64/pjibot_patrol/common/config/yaml/配送机器人默认配置文件-local-config.yaml
  73. 90 0
      aarch64/pjibot_patrol/common/service/disk_clean.go
  74. 118 0
      aarch64/pjibot_patrol/common/service/kill_self.go
  75. 36 0
      aarch64/pjibot_patrol/common/service/rosbag_clean.go
  76. 131 0
      aarch64/pjibot_patrol/common/service/rosbag_record.go
  77. 160 0
      aarch64/pjibot_patrol/common/service/rosbag_upload.go
  78. 6 0
      aarch64/pjibot_patrol/common/variable/application.go
  79. 103 0
      aarch64/pjibot_patrol/control/main.go
  80. 62 0
      aarch64/pjibot_patrol/master/main.go
  81. 140 0
      aarch64/pjibot_patrol/master/package/config/master_trigger_cfg.go
  82. 96 0
      aarch64/pjibot_patrol/master/package/service/collect_one_msg.go
  83. 68 0
      aarch64/pjibot_patrol/master/package/service/move_bag_and_send_window.go
  84. 156 0
      aarch64/pjibot_patrol/master/package/service/produce_window.go

+ 0 - 0
aarch64/pji/README.md → aarch64/pjibot_delivery/README.md


+ 0 - 0
aarch64/pji/common/config/c_cloud.go → aarch64/pjibot_delivery/common/config/c_cloud.go


+ 0 - 0
aarch64/pji/common/config/c_killrpcserver.go → aarch64/pjibot_delivery/common/config/c_killrpcserver.go


+ 0 - 0
aarch64/pji/common/config/c_local.go → aarch64/pjibot_delivery/common/config/c_local.go


+ 0 - 0
aarch64/pji/common/config/c_oss.go → aarch64/pjibot_delivery/common/config/c_oss.go


+ 0 - 0
aarch64/pji/common/config/c_platform.go → aarch64/pjibot_delivery/common/config/c_platform.go


+ 0 - 0
aarch64/pji/common/config/c_resource.go → aarch64/pjibot_delivery/common/config/c_resource.go


+ 0 - 0
aarch64/pji/common/config/c_ros.go → aarch64/pjibot_delivery/common/config/c_ros.go


+ 0 - 0
aarch64/pji/common/config/sh/start-control.sh → aarch64/pjibot_delivery/common/config/sh/start-control.sh


+ 0 - 0
aarch64/pji/common/config/sh/start-master.sh → aarch64/pjibot_delivery/common/config/sh/start-master.sh


+ 0 - 0
aarch64/pji/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml → aarch64/pjibot_delivery/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml


+ 0 - 0
aarch64/pji/common/config/yaml/配送机器人默认配置文件-local-config.yaml → aarch64/pjibot_delivery/common/config/yaml/配送机器人默认配置文件-local-config.yaml


+ 90 - 0
aarch64/pjibot_delivery/common/service/disk_clean.go

@@ -0,0 +1,90 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_delivery/master/package/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/domain"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	"time"
+)
+
+// DiskClean 如果磁盘占用过高,则删除timeWindow和对应的文件
+func DiskClean() {
+	c_log.GlobalLogger.Info("清理timeWindow,启动!")
+	/*
+		TTL(0, "删除旧数据");
+		STOP(1, "停止缓存");
+		LRU(2, "保留高优先级")
+	*/
+	policyToDescription := map[string]string{
+		"TTL":  "删除旧数据",
+		"STOP": "停止缓存",
+		"LRU":  "保留高优先级",
+	}
+
+	for {
+		time.Sleep(1000 * time.Millisecond)
+		// 1 获取磁盘占用
+		diskUsed, _ := util.GetDiskUsed(commonConfig.CloudConfig.Disk.Name)
+		if diskUsed > commonConfig.CloudConfig.Disk.Used {
+			policy := commonConfig.PlatformConfig.TaskCachePolicy
+			c_log.GlobalLogger.Errorf("磁盘占用 %v 超过 %v,触发删除规则 %v", diskUsed, commonConfig.CloudConfig.Disk.Used, policyToDescription[policy])
+			// 2 获取策略
+			if policy == "TTL" {
+				// 1 获取时间窗口队列中的第二个
+				if len(entity.TimeWindowConsumerQueue) > 2 {
+					deleteTimeWindow(1)
+				}
+			} else if policy == "STOP" {
+				// 2 获取时间窗口队列中的倒数第一个
+				if len(entity.TimeWindowConsumerQueue) > 2 {
+					deleteTimeWindow(len(entity.TimeWindowConsumerQueue) - 1)
+				}
+			} else if policy == "LRU" {
+				// 3 获取优先级最低的时间窗口
+				if len(entity.TimeWindowConsumerQueue) > 2 {
+					indexToRemove := getIndexToRemoveForLRU()
+					if indexToRemove != -1 {
+						deleteTimeWindow(indexToRemove)
+					}
+				}
+			} else {
+				c_log.GlobalLogger.Error("未知的缓存策略:", policy)
+			}
+
+		}
+	}
+}
+
+func deleteTimeWindow(indexToRemove int) {
+	timeWindowToRemove := entity.TimeWindowConsumerQueue[indexToRemove]
+	// 1 删除队列中的窗口。使用切片的特性删除指定位置的元素
+	entity.TimeWindowConsumerQueueMutex.Lock()
+	entity.TimeWindowConsumerQueue = append(entity.TimeWindowConsumerQueue[:indexToRemove], entity.TimeWindowConsumerQueue[indexToRemove+1:]...)
+	entity.TimeWindowConsumerQueueMutex.Unlock()
+	// 2 删除该窗口对应的文件目录。
+	faultTime := timeWindowToRemove.FaultTime
+	dir := domain.GetCopyDir(commonConfig.CloudConfig.BagCopyDir, faultTime)
+	err := util.RemoveDir(dir)
+	if err != nil {
+		c_log.GlobalLogger.Error("删除目录", dir, "失败:", err)
+	}
+}
+
+func getIndexToRemoveForLRU() int {
+	lru := commonConfig.PlatformConfig.Lru
+	i := len(lru) - 1
+	for i >= 0 {
+		for i2, window := range entity.TimeWindowConsumerQueue {
+			for _, label := range window.Labels {
+				if masterConfig.LabelMapTriggerId[label] == lru[i] {
+					return i2
+				}
+			}
+		}
+	}
+	return -1
+
+}

+ 118 - 0
aarch64/pjibot_delivery/common/service/kill_self.go

@@ -0,0 +1,118 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"net/rpc"
+	"os"
+	"sync"
+	"time"
+)
+
+var (
+	ChannelKillRosRecord  = make(chan int)
+	ChannelKillDiskClean  = make(chan int)
+	ChannelKillSubscriber = make(chan int)
+	ChannelKillMove       = make(chan int)
+	ChannelKillConsume    = make(chan int)
+
+	KillChannel = 5
+	KillTimes   = 0
+	MutexKill   sync.Mutex
+)
+
+// KillSignal 停止信号,主从节点接收到数据后准备重启
+type KillSignal struct {
+	NodeName       string
+	DropUploadData bool
+	Restart        bool
+}
+
+// KillService 定义要远程调用的类型和方法
+type KillService struct{}
+
+// Kill 杀死自身程序,通过通道实现 方法必须满足RPC规范:函数有两个参数,第一个参数是请求,第二个是响应
+func (m *KillService) Kill(args KillSignal, reply *int) error {
+	c_log.GlobalLogger.Info("接收到自杀信号:", args)
+	// 1 杀死 rosbag record 命令
+	ChannelKillRosRecord <- 1
+	// 2 杀死所有 ros 订阅者
+	ChannelKillSubscriber <- 1
+	// 3 杀死上传任任务
+	if args.DropUploadData == true {
+		// 3-1 等待上传结束再杀死
+		ChannelKillMove <- 1
+		ChannelKillConsume <- 1
+	} else {
+		// 3-2 直接杀死
+		ChannelKillMove <- 2
+		ChannelKillConsume <- 2
+	}
+	go killDone(args.Restart)
+	return nil
+}
+func WaitKillSelf() {
+	killService := new(KillService)
+	err := rpc.Register(killService)
+	if err != nil {
+		c_log.GlobalLogger.Error("注册rpc服务失败:", err)
+		return
+	}
+
+	// 等待并处理远程调用请求
+	for {
+		conn, err := commonConfig.KillSignalListener.Accept()
+		if err != nil {
+			continue
+		}
+		go rpc.ServeConn(conn)
+	}
+}
+
+func AddKillTimes(info string) {
+	MutexKill.Lock()
+	defer MutexKill.Unlock()
+	switch info {
+	case "1":
+		ChannelKillDiskClean <- 1
+		close(ChannelKillRosRecord)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 record 打包 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "2":
+		close(ChannelKillDiskClean)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 bag 包数量维护 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "3":
+		close(ChannelKillSubscriber)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 rosnode 和ros 订阅者 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "4":
+		close(ChannelKillMove)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 bag 包移动 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "5":
+		close(ChannelKillConsume)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 bag 包消费 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	}
+}
+
+func killDone(restart bool) {
+	for {
+		time.Sleep(time.Duration(1) * time.Second)
+		if KillChannel == KillTimes {
+			if restart {
+				_, err := util.ExecuteWithPath(commonConfig.LocalConfig.RestartCmd.Dir, commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args...)
+				if err != nil {
+					c_log.GlobalLogger.Info("启动新程序失败,【path】=", commonConfig.LocalConfig.RestartCmd.Dir, "【cmd】=", commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args, ":", err)
+					os.Exit(-1)
+				}
+				c_log.GlobalLogger.Info("数据采集任务更新,正常退出当前程序。")
+			} else {
+				c_log.GlobalLogger.Info("数据采集任务终止,正常退出当前程序。")
+			}
+			os.Exit(0)
+		}
+	}
+}

+ 36 - 0
aarch64/pjibot_delivery/common/service/rosbag_clean.go

@@ -0,0 +1,36 @@
+package service
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"time"
+)
+
+// BagCacheClean 保证本地缓存的包数量不超过设定值
+func BagCacheClean() {
+	c_log.GlobalLogger.Info("启动清理缓存的 goroutine 维护目录【", config.CloudConfig.BagDataDir, "】的 bag 包数量:", config.CloudConfig.BagNumber)
+	for {
+		// 收到自杀信号
+		select {
+		case signal := <-ChannelKillDiskClean:
+			if signal == 1 {
+				AddKillTimes("2")
+				return
+			}
+		default:
+		}
+
+		// 1 ------- 每10秒清理一次 -------
+		time.Sleep(time.Duration(10) * time.Second)
+		// 2 ------- 获取目录下所有bag包 -------
+		bags, _ := util.ListAbsolutePathWithSuffixAndSort(config.CloudConfig.BagDataDir, ".bag")
+		// 3 如果打包数量超过n个,删除最旧的包{
+		if len(bags) > config.CloudConfig.BagNumber {
+			diff := len(bags) - config.CloudConfig.BagNumber
+			for i := 0; i < diff; i++ {
+				_ = util.DeleteFile(bags[i])
+			}
+		}
+	}
+}

+ 131 - 0
aarch64/pjibot_delivery/common/service/rosbag_record.go

@@ -0,0 +1,131 @@
+package service
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"github.com/bluenviron/goroslib/v2"
+	"os"
+	"os/exec"
+	"time"
+)
+
+// BagRecord 打包rosbag
+func BagRecord(nodeName string) {
+	var err error
+	c_log.GlobalLogger.Info("rosbag record goroutine - 启动")
+	for {
+		c_log.GlobalLogger.Info("校验必需的 rosnode 是否全部启动。")
+		canRecord := false
+		for !canRecord {
+			time.Sleep(time.Duration(2) * time.Second)
+			canRecord = isCanRecord(config.RosNode)
+		}
+		c_log.GlobalLogger.Info("rosnode 启动完成,正在启动 rosbag record 命令。")
+
+		var command []string
+		command = append(command, "record")
+		command = append(command, "--split")
+		command = append(command, "--duration=1")
+		for _, host := range config.CloudConfig.Hosts {
+			if host.Name == nodeName {
+				for _, topic := range host.Topics {
+					command = append(command, topic)
+				}
+			}
+		}
+
+		// 2 ------- 调用 rosbag 打包命令,该命令自动阻塞 -------
+		// 不在此处压缩,因为 rosbag filter 时会报错。在上传到oss之前压缩即可。
+		// 包名格式:2023-11-15-17-35-20_0.bag
+		_ = util.CreateParentDir(config.CloudConfig.BagDataDir)
+		var recordProcessPid int
+		var recordSubProcessPid int
+		var cmd *exec.Cmd
+		systemEnv := os.Environ()
+		c_log.GlobalLogger.Info("系统环境变量为:", systemEnv)
+	parent:
+		for {
+			c_log.GlobalLogger.Info("record 环境变量为:", config.RosbagEnvs)
+			cmd, err = util.ExecuteWithEnvAndDirAsync(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+			if err != nil {
+				c_log.GlobalLogger.Error("执行record命令", command, "出错:", err)
+				continue
+			}
+			recordProcessPid = cmd.Process.Pid
+		sub:
+			for {
+				time.Sleep(time.Duration(2) * time.Second)
+				process, err := os.FindProcess(recordProcessPid)
+				if process == nil {
+					continue parent
+				}
+				recordSubProcessPid, err = util.GetSubProcessPid(recordProcessPid)
+				if err != nil {
+					c_log.GlobalLogger.Info("正在等待获取进程 ", recordProcessPid, " 的子进程的pid。")
+					continue sub
+				}
+				if recordSubProcessPid != 0 {
+					c_log.GlobalLogger.Info("获取进程 ", recordProcessPid, " 的子进程的pid:", recordSubProcessPid)
+					break parent
+				}
+			}
+		}
+		// 等待自杀信号
+		c_log.GlobalLogger.Info("启动record命令成功。等待自杀信号。")
+		select {
+		case signal := <-ChannelKillRosRecord:
+			if signal == 1 {
+				if err := util.KillProcessByPid(recordSubProcessPid); err != nil {
+					c_log.GlobalLogger.Errorf("程序阻塞,杀死record命令子进程出错,【pid】=%v,【err】=%v。", recordSubProcessPid, err)
+					select {} // 此处阻塞防止record命令一直录包占满存储
+				}
+				if err = cmd.Process.Kill(); err != nil {
+					c_log.GlobalLogger.Error("程序阻塞,杀死record命令进程", recordProcessPid, "出错:", err)
+					select {} // 此处阻塞防止record命令一直录包占满存储
+				}
+				AddKillTimes("1")
+				return
+			}
+		}
+
+		// TODO 暂时不放开该逻辑。如果监控rosnode来判断是否杀死record,太麻烦,生成窗口的线程也需要关闭
+		//{
+		//	commonCfg.GlobalLogger.Info("正在监控rosnode是否全部关闭。")
+		//	for canRecord {
+		//		time.Sleep(time.Duration(1) * time.Second)
+		//		canRecord = isCanRecord(commonCfg.RosNode)
+		//	}
+		//	commonCfg.GlobalLogger.Info("rosnode已全部关闭,正在结束record进程。")
+		//
+		//	err = cmd.Process.Kill()
+		//	if err != nil {
+		//		commonCfg.GlobalLogger.Error("杀死record进程错误:", err)
+		//		continue
+		//	}
+		//}
+	}
+}
+
+func isCanRecord(n *goroslib.Node) bool {
+	time.Sleep(time.Duration(1) * time.Second)
+	// 获取
+	nodes, err := n.MasterGetNodes()
+	if err != nil {
+		c_log.GlobalLogger.Error("获取rosnode出错:", err)
+		return false
+	}
+	// 创建一个示例的map
+	myMap := nodes
+	// 创建一个切片,包含要检查的元素
+	mySlice := config.CloudConfig.Ros.Nodes
+
+	// 判断map的键是否包含切片中的所有元素
+	for _, element := range mySlice {
+		if _, ok := myMap[element]; !ok {
+			c_log.GlobalLogger.Info("rosnode:", element, " 未启动,需等待启动后才可启动record。")
+			return false
+		}
+	}
+	return true
+}

+ 160 - 0
aarch64/pjibot_delivery/common/service/rosbag_upload.go

@@ -0,0 +1,160 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_delivery/master/package/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/domain"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+func RunTimeWindowConsumerQueue(nodeName string) {
+	c_log.GlobalLogger.Info("处理消费者队列goroutine - 启动")
+outLoop:
+	for {
+
+		// 收到自杀信号
+		select {
+		case signal := <-ChannelKillConsume:
+			if signal == 1 {
+				ChannelKillConsume <- 1
+				if len(entity.TimeWindowConsumerQueue) == 0 {
+					AddKillTimes("5")
+					return
+				}
+			} else { //signal == 2
+				AddKillTimes("5")
+				return
+			}
+		default:
+		}
+		// 每一秒扫一次
+		time.Sleep(time.Duration(1) * time.Second)
+
+		waitLength := len(entity.TimeWindowConsumerQueue)
+		if waitLength == 0 {
+			continue outLoop
+		}
+		c_log.GlobalLogger.Infof("待处理窗口个数为:%v", len(entity.TimeWindowConsumerQueue))
+		// 1 获取即将处理的窗口
+		currentTimeWindow := entity.TimeWindowConsumerQueue[0]
+		entity.RemoveHeadOfTimeWindowConsumerQueue()
+		c_log.GlobalLogger.Infof("开始处理窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", currentTimeWindow.Labels, currentTimeWindow.FaultTime, currentTimeWindow.Length)
+		// 2 获取目录
+		dir := domain.GetCopyDir(commonConfig.CloudConfig.BagCopyDir, currentTimeWindow.FaultTime)
+		bags, _ := util.ListAbsolutePathWithSuffixAndSort(dir, ".bag")
+		bagNumber := len(bags)
+		if bagNumber > currentTimeWindow.Length {
+			bagNumber = currentTimeWindow.Length
+			bags = bags[0:currentTimeWindow.Length]
+		}
+
+		// 3 如果不是全量采集,则使用 filter 命令对 bag 包进行主题过滤。
+		if commonConfig.CloudConfig.FullCollect == false {
+			var filterTopics []string
+			if nodeName == commonConfig.CloudConfig.Hosts[0].Name {
+				filterTopics = currentTimeWindow.MasterTopics
+			} else {
+				filterTopics = currentTimeWindow.SlaveTopics
+			}
+			var topicsFilterSlice []string
+			for _, topic := range filterTopics {
+				topicsFilterSlice = append(topicsFilterSlice, "topic=='"+topic+"'")
+			}
+			for i, bag := range bags {
+				oldName := bag
+				newName := bag + "_filter"
+				filterCommand := []string{"filter", oldName, newName, "\"" + strings.Join(topicsFilterSlice, " or ") + "\""}
+				_, output, err := util.ExecuteWithEnvSync(commonConfig.RosbagEnvs, commonConfig.RosbagPath, filterCommand...)
+				c_log.GlobalLogger.Info("正在过滤中,【FaultTime】=", currentTimeWindow.FaultTime, "【Label】=", currentTimeWindow.Labels, ",进度", i+1, "/", bagNumber, "。")
+				if err != nil {
+					c_log.GlobalLogger.Errorf("filter命令执行出错【命令】=%v,【输出】=%v,【err】=%v", filterCommand, output, err)
+					continue
+				}
+				// 删除旧文件
+				util.DeleteFile(oldName)
+				// 将新文件改回旧文件名
+				if err = os.Rename(newName, oldName); err != nil {
+					c_log.GlobalLogger.Info("修改文件名", oldName, "失败,放弃当前时间窗口", currentTimeWindow.FaultTime, ",错误为:", err)
+					continue outLoop
+				}
+			}
+		}
+
+		// 4 compress包,必须顺序执行,此时每个包会对应生成一个压缩过的包和原始包,原始包后缀为.orig.bag
+		// 5 todo 机器人去掉压缩过程,防止cpu跑满
+		//c_log.GlobalLogger.Info("压缩 bag 数据包,故障时间为:", currentTimeWindow.FaultTime)
+		//for i, bag := range bags {
+		//	oldName := bag
+		//	compressCommand := []string{"compress", "--bz2", oldName}
+		//	c_log.GlobalLogger.Info("正在压缩中,【FaultTime】=", currentTimeWindow.FaultTime, "【Label】=", currentTimeWindow.Labels, ",进度", i+1, "/", bagNumber, "。")
+		//	if _, output, err := util.ExecuteWithEnvSync(commonConfig.RosbagEnvs, commonConfig.RosbagPath, compressCommand...); err != nil {
+		//		c_log.GlobalLogger.Errorf("compress命令执行出错【命令】=%v,【输出】=%v,【err】=%v", compressCommand, output, err)
+		//		continue
+		//	}
+		//}
+		// 5 upload,必须顺序执行
+		c_log.GlobalLogger.Info("发送bag数据包,故障时间为:", currentTimeWindow.FaultTime)
+		start := time.Now()
+		objectKey1 := commonConfig.LocalConfig.OssBasePrefix + commonConfig.LocalConfig.EquipmentNo + "/data/" + currentTimeWindow.FaultTime + "_" + strings.Join(currentTimeWindow.Labels, "_") + "_" + fmt.Sprintf("%d", bagNumber) + "/"
+		objectKey2 := commonConfig.LocalConfig.OssBasePrefix + commonConfig.LocalConfig.EquipmentNo + "/data_merge/" + currentTimeWindow.FaultTime + "_" + strings.Join(currentTimeWindow.Labels, "_") + "_" + fmt.Sprintf("%d", bagNumber) + ".bag"
+		objectKey3 := commonConfig.LocalConfig.OssBasePrefix + commonConfig.LocalConfig.EquipmentNo + "/data_parse/" + currentTimeWindow.FaultTime + "_" + strings.Join(currentTimeWindow.Labels, "_") + "_" + fmt.Sprintf("%d", bagNumber) + "/"
+		for i, bag := range bags {
+			startOne := time.Now()
+			bagSlice := strings.Split(bag, "/")
+			for {
+				commonConfig.OssMutex.Lock()
+				err := commonConfig.OssBucket.PutObjectFromFile(objectKey1+bagSlice[len(bagSlice)-1], bag)
+				commonConfig.OssMutex.Unlock()
+				if err != nil {
+					c_log.GlobalLogger.Info("因网络原因上传包 ", bag, " 时报错,需要等待网络恢复后重新上传:", err)
+					continue
+				}
+				c_log.GlobalLogger.Info("上传耗时 ", time.Since(startOne), ",【FaultTime】=", currentTimeWindow.FaultTime, "【Label】=", currentTimeWindow.Labels, ",进度", i+1, "/", bagNumber, "。【", bag, "】-------【", objectKey1+bagSlice[len(bagSlice)-1], "】")
+				break
+			}
+		}
+		c_log.GlobalLogger.Info("上传完成,花费时间:", time.Since(start))
+		// 在上传完成的包目录同级下添加一个目录同名的json
+		triggerIds := make([]string, 0)
+		for _, label := range currentTimeWindow.Labels {
+			triggerIds = append(triggerIds, masterConfig.LabelMapTriggerId[label])
+		}
+		callBackMap := map[string]interface{}{
+			"dataName":    currentTimeWindow.FaultTime, // 云端callback程序会将该值加8小时,因为UTC和CSV时区相差8小时
+			"dataSize":    "",                          // 由合并程序补充
+			"equipmentNo": commonConfig.LocalConfig.EquipmentNo,
+			"secretKey":   commonConfig.LocalConfig.SecretKey,
+			"rosBagPath":  objectKey2,
+			"filePath":    objectKey3,
+			"taskId":      commonConfig.PlatformConfig.TaskConfigId,
+			"triggerId":   triggerIds,
+		}
+		callBackJson, err := util.MapToJsonString(callBackMap)
+		if err != nil {
+			c_log.GlobalLogger.Error("callBackMap", callBackMap, "转json失败:", err)
+		}
+		commonConfig.OssMutex.Lock()
+		err = commonConfig.OssBucket.PutObject(objectKey3+"callback.json", strings.NewReader(callBackJson))
+		for _, file := range commonConfig.CloudConfig.MapBufFiles {
+			err = commonConfig.OssBucket.PutObjectFromFile(objectKey3+filepath.Base(file), file)
+		}
+		commonConfig.OssMutex.Unlock()
+		if err != nil {
+			c_log.GlobalLogger.Error("上传 callback.json 或 mapBuf 文件失败:", err)
+		}
+
+		// 删除本地所有已上传的bag文件
+		c_log.GlobalLogger.Infof("结束处理窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", currentTimeWindow.Labels, currentTimeWindow.FaultTime, currentTimeWindow.Length)
+		if err = util.RemoveDir(dir); err != nil {
+			continue outLoop
+		}
+
+	}
+}

+ 0 - 0
aarch64/pji/common/variable/application.go → aarch64/pjibot_delivery/common/variable/application.go


+ 103 - 0
aarch64/pjibot_delivery/control/main.go

@@ -0,0 +1,103 @@
+package main
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_delivery/common/service"
+	"cicv-data-closedloop/aarch64/pjibot_delivery/common/variable"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"net/rpc"
+	"os"
+	"runtime"
+	"time"
+)
+
+var applicationName = "pji-control"
+
+func init() {
+	runtime.GOMAXPROCS(1)
+	// 初始化日志配置
+	c_log.InitLog(variable.LogDir, applicationName)
+	// 初始化本地配置文件(第1处配置,在本地文件)
+	commonConfig.InitLocalConfig(variable.LocalConfigPath)
+	// 初始化Oss连接信息
+	commonConfig.InitOssConfig()
+	// 初始化业务逻辑配置信息,配置文件在oss上(第2处配置,在oss文件)
+	commonConfig.InitCloudConfig()
+	// 初始化rpc客户端,用于杀死旧的采集程序
+}
+
+func main() {
+	lastStatus := "NONE"
+	//  轮询任务接口判断是否有更新
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		// 1 获取当前设备的任务的 status
+		status, err := commonConfig.GetStatus(commonConfig.PlatformConfig.TaskConfigId)
+		if err != nil {
+			c_log.GlobalLogger.Error("获取配置status失败:", err)
+			continue
+		}
+		// 2 判断 status
+		// UN_CHANGE 没有新的任务,无需更改
+		// CHANGE 有新的任务,需要杀死旧的任务并重启
+		// NONE 设备没有配置任务,需要杀死旧的任务
+		if status == "UN_CHANGE" {
+			lastStatus = "UN_CHANGE"
+			continue
+		} else if status == "CHANGE" || status == "NONE" {
+			if lastStatus == "CHANGE" && status == "CHANGE" { // 供更新使用
+				commonConfig.InitPlatformConfig()
+				continue
+			}
+			if lastStatus == "NONE" && status == "NONE" {
+				continue
+			}
+			// 3 发送rpc信号杀死采集程序
+			if lastStatus == "NONE" && status == "CHANGE" {
+				if _, err := util.ExecuteWithPath(commonConfig.LocalConfig.RestartCmd.Dir, commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args...); err != nil {
+					c_log.GlobalLogger.Info("启动新程序失败,【path】=", commonConfig.LocalConfig.RestartCmd.Dir, "【cmd】=", commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args, ":", err)
+					os.Exit(-1)
+				}
+				c_log.GlobalLogger.Info("启动任务,本地执行启动命令:【path】=", commonConfig.LocalConfig.RestartCmd.Dir, "【cmd】=", commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args)
+				lastStatus = status
+				c_log.GlobalLogger.Info("获取数据闭环平台最新配置。")
+				commonConfig.InitPlatformConfig()
+				continue
+			}
+			var killArgs commonService.KillSignal
+			if lastStatus == "UN_CHANGE" && status == "CHANGE" {
+				killArgs = commonService.KillSignal{NodeName: "master", DropUploadData: commonConfig.PlatformConfig.DropUploadData, Restart: true}
+				c_log.GlobalLogger.Info("更新任务,发送rpc重启信号:", killArgs)
+			}
+			if lastStatus == "UN_CHANGE" && status == "NONE" {
+				killArgs = commonService.KillSignal{NodeName: "master", DropUploadData: commonConfig.PlatformConfig.DropUploadData, Restart: false}
+				c_log.GlobalLogger.Info("杀死任务,发送rpc结束信号:", killArgs)
+			}
+
+			KillRpcClient, err := rpc.Dial("tcp", commonConfig.LocalConfig.Node.Ip+":"+commonConfig.CloudConfig.RpcPort)
+			if err != nil {
+				// 此处如果连接失败说明采集程序已经停止了
+				lastStatus = "NONE"
+				c_log.GlobalLogger.Error("采集程序已经停止:", err)
+				continue
+			}
+
+			reply := 0
+			if err = KillRpcClient.Call("KillService.Kill", killArgs, &reply); err != nil {
+				c_log.GlobalLogger.Error("发送 rpc 请求到 master 报错:", err)
+				// 这里可能会报错unexpected EOF但是不影响,先注释 close 和 continue
+				//KillRpcClient.Close()
+				//continue
+			}
+			lastStatus = status
+			c_log.GlobalLogger.Info("结束任务后,将数据闭环平台配置置空。")
+			commonConfig.PlatformConfig = commonConfig.PlatformConfigStruct{}
+			if err = KillRpcClient.Close(); err != nil {
+				// 不做处理
+			}
+		} else {
+			c_log.GlobalLogger.Error("未知的采集任务状态。【status】=", status)
+		}
+	}
+}

+ 62 - 0
aarch64/pjibot_delivery/master/main.go

@@ -0,0 +1,62 @@
+package main
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_delivery/common/service"
+	"cicv-data-closedloop/aarch64/pjibot_delivery/common/variable"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_delivery/master/package/config"
+	masterService "cicv-data-closedloop/aarch64/pjibot_delivery/master/package/service"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"runtime"
+	"time"
+)
+
+var applicationName = "pji-master"
+
+func init() {
+	runtime.GOMAXPROCS(1)
+	// 初始化日志配置
+	c_log.InitLog(variable.LogDir, applicationName)
+	// 初始化本地配置文件(第1处配置,在本地文件)
+	commonConfig.InitLocalConfig(variable.LocalConfigPath)
+	// 初始化Oss连接信息
+	commonConfig.InitOssConfig()
+	// 初始化业务逻辑配置信息,配置文件在oss上(第2处配置,在oss文件)
+	commonConfig.InitCloudConfig()
+	_ = util.RemoveSubFiles(commonConfig.CloudConfig.BagDataDir)
+	_ = util.RemoveSubFiles(commonConfig.CloudConfig.BagCopyDir)
+	go commonConfig.RefreshCloudConfig()
+	// 初始化数据闭环平台的配置(第3处配置,在数据闭环平台接口)
+	commonConfig.InitPlatformConfig()
+	// 初始化ros节点
+	commonConfig.InitRosConfig()
+	// 发送资源占用信息
+	go commonConfig.SendResourceUsage()
+	// 维护data目录缓存的包数量
+	go commonService.BagCacheClean()
+	// 磁盘占用过高时根据缓存策略处理copy目录
+	go commonService.DiskClean()
+	masterConfig.InitTriggerConfig()
+	commonConfig.InitKillSignalListener(commonConfig.CloudConfig.Hosts[0].Ip)
+	// 等待重启,接收到重启信号,会把信号分发给以下channel
+	go commonService.WaitKillSelf()
+	// 先采集地图bag包
+	masterService.CollectOneMsg()
+}
+
+func main() {
+
+	// 1 负责打包数据到data目录
+	go commonService.BagRecord(commonConfig.CloudConfig.Hosts[0].Name)
+	time.Sleep(time.Duration(10) * time.Second)
+	// 2 负责监控故障,并修改timeWindow
+	go masterService.PrepareTimeWindowProducerQueue()
+	// 3 将时间窗口内的包全部move出去,并等待当前时间窗口结束触发上传
+	go masterService.RunTimeWindowProducerQueue()
+	// 4 排队运行时间窗口
+	go commonService.RunTimeWindowConsumerQueue(commonConfig.CloudConfig.Hosts[0].Name)
+
+	// 阻塞主线程,等待其他线程执行。
+	select {}
+}

+ 140 - 0
aarch64/pjibot_delivery/master/package/config/master_trigger_cfg.go

@@ -0,0 +1,140 @@
+package config
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"cicv-data-closedloop/pji_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/diagnostic_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/nav_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/sensor_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/std_msgs"
+	"plugin"
+	"strconv"
+)
+
+var (
+	LabelMapTriggerId = make(map[string]string)
+
+	// 1
+	TopicOfDiagnostics = "/diagnostics"
+	RuleOfDiagnostics  []func(data *diagnostic_msgs.DiagnosticArray) string
+	// 2
+	TopicOfImu = "/imu"
+	RuleOfImu  []func(data *sensor_msgs.Imu) string
+	// 3
+	TopicOfLocateInfo = "/locate_info"
+	RuleOfLocateInfo  []func(data *pji_msgs.LocateInfo) string
+	// 4
+	TopicOfObstacleDetection = "/obstacle_detection"
+	RuleOfObstacleDetection  []func(data *std_msgs.UInt8) string
+	// 5
+	TopicOfOdom = "/odom"
+	RuleOfOdom  []func(data *nav_msgs.Odometry) string
+	// 6
+	TopicOfSysInfo = "/sys_info"
+	RuleOfSysInfo  []func(data *pji_msgs.SysInfo) string
+)
+
+func InitTriggerConfig() {
+	loadSuccess := 0
+	// 下载所有触发器的文件
+	for _, trigger := range config.PlatformConfig.TaskTriggers {
+		// 下载
+		triggerLocalPath := config.CloudConfig.TriggersDir + trigger.TriggerScriptPath
+		_ = util.CreateParentDir(triggerLocalPath)
+		c_log.GlobalLogger.Info("下载触发器插件从", trigger.TriggerScriptPath, "到", triggerLocalPath)
+		config.OssMutex.Lock()
+		err := config.OssBucket.GetObjectToFile(trigger.TriggerScriptPath, triggerLocalPath)
+		config.OssMutex.Unlock()
+		if err != nil {
+			c_log.GlobalLogger.Errorf("下载oss上的触发器插件失败【%v】->【%v】:%v", trigger.TriggerScriptPath, triggerLocalPath, err)
+			continue
+		}
+		// 载入插件到数组
+		open, err := plugin.Open(triggerLocalPath)
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "失败。", err)
+		}
+		topic0, err := open.Lookup("Topic")
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "中的Topic方法失败。", err)
+			continue
+		}
+		topic1, ok := topic0.(func() string)
+		if ok != true {
+			c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func() string):", err)
+			continue
+		}
+		topic2 := topic1()
+		rule, err := open.Lookup("Rule")
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "中的Rule方法失败。", err)
+			continue
+		}
+		// 判断topic
+		// todo 如果是未知的topic,可以添加一个循环,更新平台配置,同理金龙车和多功能车
+		if TopicOfDiagnostics == topic2 { // 1
+			f, ok := rule.(func(*diagnostic_msgs.DiagnosticArray) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *diagnostic_msgs.DiagnosticArray) string):", err)
+				continue
+			}
+			RuleOfDiagnostics = append(RuleOfDiagnostics, f)
+		} else if TopicOfImu == topic2 { // 2
+			f, ok := rule.(func(data *sensor_msgs.Imu) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *sensor_msgs.Imu) string):", err)
+				continue
+			}
+			RuleOfImu = append(RuleOfImu, f)
+		} else if TopicOfLocateInfo == topic2 { // 3
+			f, ok := rule.(func(data *pji_msgs.LocateInfo) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *pji_msgs.LocateInfo) string):", err)
+				continue
+			}
+			RuleOfLocateInfo = append(RuleOfLocateInfo, f)
+		} else if TopicOfObstacleDetection == topic2 { // 4
+			f, ok := rule.(func(data *std_msgs.UInt8) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *std_msgs.UInt8) string):", err)
+				continue
+			}
+			RuleOfObstacleDetection = append(RuleOfObstacleDetection, f)
+		} else if TopicOfOdom == topic2 { // 5
+			f, ok := rule.(func(data *nav_msgs.Odometry) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *nav_msgs.Odometry) string):", err)
+				continue
+			}
+			RuleOfOdom = append(RuleOfOdom, f)
+		} else if TopicOfSysInfo == topic2 { // 6
+			f, ok := rule.(func(data *pji_msgs.SysInfo) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *pji_msgs.SysInfo) string):", err)
+				continue
+			}
+			RuleOfSysInfo = append(RuleOfSysInfo, f)
+		} else {
+			c_log.GlobalLogger.Error("未知的topic:", topic2)
+			continue
+		}
+
+		label, err := open.Lookup("Label")
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "中的TriggerName方法失败。", err)
+			continue
+		}
+		labelFunc, ok := label.(func() string)
+		if ok != true {
+			c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Label方法必须是(func() string):", err)
+			continue
+		}
+		labelString := labelFunc()
+		LabelMapTriggerId[labelString] = strconv.Itoa(trigger.TriggerId)
+		loadSuccess++
+		c_log.GlobalLogger.Info("主节点加载触发器插件:【ros topic】=", topic2, ",【触发器label】=", labelString, "【触发器ID】=", trigger.TriggerId)
+	}
+	c_log.GlobalLogger.Infof("一共有%v个触发器,加载成功了%v个,【label和id映射关系】=%v", len(config.PlatformConfig.TaskTriggers), loadSuccess, LabelMapTriggerId)
+}

+ 96 - 0
aarch64/pjibot_delivery/master/package/service/collect_one_msg.go

@@ -0,0 +1,96 @@
+package service
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"os"
+)
+
+func CollectOneMsg() {
+	collectMap()
+	collectTfStatic()
+	collectCostmap()
+}
+
+func collectMap() {
+
+	// rosbag record -O /root/cicv-data-closedloop/map_data.bag -l 1 /map
+	ossMapBagObjectKey := config.LocalConfig.OssBasePrefix + config.LocalConfig.EquipmentNo + "/map.bag"
+
+	var command []string
+	command = append(command, "record")
+	command = append(command, "-O")
+	command = append(command, config.CloudConfig.MapBagPath)
+	command = append(command, "-l")
+	command = append(command, "1")
+	command = append(command, "/map")
+	_, s, err := util.ExecuteWithEnvAndDir(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。采集/map包", command, "出错:", s, "----", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("采集/map包", command, "完成。")
+	config.OssMutex.Lock()
+	err = config.OssBucket.PutObjectFromFile(ossMapBagObjectKey, config.CloudConfig.MapBagPath)
+	config.OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。上传/map包", config.CloudConfig.MapBagPath, "->", ossMapBagObjectKey, "出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("上传/map包", config.CloudConfig.MapBagPath, "------", ossMapBagObjectKey, "成功。")
+}
+func collectTfStatic() {
+
+	// rosbag record -O /root/cicv-data-closedloop/map_data.bag -l 1 /map
+	ossMapBagObjectKey := config.LocalConfig.OssBasePrefix + config.LocalConfig.EquipmentNo + "/tfstatic.bag"
+
+	var command []string
+	command = append(command, "record")
+	command = append(command, "-O")
+	command = append(command, config.CloudConfig.MapBagPath)
+	command = append(command, "-l")
+	command = append(command, "1")
+	command = append(command, "/tf_static")
+	_, s, err := util.ExecuteWithEnvAndDir(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。采集/tf_static包", command, "出错:", s, "----", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("采集/tf_static包", command, "完成。")
+	config.OssMutex.Lock()
+	err = config.OssBucket.PutObjectFromFile(ossMapBagObjectKey, config.CloudConfig.MapBagPath)
+	config.OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。上传/tf_static包", config.CloudConfig.MapBagPath, "->", ossMapBagObjectKey, "出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("上传/tf_static包", config.CloudConfig.MapBagPath, "------", ossMapBagObjectKey, "成功。")
+}
+func collectCostmap() {
+
+	// rosbag record -O /root/cicv-data-closedloop/map_data.bag -l 1 /map
+	ossMapBagObjectKey := config.LocalConfig.OssBasePrefix + config.LocalConfig.EquipmentNo + "/costmap.bag"
+
+	var command []string
+	command = append(command, "record")
+	command = append(command, "-O")
+	command = append(command, config.CloudConfig.MapBagPath)
+	command = append(command, "-l")
+	command = append(command, "1")
+	command = append(command, "/move_base/global_costmap/costmap")
+	_, s, err := util.ExecuteWithEnvAndDir(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。采集/move_base/global_costmap/costmap包", command, "出错:", s, "----", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("采集/move_base/global_costmap/costmap包", command, "完成。")
+	config.OssMutex.Lock()
+	err = config.OssBucket.PutObjectFromFile(ossMapBagObjectKey, config.CloudConfig.MapBagPath)
+	config.OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。上传/move_base/global_costmap/costmap包", config.CloudConfig.MapBagPath, "->", ossMapBagObjectKey, "出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("上传/move_base/global_costmap/costmap包", config.CloudConfig.MapBagPath, "------", ossMapBagObjectKey, "成功。")
+}

+ 68 - 0
aarch64/pjibot_delivery/master/package/service/move_bag_and_send_window.go

@@ -0,0 +1,68 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_delivery/common/service"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/domain"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	"time"
+)
+
+// RunTimeWindowProducerQueue 将时间窗口内的包全部move出去,并等待当前时间窗口结束触发上传
+func RunTimeWindowProducerQueue() {
+	c_log.GlobalLogger.Info("生产者队列goroutine - 启动")
+	for {
+		// 收到自杀信号
+		select {
+		case signal := <-commonService.ChannelKillMove:
+			if signal == 1 {
+				commonService.ChannelKillMove <- 1
+				if len(entity.TimeWindowProducerQueue) == 0 {
+					commonService.AddKillTimes("4")
+					return
+				}
+			} else { //signal == 2
+				commonService.AddKillTimes("4")
+				return
+			}
+		default:
+		}
+
+		// 处理
+		time.Sleep(time.Duration(1) * time.Second)
+		if len(entity.TimeWindowProducerQueue) > 0 {
+			bags, _ := util.ListAbsolutePathWithSuffixAndSort(commonConfig.CloudConfig.BagDataDir, ".bag")
+			currentTimeWindow := entity.TimeWindowProducerQueue[0]
+			move := false
+			bigger := false
+			for _, bag := range bags {
+				bagTime := util.GetBagTime(bag)
+				// 2 如果bag不小于timeWindowBegin不大于timeWindowEnd,则移动
+				compare1 := util.TimeCustom1GreaterEqualThanTimeCustom2(bagTime, currentTimeWindow.TimeWindowBegin)
+				compare2 := util.TimeCustom1LessEqualThanTimeCustom2(bagTime, currentTimeWindow.TimeWindowEnd)
+				if compare1 && compare2 {
+					// 将bag包移动到Copy目录
+					domain.MoveFromDataToCopy(currentTimeWindow.FaultTime, commonConfig.CloudConfig.BagDataDir, bag, commonConfig.CloudConfig.BagCopyDir)
+					move = true
+				} else {
+					if util.TimeCustom1GreaterEqualThanTimeCustom2(bagTime, currentTimeWindow.TimeWindowBegin) {
+						// 必须已经生成了窗口之后的包才算窗口结束了
+						bigger = true
+						break
+					}
+				}
+			}
+			// 如果没有包可以供当前窗口移动,且已经生成了更新的包,则当前窗口已经可以上传
+			if !move && bigger {
+				domain.SupplyCopyBags(commonConfig.CloudConfig.BagDataDir, commonConfig.CloudConfig.BagCopyDir, currentTimeWindow)
+				// 将时间窗口移出准备队列
+				entity.RemoveHeadOfTimeWindowProducerQueue()
+				// 将时间窗口加入运行队列
+				entity.AddTimeWindowToTimeWindowConsumerQueue(currentTimeWindow)
+				continue
+			}
+		}
+	}
+}

+ 156 - 0
aarch64/pjibot_delivery/master/package/service/produce_window.go

@@ -0,0 +1,156 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_delivery/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_delivery/common/service"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_delivery/master/package/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	commonUtil "cicv-data-closedloop/common/util"
+	"encoding/json"
+	"github.com/bluenviron/goroslib/v2"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/std_msgs"
+	"sync"
+	"time"
+)
+
+// PrepareTimeWindowProducerQueue 负责监听所有主题并修改时间窗口
+func PrepareTimeWindowProducerQueue() {
+
+	var err error
+	subscribers := make([]*goroslib.Subscriber, len(commonConfig.SubscribeTopics))
+	subscribersTimes := make([]time.Time, len(commonConfig.SubscribeTopics))
+	subscribersTimeMutexes := make([]sync.Mutex, len(commonConfig.SubscribeTopics))
+	subscribersMutexes := make([]sync.Mutex, len(commonConfig.SubscribeTopics))
+	for i, topic := range commonConfig.SubscribeTopics {
+		c_log.GlobalLogger.Info("创建订阅者订阅话题:" + topic)
+		if topic == masterConfig.TopicOfObstacleDetection && len(masterConfig.RuleOfObstacleDetection) > 0 {
+			subscribers[i], err = goroslib.NewSubscriber(goroslib.SubscriberConf{
+				Node:  commonConfig.RosNode,
+				Topic: topic,
+				Callback: func(data *std_msgs.UInt8) {
+					subscribersTimeMutexes[i].Lock()
+					if time.Since(subscribersTimes[i]).Seconds() > 1 {
+						subscribersMutexes[i].Lock()
+						faultHappenTime := commonUtil.GetNowTimeCustom() // 获取当前故障发生时间
+						lastTimeWindow := entity.GetLastTimeWindow()     // 获取最后一个时间窗口
+						var faultLabel string
+						for _, f := range masterConfig.RuleOfObstacleDetection {
+							faultLabel = f(data)
+							if faultLabel != "" {
+								if canCollect() {
+									saveTimeWindow(faultLabel, faultHappenTime, lastTimeWindow)
+									subscribersTimes[i] = time.Now()
+									break
+								}
+							}
+						}
+						subscribersMutexes[i].Unlock()
+					}
+					subscribersTimeMutexes[i].Unlock()
+				},
+			})
+		}
+		if err != nil {
+			c_log.GlobalLogger.Info("创建订阅者", masterConfig.TopicOfObstacleDetection, "发生故障:", err)
+			//TODO 如何回传日志
+			continue
+		}
+	}
+
+	select {
+	case signal := <-commonService.ChannelKillSubscriber:
+		if signal == 1 {
+			commonConfig.RosNode.Close()
+			commonService.AddKillTimes("3")
+			return
+		}
+	}
+}
+
+func saveTimeWindow(faultLabel string, faultHappenTime string, lastTimeWindow *entity.TimeWindow) {
+	masterTopics, slaveTopics := getTopicsOfNode(faultLabel)
+	if lastTimeWindow == nil || commonUtil.TimeCustom1GreaterTimeCustom2(faultHappenTime, lastTimeWindow.TimeWindowEnd) {
+		// 2-1 如果是不在旧故障窗口内,添加一个新窗口
+		newTimeWindow := entity.TimeWindow{
+			FaultTime:       faultHappenTime,
+			TimeWindowBegin: commonUtil.TimeCustomChange(faultHappenTime, -commonConfig.PlatformConfig.TaskBeforeTime),
+			TimeWindowEnd:   commonUtil.TimeCustomChange(faultHappenTime, commonConfig.PlatformConfig.TaskAfterTime),
+			Length:          commonConfig.PlatformConfig.TaskBeforeTime + commonConfig.PlatformConfig.TaskAfterTime + 1,
+			Labels:          []string{faultLabel},
+			MasterTopics:    masterTopics,
+			SlaveTopics:     slaveTopics,
+		}
+		c_log.GlobalLogger.Infof("不在旧故障窗口内,向生产者队列添加一个新窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", newTimeWindow.Labels, newTimeWindow.FaultTime, newTimeWindow.Length)
+		entity.AddTimeWindowToTimeWindowProducerQueue(newTimeWindow)
+	} else {
+		// 2-2 如果在旧故障窗口内
+		entity.TimeWindowProducerQueueMutex.RLock()
+		defer entity.TimeWindowProducerQueueMutex.RUnlock()
+		// 2-2-1 更新故障窗口end时间
+		maxEnd := commonUtil.TimeCustomChange(lastTimeWindow.TimeWindowBegin, commonConfig.PlatformConfig.TaskMaxTime)
+		expectEnd := commonUtil.TimeCustomChange(faultHappenTime, commonConfig.PlatformConfig.TaskAfterTime)
+		if commonUtil.TimeCustom1GreaterTimeCustom2(expectEnd, maxEnd) {
+			lastTimeWindow.TimeWindowEnd = maxEnd
+			lastTimeWindow.Length = commonConfig.PlatformConfig.TaskMaxTime
+		} else {
+			if commonUtil.TimeCustom1GreaterTimeCustom2(expectEnd, lastTimeWindow.TimeWindowEnd) {
+				lastTimeWindow.TimeWindowEnd = expectEnd
+				lastTimeWindow.Length = commonUtil.CalculateDifferenceOfTimeCustom(lastTimeWindow.TimeWindowBegin, expectEnd)
+			}
+		}
+		// 2-2-2 更新label
+		labels := lastTimeWindow.Labels
+		lastTimeWindow.Labels = commonUtil.AppendIfNotExists(labels, faultLabel)
+		// 2-2-3 更新 topic
+		sourceMasterTopics := lastTimeWindow.MasterTopics
+		lastTimeWindow.MasterTopics = commonUtil.MergeSlice(sourceMasterTopics, masterTopics)
+		sourceSlaveTopics := lastTimeWindow.SlaveTopics
+		lastTimeWindow.SlaveTopics = commonUtil.MergeSlice(sourceSlaveTopics, slaveTopics)
+		c_log.GlobalLogger.Infof("在旧故障窗口内,更新生产者队列最新的窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", lastTimeWindow.Labels, lastTimeWindow.FaultTime, lastTimeWindow.Length)
+	}
+}
+
+func getTopicsOfNode(faultLabel string) (masterTopics []string, slaveTopics []string) {
+	// 获取所有需要采集的topic
+	var faultCodeTopics []string
+	for _, code := range commonConfig.CloudConfig.Triggers {
+		if code.Label == faultLabel {
+			faultCodeTopics = code.Topics
+		}
+	}
+	return faultCodeTopics, nil
+}
+
+// 判断采集包数量是否超过限额
+func canCollect() bool {
+	responseString, err := commonUtil.HttpPostJsonWithHeaders(
+		commonConfig.CloudConfig.CollectLimit.Url,
+		map[string]string{"Authorization": "U9yKpD6kZZDDe4LFKK6myAxBUT1XRrDM"},
+		map[string]string{
+			"snCode":            commonConfig.LocalConfig.SecretKey,
+			"collectLimitDay":   util.ToString(commonConfig.CloudConfig.CollectLimit.Day),
+			"collectLimitWeek":  util.ToString(commonConfig.CloudConfig.CollectLimit.Week),
+			"collectLimitMonth": util.ToString(commonConfig.CloudConfig.CollectLimit.Month),
+			"collectLimitYear":  util.ToString(commonConfig.CloudConfig.CollectLimit.Year),
+		},
+	)
+	if err != nil {
+		c_log.GlobalLogger.Error("发送http请求获取是否允许采集失败:", err)
+		return false
+	}
+	// 解析JSON字符串到Response结构体
+	var resp entity.Response
+	err = json.Unmarshal([]byte(responseString), &resp)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析是否允许采集接口返回结果失败:", err)
+		return false
+	}
+	if resp.Code != 200 { // 不是200 代表不允许采集
+		c_log.GlobalLogger.Info("采集数量已超过限额,当前周期内不再采集。", resp.Code)
+		return false
+	}
+	c_log.GlobalLogger.Info("允许采集。")
+	return true
+}

+ 33 - 0
aarch64/pjibot_guide/README.md

@@ -0,0 +1,33 @@
+# 一、由于机器人由启用单个相机变成两个相机,所以话题有多变化
+1、只启用了单个相机的机器
+/amcl_pose
+/camera/color/image_raw
+/camera/depth/points
+/diagnostics
+/locate_info
+/obstacle_detection
+/odom
+/move_base/global_costmap/costmap
+/move_base/global_costmap/costmap_updates     
+/move_base/local_costmap/costmap
+/move_base/local_costmap/costmap_updates
+/scan
+/scan_map_icp_amcl_node/scan_point_transformed
+/sys_info
+# 2、启用了两个相机的机器
+/amcl_pose
+/ob_camera_01/color/image_raw(上边的摄像头)
+/ob_camera_01/depth/points(上边的摄像头)
+/ob_camera_02/color/image_raw(下边的摄像头)
+/ob_camera_02/depth/points(下边的摄像头)
+/diagnostics
+/locate_info
+/obstacle_detection(有引导任务的时候才会有)
+/odom
+/move_base/global_costmap/costmap
+/move_base/global_costmap/costmap_updates     
+/move_base/local_costmap/costmap
+/move_base/local_costmap/costmap_updates
+/scan
+/scan_map_icp_amcl_node/scan_point_transformed
+/sys_info

+ 335 - 0
aarch64/pjibot_guide/common/config/c_cloud.go

@@ -0,0 +1,335 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/gorilla/websocket"
+	"gopkg.in/yaml.v3"
+	"net/url"
+	"os"
+	"strings"
+	"sync"
+	"time"
+)
+
+type MonitorStruct struct {
+	Url string `yaml:"url"`
+}
+
+type platform struct {
+	UrlDeviceAuth string `yaml:"url-device-auth"`
+	UrlTaskPoll   string `yaml:"url-task-poll"`
+	UrlTask       string `yaml:"url-task"`
+}
+
+type rosbagStruct struct {
+	Path string   `yaml:"path"`
+	Envs []string `yaml:"envs"`
+}
+
+type hostStruct struct {
+	Name   string       `yaml:"name"`
+	Ip     string       `yaml:"ip"`
+	Topics []string     `yaml:"topics"`
+	Rosbag rosbagStruct `yaml:"rosbag"`
+}
+
+type ros struct {
+	MasterAddress string   `yaml:"master-address"`
+	Nodes         []string `yaml:"nodes"`
+}
+
+type disk struct {
+	Name string `yaml:"name"`
+	Used uint64 `yaml:"used"`
+}
+type trigger struct {
+	Label  string   `yaml:"label"`
+	Topics []string `yaml:"topics"`
+}
+
+type CollectLimitStruct struct {
+	Url   string `yaml:"url"`
+	Day   int    `yaml:"day"`
+	Week  int    `yaml:"week"`
+	Month int    `yaml:"month"`
+	Year  int    `yaml:"year"`
+}
+
+type cloudConfig struct {
+	CollectLimit          CollectLimitStruct `yaml:"collect-limit"`
+	MapBufFiles           []string           `yaml:"map-buf-files"`
+	FullCollect           bool               `yaml:"full-collect"`
+	ConfigRefreshInterval int                `yaml:"config-refresh-interval"` // 配置刷新时间间隔
+	BagNumber             int                `yaml:"bag-number"`
+	TimeWindowSendGap     int                `yaml:"time-window-send-gap"` // 主节点向从节点发送窗口的最小时间间隔
+	MapBagPath            string             `yaml:"map-bag-path"`
+	TfstaticBagPath       string             `yaml:"tfstatic-bag-path"`
+	CostmapBagPath        string             `yaml:"costmap-bag-path"`
+	BagDataDir            string             `yaml:"bag-data-dir"`
+	BagCopyDir            string             `yaml:"bag-copy-dir"`
+	TriggersDir           string             `yaml:"triggers-dir"`
+	RpcPort               string             `yaml:"rpc-port"`
+	Triggers              []trigger          `yaml:"triggers"`
+	Hosts                 []hostStruct       `yaml:"hosts"`
+	Ros                   ros                `yaml:"ros"`
+	Platform              platform           `yaml:"platform"`
+	Disk                  disk               `yaml:"disk"`
+	Monitor               MonitorStruct      `yaml:"monitor"`
+}
+
+// Request 结构体定义
+type Request struct {
+	Type      string      `json:"type"`
+	UUID      string      `json:"uuid"`
+	CommandID string      `json:"commandId"`
+	Parameter interface{} `json:"parameter"`
+}
+
+// Response 结构体定义
+type Response struct {
+	CommandID string            `json:"commandId"`
+	ErrorCode string            `json:"errorCode"`
+	Results   map[string]string `json:"results"`
+	Status    string            `json:"status"`
+	Time      int64             `json:"time"`
+	Type      string            `json:"type"`
+	UUID      string            `json:"uuid"`
+}
+
+var (
+	CloudConfig      cloudConfig
+	CloudConfigMutex sync.RWMutex
+)
+
+// InitCloudConfig 初始化业务配置
+func InitCloudConfig() {
+	// history20240401:朴津机器人额外加一个获取sn码
+	var snCode string
+
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		snCode, err := getSnCode()
+		if err != nil {
+			c_log.GlobalLogger.Error("获取sn码失败:", err.Error())
+			continue
+		}
+		LocalConfig.SecretKey = snCode
+		LocalConfig.EquipmentNo = "pjibot-" + snCode
+		break
+	}
+	c_log.GlobalLogger.Info("本地机器人sn码为:", snCode)
+
+	c_log.GlobalLogger.Info("初始化OSS配置文件 - 开始。")
+	// 获取文件的目录
+	_ = util.CreateParentDir(LocalConfig.CloudConfigLocalPath)
+	// 3 ------- 获取 yaml 字符串 -------
+	cloudConfigObjectKey := LocalConfig.OssBasePrefix + LocalConfig.EquipmentNo + "/" + LocalConfig.CloudConfigFilename
+
+	// 判断文件是否存在。如果不存在则使用默认的
+	isExist, err := OssBucket.IsObjectExist(cloudConfigObjectKey)
+	if err != nil {
+		c_log.GlobalLogger.Errorf("判断配置文件是否存在失败,错误信息为:%v", err)
+	}
+	if isExist {
+		c_log.GlobalLogger.Info("使用机器人自定义配置文件:", cloudConfigObjectKey)
+	} else {
+		cloudConfigObjectKey = LocalConfig.OssBasePrefix + LocalConfig.CloudConfigFilename // 默认配置文件路径
+		c_log.GlobalLogger.Info("使用机器人默认配置文件:", cloudConfigObjectKey)
+	}
+
+	for {
+		OssMutex.Lock()
+		err := OssBucket.GetObjectToFile(cloudConfigObjectKey, LocalConfig.CloudConfigLocalPath)
+		OssMutex.Unlock()
+		if err != nil {
+			c_log.GlobalLogger.Error("下载 OSS 上的配置文件 "+cloudConfigObjectKey+" 失败,请尽快在 OSS 上传配置文件。", err)
+			time.Sleep(time.Duration(2) * time.Second)
+			continue
+		}
+		break
+	}
+
+	content, err := os.ReadFile(LocalConfig.CloudConfigLocalPath)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序崩溃,配置文件 ", LocalConfig.CloudConfigLocalPath, " 读取失败:", err)
+		os.Exit(-1)
+	}
+
+	// 4 ------- 解析YAML内容 -------
+	var newCloudConfig cloudConfig
+	err = yaml.Unmarshal(content, &newCloudConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序崩溃,配置文件 ", LocalConfig.CloudConfigLocalPath, " 解析失败:", err)
+		os.Exit(-1)
+	}
+
+	// 5 ------- 校验 yaml -------
+	if checkCloudConfig(newCloudConfig) {
+		CloudConfigMutex.RLock()
+		CloudConfig = newCloudConfig
+		CloudConfigMutex.RUnlock()
+	} else {
+		c_log.GlobalLogger.Error("程序崩溃,配置文件格式错误:", newCloudConfig)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("初始化OSS配置文件 - 成功。")
+	util.CreateDir(CloudConfig.BagDataDir)
+	util.CreateDir(CloudConfig.BagCopyDir)
+}
+
+// refreshCloudConfig 更新业务配置
+func refreshCloudConfig() {
+	// 获取文件的目录
+	_ = util.CreateParentDir(LocalConfig.CloudConfigLocalPath)
+	// 3 ------- 获取 yaml 字符串 -------
+	var content []byte
+	cloudConfigObjectKey := LocalConfig.OssBasePrefix + LocalConfig.EquipmentNo + "/" + LocalConfig.CloudConfigFilename
+
+	isExist, err := OssBucket.IsObjectExist(cloudConfigObjectKey)
+	if err != nil {
+		c_log.GlobalLogger.Errorf("判断配置文件是否存在失败,错误信息为:%v", err)
+	}
+	if !isExist {
+		cloudConfigObjectKey = LocalConfig.OssBasePrefix + LocalConfig.CloudConfigFilename // 默认配置文件路径
+	}
+
+	OssMutex.Lock()
+	err = OssBucket.GetObjectToFile(cloudConfigObjectKey, LocalConfig.CloudConfigLocalPath)
+	OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("下载oss上的配置文件"+cloudConfigObjectKey+"失败。", err)
+		//os.Exit(-1)
+	}
+
+	content, err = os.ReadFile(LocalConfig.CloudConfigLocalPath)
+	if err != nil {
+		c_log.GlobalLogger.Error("配置文件 ", LocalConfig.CloudConfigLocalPath, " 读取失败:", err)
+		os.Exit(-1)
+	}
+
+	// 4 ------- 解析YAML内容 -------
+	var newCloudConfig cloudConfig
+	err = yaml.Unmarshal(content, &newCloudConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("配置文件 ", LocalConfig.CloudConfigLocalPath, " 解析失败:", err)
+		os.Exit(-1)
+	}
+
+	// 5 ------- 校验 yaml -------
+	if checkCloudConfig(newCloudConfig) {
+		CloudConfigMutex.RLock()
+		CloudConfig = newCloudConfig
+		CloudConfigMutex.RUnlock()
+	} else {
+		c_log.GlobalLogger.Error("配置文件格式错误:", newCloudConfig)
+		os.Exit(-1)
+	}
+	util.CreateDir(CloudConfig.BagDataDir)
+	util.CreateDir(CloudConfig.BagCopyDir)
+
+}
+
+// RefreshCloudConfig 轮询oss上的配置文件更新到本地
+func RefreshCloudConfig() {
+	for {
+		time.Sleep(time.Duration(CloudConfig.ConfigRefreshInterval) * time.Second)
+		refreshCloudConfig()
+	}
+}
+
+// CheckConfig 校验 cfg.yaml 文件
+func checkCloudConfig(check cloudConfig) bool {
+	if len(check.Hosts) != 1 {
+		c_log.GlobalLogger.Error("cloud-config.yaml中配置的hosts必须为1。")
+		os.Exit(-1)
+	}
+	return true
+}
+
+func getSnCode() (string, error) {
+	if LocalConfig.Type == "1" {
+		var command []string
+		command = append(command, "get")
+		command = append(command, "sn")
+		_, snOutput, err := util.ExecuteSync(LocalConfig.RosparamPath, command...)
+		if err != nil {
+			return "", errors.New("执行获取sn码命令" + util.ToString(command) + "出错:" + util.ToString(err))
+		}
+		c_log.GlobalLogger.Info("执行获取sn码命令", command, "成功,结果为:", snOutput)
+		snCode := strings.Replace(strings.Replace(snOutput, " ", "", -1), "\n", "", -1)
+		return snCode, nil
+	} else if LocalConfig.Type == "2" || LocalConfig.Type == "3" {
+		// 示例使用
+		serverURL := "192.168.1.104:9002"
+		path := "/"
+		request := Request{
+			Type:      "request",
+			UUID:      "",
+			CommandID: "getRobotBaseInfo",
+			Parameter: nil,
+		}
+
+		sn, err := SendWebsocketRequest(serverURL, path, request)
+		if err != nil {
+			return "", errors.New("通过api获取sn码失败:" + util.ToString(err))
+		}
+		return sn, nil
+	} else {
+		c_log.GlobalLogger.Error("程序崩溃,未知的机器人类型【" + LocalConfig.Type + "】,请修改local-config.yaml文件")
+		os.Exit(-1)
+		return "", nil
+	}
+}
+
+// SendWebsocketRequest 发送WebSocket请求并返回sn字段的值
+func SendWebsocketRequest(serverURL, path string, request Request) (string, error) {
+	// 构建WebSocket连接URL
+	u := url.URL{Scheme: "ws", Host: serverURL, Path: path}
+
+	// 创建一个Dialer实例,用于建立WebSocket连接
+	dialer := websocket.Dialer{
+		ReadBufferSize:  1024,
+		WriteBufferSize: 1024,
+		// 可选:设置超时等
+		HandshakeTimeout: 5 * time.Second,
+	}
+
+	// 建立WebSocket连接
+	conn, _, err := dialer.Dial(u.String(), nil)
+	if err != nil {
+		return "", fmt.Errorf("dial: %w", err)
+	}
+	defer conn.Close()
+
+	// 将请求JSON编码为字节
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return "", fmt.Errorf("marshal request: %w", err)
+	}
+
+	// 发送WebSocket消息
+	err = conn.WriteMessage(websocket.TextMessage, requestJSON)
+	if err != nil {
+		return "", fmt.Errorf("write: %w", err)
+	}
+
+	// 读取WebSocket响应
+	_, responseBytes, err := conn.ReadMessage()
+	if err != nil {
+		return "", fmt.Errorf("read: %w", err)
+	}
+
+	// 将响应字节解码为JSON
+	var response Response
+	err = json.Unmarshal(responseBytes, &response)
+	if err != nil {
+		return "", fmt.Errorf("unmarshal response: %w", err)
+	}
+
+	// 返回sn字段的值
+	return response.Results["sn"], nil
+}

+ 21 - 0
aarch64/pjibot_guide/common/config/c_killrpcserver.go

@@ -0,0 +1,21 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"net"
+	"os"
+)
+
+var KillSignalListener net.Listener
+
+func InitKillSignalListener(serverIp string) {
+	var err error
+	c_log.GlobalLogger.Info("初始化RPC端口监听Kill信号 - 开始。")
+	socket := serverIp + ":" + CloudConfig.RpcPort
+	KillSignalListener, err = net.Listen("tcp", socket)
+	if err != nil {
+		c_log.GlobalLogger.Error("监听rpc端口失败:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("初始化RPC端口监听Kill信号 - 成功:", socket)
+}

+ 62 - 0
aarch64/pjibot_guide/common/config/c_local.go

@@ -0,0 +1,62 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"gopkg.in/yaml.v2"
+	"os"
+)
+
+type node struct {
+	Name string `yaml:"name"`
+	Ip   string `yaml:"ip"`
+}
+
+type restartCmd struct {
+	Dir  string   `yaml:"dir"`
+	Name string   `yaml:"name"`
+	Args []string `yaml:"args"`
+}
+
+type localConfig struct {
+	Type                 string     `yaml:"type"`                    // 机器人类型(不同机器人获取sn码方式不同)
+	Node                 node       `yaml:"node"`                    // 节点信息
+	RosparamPath         string     `yaml:"rosparam-path"`           // 获取oss配置的url
+	UrlGetOssConfig      string     `yaml:"url-get-oss-config"`      // 获取oss配置的url
+	OssBasePrefix        string     `yaml:"oss-base-prefix"`         // 云端配置文件的位置
+	CloudConfigFilename  string     `yaml:"cloud-config-filename"`   // 云端配置文件名称
+	CloudConfigLocalPath string     `yaml:"cloud-config-local-path"` // 将 oss 的配置文件下载到本地的位置
+	RestartCmd           restartCmd `yaml:"restart-cmd"`             // 重启命令
+	EquipmentNo          string     // 当前设备的编号
+	SecretKey            string     // 当前设备的密钥
+}
+
+var (
+	LocalConfig localConfig
+)
+
+func InitLocalConfig(localConfigPath string) {
+	c_log.GlobalLogger.Info("初始化本地配置文件 - 开始:", localConfigPath)
+	// 读取YAML文件内容
+	content, err := os.ReadFile(localConfigPath)
+	if err != nil {
+		c_log.GlobalLogger.Error("读取本地配置文件失败。", err)
+		os.Exit(-1)
+	}
+
+	// 解析YAML内容
+	err = yaml.Unmarshal(content, &LocalConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析本地配置文件失败。", err)
+		os.Exit(-1)
+	}
+
+	// history20240401:设备密钥需要获取sn码,设备编号同样。######由于执行命令需要环境变量,所以放到 c_cloud.go 中####
+	/*
+		# 例如,数据闭环平台参数
+		equipment-no: pjibot-P1YNYD1M228000127
+		secret-key: P1YNYD1M228000127
+	*/
+
+	c_log.GlobalLogger.Info("初始化本地配置文件 - 成功:", LocalConfig)
+
+}

+ 52 - 0
aarch64/pjibot_guide/common/config/c_oss.go

@@ -0,0 +1,52 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+	"os"
+	"sync"
+)
+
+type OssConnectInfoStruct struct {
+	Endpoint        string `json:"endpoint"`
+	AccessKeyId     string `json:"accessKeyId"`
+	AccessKeySecret string `json:"accessKeySecret"`
+	BucketName      string `json:"bucketName"`
+}
+
+var (
+	OssClient *oss.Client
+	OssBucket *oss.Bucket
+	OssMutex  sync.Mutex
+)
+
+func InitOssConfig() {
+	c_log.GlobalLogger.Info("初始化OSS客户端对象 - 开始。")
+	// 1 访问 HTTP 服务获取 OSS 配置
+	get, err := util.HttpGet(LocalConfig.UrlGetOssConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("http获取oss配置时出错:", err)
+		os.Exit(-1)
+	}
+	var ossConnectInfo OssConnectInfoStruct
+	err = json.Unmarshal([]byte(get), &ossConnectInfo)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析json时出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Infof("oss 配置信息为:%v", ossConnectInfo)
+
+	OssClient, err = oss.New(ossConnectInfo.Endpoint, ossConnectInfo.AccessKeyId, ossConnectInfo.AccessKeySecret, oss.UseCname(true))
+	if err != nil {
+		c_log.GlobalLogger.Error("无法创建阿里云client:", err)
+		os.Exit(-1)
+	}
+	OssBucket, err = OssClient.Bucket(ossConnectInfo.BucketName)
+	if err != nil {
+		c_log.GlobalLogger.Error("无法创建阿里云bucket:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("初始化OSS客户端对象 - 成功。")
+}

+ 193 - 0
aarch64/pjibot_guide/common/config/c_platform.go

@@ -0,0 +1,193 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"strings"
+	"time"
+)
+
+type taskTrigger struct {
+	TriggerId         int    `json:"triggerId"`
+	TriggerName       string `json:"triggerName"`
+	TriggerScriptPath string `json:"triggerScriptPath"`
+	TriggerType       string `json:"triggerType"`
+}
+
+type PlatformConfigStruct struct {
+	TaskConfigId    string        `json:"taskConfigId"`   // 配置ID
+	TaskConfigName  string        `json:"taskConfigName"` // 配置名称
+	DropUploadData  bool          `json:"dropUploadData"` // 更新任务时 true 先上传旧任务 false 删除旧任务
+	TaskMaxTime     int           `json:"taskMaxTime"`
+	TaskBeforeTime  int           `json:"taskBeforeTime"`
+	TaskAfterTime   int           `json:"taskAfterTime"`
+	TaskCachePolicy string        `json:"taskCachePolicy"`
+	EquipmentTopic  string        `json:"equipmentTopic"` // topic序列
+	Lru             []string      `json:"LRU"`
+	TaskTriggers    []taskTrigger `json:"taskTriggers"`
+}
+
+type response struct {
+	Data    PlatformConfigStruct `json:"data"`
+	Success bool                 `json:"success"`
+	Message string               `json:"message"`
+	Code    int                  `json:"code"`
+	NowTime string               `json:"nowTime"`
+}
+
+var (
+	PlatformConfig  PlatformConfigStruct
+	SubscribeTopics []string
+)
+
+// InitPlatformConfig 初始化数据闭环平台的配置
+func InitPlatformConfig() {
+	var err error
+	c_log.GlobalLogger.Info("获取数据闭环平台配置 - 开始")
+	// 1 如果车辆没有配置任务,则阻塞在这里,不启动任务
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		// 判断是否有配置,第一次访问状态应该为:CHANGE(一共三种状态 CHANGE|UNCHANGE|NONE)
+		PlatformConfig, err = getConfig()
+		if err != nil {
+			c_log.GlobalLogger.Error("获取配置status失败:", err)
+			continue
+		}
+		if checkPlatformConfig() {
+			SubscribeTopics = strings.Split(PlatformConfig.EquipmentTopic, ",")
+			// 去掉首尾空格
+			for i, topic := range SubscribeTopics {
+				SubscribeTopics[i] = strings.TrimSpace(topic)
+			}
+			break
+		}
+	}
+	c_log.GlobalLogger.Info("获取数据闭环平台配置 - 成功。")
+}
+
+/*
+	{
+	  "data": {
+	    "accessToken": "YWRmYWRzZmFzZGZhZHNmYWRmYWRm=",
+	    "expireTime": "28800",
+	    "equipmentNo": "robot-001"
+	  },
+	  "success": true,
+	  "message": "ok",
+	  "code": 1,
+	  "nowTime": "2023-12-09 22:41:00"
+	}
+*/
+// GetAccessToken 认证接口,获取access_token
+func GetAccessToken() (string, error) {
+	respJson, err := util.HttpPostJsonResponseString(
+		CloudConfig.Platform.UrlDeviceAuth,
+		map[string]string{
+			"equipmentNo": LocalConfig.EquipmentNo,
+			"secretKey":   LocalConfig.SecretKey,
+		},
+	)
+	if err != nil {
+		return "", nil
+	}
+	respMap, err := util.JsonStringToMap(respJson)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析返回结果", respJson, "失败:", err)
+		return "", nil
+	}
+
+	dataMap, ok := respMap["data"].(map[string]interface{})
+	if !ok {
+		c_log.GlobalLogger.Error("解析返回结果.data", dataMap, "失败:", err)
+		return "", nil
+	}
+	return dataMap["accessToken"].(string), nil
+}
+
+/*
+	{
+	  "data": {
+	    "status": "UNCHANGE"
+	    "taskConfigld": "xxx"
+	  },
+	  "success": true,
+	  "message": "ok",
+	  "code": 1,
+	  "nowTime": "2023-12-09 21:08:49"
+	}
+*/
+//GetStatus 根据taskConfigId获取任务status,如果传入空代表车端没有配置,直接获取新的配置
+func GetStatus(taskConfigId string) (string, error) {
+	token, err := GetAccessToken()
+	if err != nil {
+		return "", err
+	}
+	resp, err := util.HttpGetStringAddHeadersResponseString(
+		CloudConfig.Platform.UrlTaskPoll,
+		map[string]string{
+			"authorization": token,
+		},
+		map[string]string{
+			"equipmentNo":  LocalConfig.EquipmentNo,
+			"taskConfigId": taskConfigId,
+		},
+	)
+
+	if err != nil {
+		c_log.GlobalLogger.Error("访问接口", CloudConfig.Platform.UrlTask, "失败:", err)
+		return "", err
+	}
+	respMap, err := util.JsonStringToMap(resp)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析【返回结果1】", resp, "失败:", err)
+		return "", err
+	}
+	dataMap, ok := respMap["data"].(map[string]interface{})
+	if !ok {
+		c_log.GlobalLogger.Errorf("解析【返回结果.data】的类型不是(map[string]interface{}),【dataMap】=%v", dataMap)
+		return "", err
+	}
+	return dataMap["status"].(string), nil
+}
+
+func getConfig() (PlatformConfigStruct, error) {
+	token, err := GetAccessToken()
+	if err != nil {
+		return PlatformConfigStruct{}, err
+	}
+	// 下载插件和获取配置
+	// 2 访问配置获取接口
+	resp, err := util.HttpGetStringAddHeadersResponseString(
+		CloudConfig.Platform.UrlTask,
+		map[string]string{
+			"authorization": token,
+		},
+		map[string]string{
+			"equipmentNo": LocalConfig.EquipmentNo,
+		},
+	)
+	if err != nil {
+		c_log.GlobalLogger.Error("访问接口", CloudConfig.Platform.UrlTask, "失败:", err)
+		return PlatformConfigStruct{}, err
+	}
+	var result response
+	err = json.Unmarshal([]byte(resp), &result)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析【返回结果】", resp, "失败:", err)
+		return PlatformConfigStruct{}, err
+	}
+	return result.Data, nil
+}
+
+func checkPlatformConfig() bool {
+	if PlatformConfig.TaskConfigId == "" {
+		c_log.GlobalLogger.Error("数据闭环平台没有配置任务。")
+		return false
+	}
+	if PlatformConfig.EquipmentTopic == "" {
+		c_log.GlobalLogger.Error("数据闭环平台没有配置topic序列。")
+		return false
+	}
+	return true
+}

+ 35 - 0
aarch64/pjibot_guide/common/config/c_resource.go

@@ -0,0 +1,35 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"time"
+)
+
+// SendResourceUsage 保存资源占用情况
+func SendResourceUsage() {
+	for {
+		time.Sleep(time.Duration(1) * time.Second)
+		top10Cpu, top10Mem := util.GetTop10CpuAndMem()
+		top10CpuJson, _ := json.MarshalIndent(top10Cpu, "", "    ")
+		top10MemJson, _ := json.MarshalIndent(top10Mem, "", "    ")
+		responseString, err := util.HttpPostJsonWithHeaders(
+			CloudConfig.Monitor.Url,
+			map[string]string{"Authorization": "U9yKpD6kZZDDe4LFKK6myAxBUT1XRrDM"},
+			map[string]string{
+				"totalCpuUsage":    util.ToString(util.GetCpuPercent()),
+				"totalMemoryUsage": util.ToString(util.GetMemoryPercent()),
+				"top10Process":     string(top10CpuJson),
+				"top10Cpu":         string(top10CpuJson),
+				"top10Mem":         string(top10MemJson),
+				"deviceNumber":     LocalConfig.EquipmentNo,
+				"socIp":            LocalConfig.Node.Ip,
+			},
+		)
+		if err != nil {
+			c_log.GlobalLogger.Errorf("发送数据监控信息报错%v,响应信息为:%v", err, responseString)
+		}
+		//c_log.GlobalLogger.Infof("发送数据监控信息成功,响应信息为:%v", responseString)
+	}
+}

+ 39 - 0
aarch64/pjibot_guide/common/config/c_ros.go

@@ -0,0 +1,39 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"github.com/bluenviron/goroslib/v2"
+	"time"
+)
+
+var (
+	RosNode    *goroslib.Node
+	RosbagPath string
+	RosbagEnvs []string
+)
+
+func InitRosConfig() {
+	var err error
+	// 1
+	c_log.GlobalLogger.Info("初始化RosNode - 开始")
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		if RosNode, err = goroslib.NewNode(goroslib.NodeConf{Name: "node" + util.GetNowTimeCustom(), MasterAddress: CloudConfig.Ros.MasterAddress}); err != nil {
+			c_log.GlobalLogger.Info("初始化RosNode - 进行中:", err)
+			continue
+		}
+		break
+	}
+	c_log.GlobalLogger.Info("初始化RosNode - 成功:", CloudConfig.Ros.MasterAddress)
+	// 2 获取 rosbag 命令路径和环境变量
+	for _, host := range CloudConfig.Hosts {
+		if host.Name == LocalConfig.Node.Name {
+			RosbagPath = host.Rosbag.Path
+			RosbagEnvs = host.Rosbag.Envs
+			break
+		}
+	}
+	c_log.GlobalLogger.Infof("rosbag 命令路径为:%v,环境变量为:%v", RosbagPath, RosbagEnvs)
+
+}

+ 3 - 0
aarch64/pjibot_guide/common/config/sh/start-control.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+chmod 777 /root/cicv-data-closedloop/pji-control.exe
+nohup /root/cicv-data-closedloop/pji-control.exe > /root/cicv-data-closedloop/log/pji-control.out 2>&1 &

+ 3 - 0
aarch64/pjibot_guide/common/config/sh/start-master.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+chmod 777 /root/cicv-data-closedloop/pji-master.exe
+nohup /root/cicv-data-closedloop/pji-master.exe > /root/cicv-data-closedloop/log/pji-master.out 2>&1 &

+ 0 - 0
aarch64/pji/common/config/yaml/巡检机器人默认配置文件-cloud-config.yaml → aarch64/pjibot_guide/common/config/yaml/巡检机器人默认配置文件-cloud-config.yaml


+ 0 - 0
aarch64/pji/common/config/yaml/巡检机器人默认配置文件-local-config.yaml → aarch64/pjibot_guide/common/config/yaml/巡检机器人默认配置文件-local-config.yaml


+ 0 - 0
aarch64/pji/common/config/yaml/引导机器人默认配置文件单摄像头-cloud-config.yaml → aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件单摄像头-cloud-config.yaml


+ 0 - 0
aarch64/pji/common/config/yaml/引导机器人默认配置文件单摄像头-local-config.yaml → aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件单摄像头-local-config.yaml


+ 0 - 0
aarch64/pji/common/config/yaml/引导机器人默认配置文件双摄像头-cloud-config.yaml → aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件双摄像头-cloud-config.yaml


+ 0 - 0
aarch64/pji/common/config/yaml/引导机器人默认配置文件双摄像头-local-config.yaml → aarch64/pjibot_guide/common/config/yaml/引导机器人默认配置文件双摄像头-local-config.yaml


+ 159 - 0
aarch64/pjibot_guide/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml

@@ -0,0 +1,159 @@
+---
+monitor:
+  url: http://36.110.106.142:12341/web_server/monitor/insert
+platform:
+  url-device-auth: http://1.202.169.139:8081/device/auth
+  url-task-poll: http://1.202.169.139:8081/device/task/poll
+  url-task: http://1.202.169.139:8081/device/task
+
+bag-number: 60
+config-refresh-interval: 60
+disk:
+  name: /dev/sda1 # 磁盘名称
+  used: 60000000000 # 磁盘占用阈值,单位bytes
+map-bag-path: /root/cicv-data-closedloop/map.bag
+tfstatic-bag-path: /root/cicv-data-closedloop/tf_static.bag
+costmap-bag-path: /root/cicv-data-closedloop/costmap.bag
+bag-data-dir: /root/cicv-data-closedloop/data/
+bag-copy-dir: /root/cicv-data-closedloop/copy/
+triggers-dir: /root/cicv-data-closedloop/triggers/
+time-window-send-gap: 6
+rpc-port: 12341
+ros:
+  master-address: 192.168.1.104:11311
+  nodes:
+    - /amcl
+    - /ob_camera_01/camera
+    - /ob_camera_02/camera
+    - /node_diagnostics
+    - /localization_monitor_node
+    - /move_base
+    - /sensor_fusion_node
+    - /ltme_node
+    - /scan_map_icp_amcl_node
+    - /monitor
+
+hosts:
+  - name: node1
+    ip: 192.168.1.104
+    rosbag:
+      path: "/opt/ros/melodic/bin/rosbag"
+      envs:
+        - "C_INCLUDE_PATH=/usr/include/drm:"
+        - "USER=root"
+        - "ROS_PACKAGE_PATH=/opt/ros/melodic/share"
+        - "LD_LIBRARY_PATH=/opt/ros/melodic/lib:/opt/ros/melodic/lib/aarch64-linux-gnu"
+        - "ROS_ETC_DIR=/opt/ros/melodic/etc/ros"
+        - "SHLVL=1"
+        - "HOME=/root"
+        - "ROS_PYTHON_VERSION=2"
+        - "PCMANFM_OUTLINE_MODE=on"
+        - "CPLUS_INCLUDE_PATH=/usr/include/drm:"
+        - "ROS_DISTRO=melodic"
+        - "ROS_VERSION=1"
+        - "PKG_CONFIG_PATH=/opt/ros/melodic/lib/pkgconfig"
+        - "PATH=/opt/ros/melodic/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/root/go/bin"
+        - "ROS_ROOT=/opt/ros/melodic/share/ros"
+        - "ROSLISP_PACKAGE_DIRECTORIES="
+        - "ROS_MASTER_URI=http://192.168.1.104:11311"
+        - "PYTHONPATH=/opt/ros/melodic/lib/python2.7/dist-packages"
+        - "ROS_HOSTNAME=192.168.1.104"
+        - "CMAKE_PREFIX_PATH=/opt/ros/melodic"
+    topics:
+      - /robot_pose
+      - /robot/realtime_cost_map_
+      - /tracking/objects
+      - /robot/TaskInfo
+      - /robot/targetposition
+      - /wheel
+      - /wheel_odom
+      - /robot/global_trajectory_
+      - /robot/target_trajectories
+      - /robot/evaluator_trajectories
+      - /robot/final_trajectory
+      - /nav/task_feedback_info
+      - /cmd_vel
+      - /imu
+      - /points_cluster
+      - /nav/task_feedback_info
+
+
+full-collect: true # 控制是否根据不同的触发器采集不通的topic,一般设置为true,即忽略下面的配置
+triggers:
+  - label: detectfault
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: unstabledriving
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: locationfailed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: obstacledetection
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: overspeed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: cpuoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: memoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed

+ 19 - 0
aarch64/pjibot_guide/common/config/yaml/配送机器人默认配置文件-local-config.yaml

@@ -0,0 +1,19 @@
+type: 2 # 机器人类型 1 引导机器人 2 配送机器人 3 巡检机器人
+node:
+  name: node1
+  ip: 192.168.1.104
+rosparam-path: /opt/ros/melodic/bin/rosparam
+# 获取oss连接信息的接口url
+#url-get-oss-config: http://36.110.106.156:18379/oss/config?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD # 国汽数据闭环
+url-get-oss-config: http://36.110.106.156:18379/oss/pji?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD # 朴津数据闭环
+# 朴津机器人数据前缀
+oss-base-prefix: pjibot-delivery/
+# oss上的配置文件的名称
+cloud-config-filename: cloud-config.yaml
+# 将oss上的配置文件下载到本地的路径
+cloud-config-local-path: /root/cicv-data-closedloop/config/cloud-config.yaml
+restart-cmd:
+  dir: "/root/cicv-data-closedloop/"
+  name: "sh"
+  args:
+    - "start-master.sh"

+ 2 - 2
aarch64/pji/common/service/disk_clean.go → aarch64/pjibot_guide/common/service/disk_clean.go

@@ -1,8 +1,8 @@
 package service
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
-	masterConfig "cicv-data-closedloop/aarch64/pji/master/package/config"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_guide/master/package/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/domain"
 	"cicv-data-closedloop/common/entity"

+ 1 - 1
aarch64/pji/common/service/kill_self.go → aarch64/pjibot_guide/common/service/kill_self.go

@@ -1,7 +1,7 @@
 package service
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"net/rpc"

+ 1 - 1
aarch64/pji/common/service/rosbag_clean.go → aarch64/pjibot_guide/common/service/rosbag_clean.go

@@ -1,7 +1,7 @@
 package service
 
 import (
-	"cicv-data-closedloop/aarch64/pji/common/config"
+	"cicv-data-closedloop/aarch64/pjibot_guide/common/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"time"

+ 1 - 1
aarch64/pji/common/service/rosbag_record.go → aarch64/pjibot_guide/common/service/rosbag_record.go

@@ -1,7 +1,7 @@
 package service
 
 import (
-	"cicv-data-closedloop/aarch64/pji/common/config"
+	"cicv-data-closedloop/aarch64/pjibot_guide/common/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"github.com/bluenviron/goroslib/v2"

+ 2 - 2
aarch64/pji/common/service/rosbag_upload.go → aarch64/pjibot_guide/common/service/rosbag_upload.go

@@ -1,8 +1,8 @@
 package service
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
-	masterConfig "cicv-data-closedloop/aarch64/pji/master/package/config"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_guide/master/package/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/domain"
 	"cicv-data-closedloop/common/entity"

+ 6 - 0
aarch64/pjibot_guide/common/variable/application.go

@@ -0,0 +1,6 @@
+package variable
+
+var (
+	LogDir          = "/root/cicv-data-closedloop/log/"
+	LocalConfigPath = "/root/cicv-data-closedloop/config/local-config.yaml"
+)

+ 3 - 3
aarch64/pji/control/main.go → aarch64/pjibot_guide/control/main.go

@@ -1,9 +1,9 @@
 package main
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
-	commonService "cicv-data-closedloop/aarch64/pji/common/service"
-	"cicv-data-closedloop/aarch64/pji/common/variable"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_guide/common/service"
+	"cicv-data-closedloop/aarch64/pjibot_guide/common/variable"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"net/rpc"

+ 5 - 5
aarch64/pji/master/main.go → aarch64/pjibot_guide/master/main.go

@@ -1,11 +1,11 @@
 package main
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
-	commonService "cicv-data-closedloop/aarch64/pji/common/service"
-	"cicv-data-closedloop/aarch64/pji/common/variable"
-	masterConfig "cicv-data-closedloop/aarch64/pji/master/package/config"
-	masterService "cicv-data-closedloop/aarch64/pji/master/package/service"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_guide/common/service"
+	"cicv-data-closedloop/aarch64/pjibot_guide/common/variable"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_guide/master/package/config"
+	masterService "cicv-data-closedloop/aarch64/pjibot_guide/master/package/service"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"runtime"

+ 1 - 1
aarch64/pji/master/package/config/master_trigger_cfg.go → aarch64/pjibot_guide/master/package/config/master_trigger_cfg.go

@@ -1,7 +1,7 @@
 package config
 
 import (
-	"cicv-data-closedloop/aarch64/pji/common/config"
+	"cicv-data-closedloop/aarch64/pjibot_guide/common/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"cicv-data-closedloop/pji_msgs"

+ 1 - 1
aarch64/pji/master/package/service/collect_one_msg.go → aarch64/pjibot_guide/master/package/service/collect_one_msg.go

@@ -1,7 +1,7 @@
 package service
 
 import (
-	"cicv-data-closedloop/aarch64/pji/common/config"
+	"cicv-data-closedloop/aarch64/pjibot_guide/common/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/util"
 	"os"

+ 2 - 2
aarch64/pji/master/package/service/move_bag_and_send_window.go → aarch64/pjibot_guide/master/package/service/move_bag_and_send_window.go

@@ -1,8 +1,8 @@
 package service
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
-	commonService "cicv-data-closedloop/aarch64/pji/common/service"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_guide/common/service"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/domain"
 	"cicv-data-closedloop/common/entity"

+ 3 - 3
aarch64/pji/master/package/service/produce_window.go → aarch64/pjibot_guide/master/package/service/produce_window.go

@@ -1,9 +1,9 @@
 package service
 
 import (
-	commonConfig "cicv-data-closedloop/aarch64/pji/common/config"
-	commonService "cicv-data-closedloop/aarch64/pji/common/service"
-	masterConfig "cicv-data-closedloop/aarch64/pji/master/package/config"
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_guide/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_guide/common/service"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_guide/master/package/config"
 	"cicv-data-closedloop/common/config/c_log"
 	"cicv-data-closedloop/common/entity"
 	"cicv-data-closedloop/common/util"

+ 33 - 0
aarch64/pjibot_patrol/README.md

@@ -0,0 +1,33 @@
+# 一、由于机器人由启用单个相机变成两个相机,所以话题有多变化
+1、只启用了单个相机的机器
+/amcl_pose
+/camera/color/image_raw
+/camera/depth/points
+/diagnostics
+/locate_info
+/obstacle_detection
+/odom
+/move_base/global_costmap/costmap
+/move_base/global_costmap/costmap_updates     
+/move_base/local_costmap/costmap
+/move_base/local_costmap/costmap_updates
+/scan
+/scan_map_icp_amcl_node/scan_point_transformed
+/sys_info
+# 2、启用了两个相机的机器
+/amcl_pose
+/ob_camera_01/color/image_raw(上边的摄像头)
+/ob_camera_01/depth/points(上边的摄像头)
+/ob_camera_02/color/image_raw(下边的摄像头)
+/ob_camera_02/depth/points(下边的摄像头)
+/diagnostics
+/locate_info
+/obstacle_detection(有引导任务的时候才会有)
+/odom
+/move_base/global_costmap/costmap
+/move_base/global_costmap/costmap_updates     
+/move_base/local_costmap/costmap
+/move_base/local_costmap/costmap_updates
+/scan
+/scan_map_icp_amcl_node/scan_point_transformed
+/sys_info

+ 335 - 0
aarch64/pjibot_patrol/common/config/c_cloud.go

@@ -0,0 +1,335 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/gorilla/websocket"
+	"gopkg.in/yaml.v3"
+	"net/url"
+	"os"
+	"strings"
+	"sync"
+	"time"
+)
+
+type MonitorStruct struct {
+	Url string `yaml:"url"`
+}
+
+type platform struct {
+	UrlDeviceAuth string `yaml:"url-device-auth"`
+	UrlTaskPoll   string `yaml:"url-task-poll"`
+	UrlTask       string `yaml:"url-task"`
+}
+
+type rosbagStruct struct {
+	Path string   `yaml:"path"`
+	Envs []string `yaml:"envs"`
+}
+
+type hostStruct struct {
+	Name   string       `yaml:"name"`
+	Ip     string       `yaml:"ip"`
+	Topics []string     `yaml:"topics"`
+	Rosbag rosbagStruct `yaml:"rosbag"`
+}
+
+type ros struct {
+	MasterAddress string   `yaml:"master-address"`
+	Nodes         []string `yaml:"nodes"`
+}
+
+type disk struct {
+	Name string `yaml:"name"`
+	Used uint64 `yaml:"used"`
+}
+type trigger struct {
+	Label  string   `yaml:"label"`
+	Topics []string `yaml:"topics"`
+}
+
+type CollectLimitStruct struct {
+	Url   string `yaml:"url"`
+	Day   int    `yaml:"day"`
+	Week  int    `yaml:"week"`
+	Month int    `yaml:"month"`
+	Year  int    `yaml:"year"`
+}
+
+type cloudConfig struct {
+	CollectLimit          CollectLimitStruct `yaml:"collect-limit"`
+	MapBufFiles           []string           `yaml:"map-buf-files"`
+	FullCollect           bool               `yaml:"full-collect"`
+	ConfigRefreshInterval int                `yaml:"config-refresh-interval"` // 配置刷新时间间隔
+	BagNumber             int                `yaml:"bag-number"`
+	TimeWindowSendGap     int                `yaml:"time-window-send-gap"` // 主节点向从节点发送窗口的最小时间间隔
+	MapBagPath            string             `yaml:"map-bag-path"`
+	TfstaticBagPath       string             `yaml:"tfstatic-bag-path"`
+	CostmapBagPath        string             `yaml:"costmap-bag-path"`
+	BagDataDir            string             `yaml:"bag-data-dir"`
+	BagCopyDir            string             `yaml:"bag-copy-dir"`
+	TriggersDir           string             `yaml:"triggers-dir"`
+	RpcPort               string             `yaml:"rpc-port"`
+	Triggers              []trigger          `yaml:"triggers"`
+	Hosts                 []hostStruct       `yaml:"hosts"`
+	Ros                   ros                `yaml:"ros"`
+	Platform              platform           `yaml:"platform"`
+	Disk                  disk               `yaml:"disk"`
+	Monitor               MonitorStruct      `yaml:"monitor"`
+}
+
+// Request 结构体定义
+type Request struct {
+	Type      string      `json:"type"`
+	UUID      string      `json:"uuid"`
+	CommandID string      `json:"commandId"`
+	Parameter interface{} `json:"parameter"`
+}
+
+// Response 结构体定义
+type Response struct {
+	CommandID string            `json:"commandId"`
+	ErrorCode string            `json:"errorCode"`
+	Results   map[string]string `json:"results"`
+	Status    string            `json:"status"`
+	Time      int64             `json:"time"`
+	Type      string            `json:"type"`
+	UUID      string            `json:"uuid"`
+}
+
+var (
+	CloudConfig      cloudConfig
+	CloudConfigMutex sync.RWMutex
+)
+
+// InitCloudConfig 初始化业务配置
+func InitCloudConfig() {
+	// history20240401:朴津机器人额外加一个获取sn码
+	var snCode string
+
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		snCode, err := getSnCode()
+		if err != nil {
+			c_log.GlobalLogger.Error("获取sn码失败:", err.Error())
+			continue
+		}
+		LocalConfig.SecretKey = snCode
+		LocalConfig.EquipmentNo = "pjibot-" + snCode
+		break
+	}
+	c_log.GlobalLogger.Info("本地机器人sn码为:", snCode)
+
+	c_log.GlobalLogger.Info("初始化OSS配置文件 - 开始。")
+	// 获取文件的目录
+	_ = util.CreateParentDir(LocalConfig.CloudConfigLocalPath)
+	// 3 ------- 获取 yaml 字符串 -------
+	cloudConfigObjectKey := LocalConfig.OssBasePrefix + LocalConfig.EquipmentNo + "/" + LocalConfig.CloudConfigFilename
+
+	// 判断文件是否存在。如果不存在则使用默认的
+	isExist, err := OssBucket.IsObjectExist(cloudConfigObjectKey)
+	if err != nil {
+		c_log.GlobalLogger.Errorf("判断配置文件是否存在失败,错误信息为:%v", err)
+	}
+	if isExist {
+		c_log.GlobalLogger.Info("使用机器人自定义配置文件:", cloudConfigObjectKey)
+	} else {
+		cloudConfigObjectKey = LocalConfig.OssBasePrefix + LocalConfig.CloudConfigFilename // 默认配置文件路径
+		c_log.GlobalLogger.Info("使用机器人默认配置文件:", cloudConfigObjectKey)
+	}
+
+	for {
+		OssMutex.Lock()
+		err := OssBucket.GetObjectToFile(cloudConfigObjectKey, LocalConfig.CloudConfigLocalPath)
+		OssMutex.Unlock()
+		if err != nil {
+			c_log.GlobalLogger.Error("下载 OSS 上的配置文件 "+cloudConfigObjectKey+" 失败,请尽快在 OSS 上传配置文件。", err)
+			time.Sleep(time.Duration(2) * time.Second)
+			continue
+		}
+		break
+	}
+
+	content, err := os.ReadFile(LocalConfig.CloudConfigLocalPath)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序崩溃,配置文件 ", LocalConfig.CloudConfigLocalPath, " 读取失败:", err)
+		os.Exit(-1)
+	}
+
+	// 4 ------- 解析YAML内容 -------
+	var newCloudConfig cloudConfig
+	err = yaml.Unmarshal(content, &newCloudConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序崩溃,配置文件 ", LocalConfig.CloudConfigLocalPath, " 解析失败:", err)
+		os.Exit(-1)
+	}
+
+	// 5 ------- 校验 yaml -------
+	if checkCloudConfig(newCloudConfig) {
+		CloudConfigMutex.RLock()
+		CloudConfig = newCloudConfig
+		CloudConfigMutex.RUnlock()
+	} else {
+		c_log.GlobalLogger.Error("程序崩溃,配置文件格式错误:", newCloudConfig)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("初始化OSS配置文件 - 成功。")
+	util.CreateDir(CloudConfig.BagDataDir)
+	util.CreateDir(CloudConfig.BagCopyDir)
+}
+
+// refreshCloudConfig 更新业务配置
+func refreshCloudConfig() {
+	// 获取文件的目录
+	_ = util.CreateParentDir(LocalConfig.CloudConfigLocalPath)
+	// 3 ------- 获取 yaml 字符串 -------
+	var content []byte
+	cloudConfigObjectKey := LocalConfig.OssBasePrefix + LocalConfig.EquipmentNo + "/" + LocalConfig.CloudConfigFilename
+
+	isExist, err := OssBucket.IsObjectExist(cloudConfigObjectKey)
+	if err != nil {
+		c_log.GlobalLogger.Errorf("判断配置文件是否存在失败,错误信息为:%v", err)
+	}
+	if !isExist {
+		cloudConfigObjectKey = LocalConfig.OssBasePrefix + LocalConfig.CloudConfigFilename // 默认配置文件路径
+	}
+
+	OssMutex.Lock()
+	err = OssBucket.GetObjectToFile(cloudConfigObjectKey, LocalConfig.CloudConfigLocalPath)
+	OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("下载oss上的配置文件"+cloudConfigObjectKey+"失败。", err)
+		//os.Exit(-1)
+	}
+
+	content, err = os.ReadFile(LocalConfig.CloudConfigLocalPath)
+	if err != nil {
+		c_log.GlobalLogger.Error("配置文件 ", LocalConfig.CloudConfigLocalPath, " 读取失败:", err)
+		os.Exit(-1)
+	}
+
+	// 4 ------- 解析YAML内容 -------
+	var newCloudConfig cloudConfig
+	err = yaml.Unmarshal(content, &newCloudConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("配置文件 ", LocalConfig.CloudConfigLocalPath, " 解析失败:", err)
+		os.Exit(-1)
+	}
+
+	// 5 ------- 校验 yaml -------
+	if checkCloudConfig(newCloudConfig) {
+		CloudConfigMutex.RLock()
+		CloudConfig = newCloudConfig
+		CloudConfigMutex.RUnlock()
+	} else {
+		c_log.GlobalLogger.Error("配置文件格式错误:", newCloudConfig)
+		os.Exit(-1)
+	}
+	util.CreateDir(CloudConfig.BagDataDir)
+	util.CreateDir(CloudConfig.BagCopyDir)
+
+}
+
+// RefreshCloudConfig 轮询oss上的配置文件更新到本地
+func RefreshCloudConfig() {
+	for {
+		time.Sleep(time.Duration(CloudConfig.ConfigRefreshInterval) * time.Second)
+		refreshCloudConfig()
+	}
+}
+
+// CheckConfig 校验 cfg.yaml 文件
+func checkCloudConfig(check cloudConfig) bool {
+	if len(check.Hosts) != 1 {
+		c_log.GlobalLogger.Error("cloud-config.yaml中配置的hosts必须为1。")
+		os.Exit(-1)
+	}
+	return true
+}
+
+func getSnCode() (string, error) {
+	if LocalConfig.Type == "1" {
+		var command []string
+		command = append(command, "get")
+		command = append(command, "sn")
+		_, snOutput, err := util.ExecuteSync(LocalConfig.RosparamPath, command...)
+		if err != nil {
+			return "", errors.New("执行获取sn码命令" + util.ToString(command) + "出错:" + util.ToString(err))
+		}
+		c_log.GlobalLogger.Info("执行获取sn码命令", command, "成功,结果为:", snOutput)
+		snCode := strings.Replace(strings.Replace(snOutput, " ", "", -1), "\n", "", -1)
+		return snCode, nil
+	} else if LocalConfig.Type == "2" || LocalConfig.Type == "3" {
+		// 示例使用
+		serverURL := "192.168.1.104:9002"
+		path := "/"
+		request := Request{
+			Type:      "request",
+			UUID:      "",
+			CommandID: "getRobotBaseInfo",
+			Parameter: nil,
+		}
+
+		sn, err := SendWebsocketRequest(serverURL, path, request)
+		if err != nil {
+			return "", errors.New("通过api获取sn码失败:" + util.ToString(err))
+		}
+		return sn, nil
+	} else {
+		c_log.GlobalLogger.Error("程序崩溃,未知的机器人类型【" + LocalConfig.Type + "】,请修改local-config.yaml文件")
+		os.Exit(-1)
+		return "", nil
+	}
+}
+
+// SendWebsocketRequest 发送WebSocket请求并返回sn字段的值
+func SendWebsocketRequest(serverURL, path string, request Request) (string, error) {
+	// 构建WebSocket连接URL
+	u := url.URL{Scheme: "ws", Host: serverURL, Path: path}
+
+	// 创建一个Dialer实例,用于建立WebSocket连接
+	dialer := websocket.Dialer{
+		ReadBufferSize:  1024,
+		WriteBufferSize: 1024,
+		// 可选:设置超时等
+		HandshakeTimeout: 5 * time.Second,
+	}
+
+	// 建立WebSocket连接
+	conn, _, err := dialer.Dial(u.String(), nil)
+	if err != nil {
+		return "", fmt.Errorf("dial: %w", err)
+	}
+	defer conn.Close()
+
+	// 将请求JSON编码为字节
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return "", fmt.Errorf("marshal request: %w", err)
+	}
+
+	// 发送WebSocket消息
+	err = conn.WriteMessage(websocket.TextMessage, requestJSON)
+	if err != nil {
+		return "", fmt.Errorf("write: %w", err)
+	}
+
+	// 读取WebSocket响应
+	_, responseBytes, err := conn.ReadMessage()
+	if err != nil {
+		return "", fmt.Errorf("read: %w", err)
+	}
+
+	// 将响应字节解码为JSON
+	var response Response
+	err = json.Unmarshal(responseBytes, &response)
+	if err != nil {
+		return "", fmt.Errorf("unmarshal response: %w", err)
+	}
+
+	// 返回sn字段的值
+	return response.Results["sn"], nil
+}

+ 21 - 0
aarch64/pjibot_patrol/common/config/c_killrpcserver.go

@@ -0,0 +1,21 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"net"
+	"os"
+)
+
+var KillSignalListener net.Listener
+
+func InitKillSignalListener(serverIp string) {
+	var err error
+	c_log.GlobalLogger.Info("初始化RPC端口监听Kill信号 - 开始。")
+	socket := serverIp + ":" + CloudConfig.RpcPort
+	KillSignalListener, err = net.Listen("tcp", socket)
+	if err != nil {
+		c_log.GlobalLogger.Error("监听rpc端口失败:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("初始化RPC端口监听Kill信号 - 成功:", socket)
+}

+ 62 - 0
aarch64/pjibot_patrol/common/config/c_local.go

@@ -0,0 +1,62 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"gopkg.in/yaml.v2"
+	"os"
+)
+
+type node struct {
+	Name string `yaml:"name"`
+	Ip   string `yaml:"ip"`
+}
+
+type restartCmd struct {
+	Dir  string   `yaml:"dir"`
+	Name string   `yaml:"name"`
+	Args []string `yaml:"args"`
+}
+
+type localConfig struct {
+	Type                 string     `yaml:"type"`                    // 机器人类型(不同机器人获取sn码方式不同)
+	Node                 node       `yaml:"node"`                    // 节点信息
+	RosparamPath         string     `yaml:"rosparam-path"`           // 获取oss配置的url
+	UrlGetOssConfig      string     `yaml:"url-get-oss-config"`      // 获取oss配置的url
+	OssBasePrefix        string     `yaml:"oss-base-prefix"`         // 云端配置文件的位置
+	CloudConfigFilename  string     `yaml:"cloud-config-filename"`   // 云端配置文件名称
+	CloudConfigLocalPath string     `yaml:"cloud-config-local-path"` // 将 oss 的配置文件下载到本地的位置
+	RestartCmd           restartCmd `yaml:"restart-cmd"`             // 重启命令
+	EquipmentNo          string     // 当前设备的编号
+	SecretKey            string     // 当前设备的密钥
+}
+
+var (
+	LocalConfig localConfig
+)
+
+func InitLocalConfig(localConfigPath string) {
+	c_log.GlobalLogger.Info("初始化本地配置文件 - 开始:", localConfigPath)
+	// 读取YAML文件内容
+	content, err := os.ReadFile(localConfigPath)
+	if err != nil {
+		c_log.GlobalLogger.Error("读取本地配置文件失败。", err)
+		os.Exit(-1)
+	}
+
+	// 解析YAML内容
+	err = yaml.Unmarshal(content, &LocalConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析本地配置文件失败。", err)
+		os.Exit(-1)
+	}
+
+	// history20240401:设备密钥需要获取sn码,设备编号同样。######由于执行命令需要环境变量,所以放到 c_cloud.go 中####
+	/*
+		# 例如,数据闭环平台参数
+		equipment-no: pjibot-P1YNYD1M228000127
+		secret-key: P1YNYD1M228000127
+	*/
+
+	c_log.GlobalLogger.Info("初始化本地配置文件 - 成功:", LocalConfig)
+
+}

+ 52 - 0
aarch64/pjibot_patrol/common/config/c_oss.go

@@ -0,0 +1,52 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+	"os"
+	"sync"
+)
+
+type OssConnectInfoStruct struct {
+	Endpoint        string `json:"endpoint"`
+	AccessKeyId     string `json:"accessKeyId"`
+	AccessKeySecret string `json:"accessKeySecret"`
+	BucketName      string `json:"bucketName"`
+}
+
+var (
+	OssClient *oss.Client
+	OssBucket *oss.Bucket
+	OssMutex  sync.Mutex
+)
+
+func InitOssConfig() {
+	c_log.GlobalLogger.Info("初始化OSS客户端对象 - 开始。")
+	// 1 访问 HTTP 服务获取 OSS 配置
+	get, err := util.HttpGet(LocalConfig.UrlGetOssConfig)
+	if err != nil {
+		c_log.GlobalLogger.Error("http获取oss配置时出错:", err)
+		os.Exit(-1)
+	}
+	var ossConnectInfo OssConnectInfoStruct
+	err = json.Unmarshal([]byte(get), &ossConnectInfo)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析json时出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Infof("oss 配置信息为:%v", ossConnectInfo)
+
+	OssClient, err = oss.New(ossConnectInfo.Endpoint, ossConnectInfo.AccessKeyId, ossConnectInfo.AccessKeySecret, oss.UseCname(true))
+	if err != nil {
+		c_log.GlobalLogger.Error("无法创建阿里云client:", err)
+		os.Exit(-1)
+	}
+	OssBucket, err = OssClient.Bucket(ossConnectInfo.BucketName)
+	if err != nil {
+		c_log.GlobalLogger.Error("无法创建阿里云bucket:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("初始化OSS客户端对象 - 成功。")
+}

+ 193 - 0
aarch64/pjibot_patrol/common/config/c_platform.go

@@ -0,0 +1,193 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"strings"
+	"time"
+)
+
+type taskTrigger struct {
+	TriggerId         int    `json:"triggerId"`
+	TriggerName       string `json:"triggerName"`
+	TriggerScriptPath string `json:"triggerScriptPath"`
+	TriggerType       string `json:"triggerType"`
+}
+
+type PlatformConfigStruct struct {
+	TaskConfigId    string        `json:"taskConfigId"`   // 配置ID
+	TaskConfigName  string        `json:"taskConfigName"` // 配置名称
+	DropUploadData  bool          `json:"dropUploadData"` // 更新任务时 true 先上传旧任务 false 删除旧任务
+	TaskMaxTime     int           `json:"taskMaxTime"`
+	TaskBeforeTime  int           `json:"taskBeforeTime"`
+	TaskAfterTime   int           `json:"taskAfterTime"`
+	TaskCachePolicy string        `json:"taskCachePolicy"`
+	EquipmentTopic  string        `json:"equipmentTopic"` // topic序列
+	Lru             []string      `json:"LRU"`
+	TaskTriggers    []taskTrigger `json:"taskTriggers"`
+}
+
+type response struct {
+	Data    PlatformConfigStruct `json:"data"`
+	Success bool                 `json:"success"`
+	Message string               `json:"message"`
+	Code    int                  `json:"code"`
+	NowTime string               `json:"nowTime"`
+}
+
+var (
+	PlatformConfig  PlatformConfigStruct
+	SubscribeTopics []string
+)
+
+// InitPlatformConfig 初始化数据闭环平台的配置
+func InitPlatformConfig() {
+	var err error
+	c_log.GlobalLogger.Info("获取数据闭环平台配置 - 开始")
+	// 1 如果车辆没有配置任务,则阻塞在这里,不启动任务
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		// 判断是否有配置,第一次访问状态应该为:CHANGE(一共三种状态 CHANGE|UNCHANGE|NONE)
+		PlatformConfig, err = getConfig()
+		if err != nil {
+			c_log.GlobalLogger.Error("获取配置status失败:", err)
+			continue
+		}
+		if checkPlatformConfig() {
+			SubscribeTopics = strings.Split(PlatformConfig.EquipmentTopic, ",")
+			// 去掉首尾空格
+			for i, topic := range SubscribeTopics {
+				SubscribeTopics[i] = strings.TrimSpace(topic)
+			}
+			break
+		}
+	}
+	c_log.GlobalLogger.Info("获取数据闭环平台配置 - 成功。")
+}
+
+/*
+	{
+	  "data": {
+	    "accessToken": "YWRmYWRzZmFzZGZhZHNmYWRmYWRm=",
+	    "expireTime": "28800",
+	    "equipmentNo": "robot-001"
+	  },
+	  "success": true,
+	  "message": "ok",
+	  "code": 1,
+	  "nowTime": "2023-12-09 22:41:00"
+	}
+*/
+// GetAccessToken 认证接口,获取access_token
+func GetAccessToken() (string, error) {
+	respJson, err := util.HttpPostJsonResponseString(
+		CloudConfig.Platform.UrlDeviceAuth,
+		map[string]string{
+			"equipmentNo": LocalConfig.EquipmentNo,
+			"secretKey":   LocalConfig.SecretKey,
+		},
+	)
+	if err != nil {
+		return "", nil
+	}
+	respMap, err := util.JsonStringToMap(respJson)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析返回结果", respJson, "失败:", err)
+		return "", nil
+	}
+
+	dataMap, ok := respMap["data"].(map[string]interface{})
+	if !ok {
+		c_log.GlobalLogger.Error("解析返回结果.data", dataMap, "失败:", err)
+		return "", nil
+	}
+	return dataMap["accessToken"].(string), nil
+}
+
+/*
+	{
+	  "data": {
+	    "status": "UNCHANGE"
+	    "taskConfigld": "xxx"
+	  },
+	  "success": true,
+	  "message": "ok",
+	  "code": 1,
+	  "nowTime": "2023-12-09 21:08:49"
+	}
+*/
+//GetStatus 根据taskConfigId获取任务status,如果传入空代表车端没有配置,直接获取新的配置
+func GetStatus(taskConfigId string) (string, error) {
+	token, err := GetAccessToken()
+	if err != nil {
+		return "", err
+	}
+	resp, err := util.HttpGetStringAddHeadersResponseString(
+		CloudConfig.Platform.UrlTaskPoll,
+		map[string]string{
+			"authorization": token,
+		},
+		map[string]string{
+			"equipmentNo":  LocalConfig.EquipmentNo,
+			"taskConfigId": taskConfigId,
+		},
+	)
+
+	if err != nil {
+		c_log.GlobalLogger.Error("访问接口", CloudConfig.Platform.UrlTask, "失败:", err)
+		return "", err
+	}
+	respMap, err := util.JsonStringToMap(resp)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析【返回结果1】", resp, "失败:", err)
+		return "", err
+	}
+	dataMap, ok := respMap["data"].(map[string]interface{})
+	if !ok {
+		c_log.GlobalLogger.Errorf("解析【返回结果.data】的类型不是(map[string]interface{}),【dataMap】=%v", dataMap)
+		return "", err
+	}
+	return dataMap["status"].(string), nil
+}
+
+func getConfig() (PlatformConfigStruct, error) {
+	token, err := GetAccessToken()
+	if err != nil {
+		return PlatformConfigStruct{}, err
+	}
+	// 下载插件和获取配置
+	// 2 访问配置获取接口
+	resp, err := util.HttpGetStringAddHeadersResponseString(
+		CloudConfig.Platform.UrlTask,
+		map[string]string{
+			"authorization": token,
+		},
+		map[string]string{
+			"equipmentNo": LocalConfig.EquipmentNo,
+		},
+	)
+	if err != nil {
+		c_log.GlobalLogger.Error("访问接口", CloudConfig.Platform.UrlTask, "失败:", err)
+		return PlatformConfigStruct{}, err
+	}
+	var result response
+	err = json.Unmarshal([]byte(resp), &result)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析【返回结果】", resp, "失败:", err)
+		return PlatformConfigStruct{}, err
+	}
+	return result.Data, nil
+}
+
+func checkPlatformConfig() bool {
+	if PlatformConfig.TaskConfigId == "" {
+		c_log.GlobalLogger.Error("数据闭环平台没有配置任务。")
+		return false
+	}
+	if PlatformConfig.EquipmentTopic == "" {
+		c_log.GlobalLogger.Error("数据闭环平台没有配置topic序列。")
+		return false
+	}
+	return true
+}

+ 35 - 0
aarch64/pjibot_patrol/common/config/c_resource.go

@@ -0,0 +1,35 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"encoding/json"
+	"time"
+)
+
+// SendResourceUsage 保存资源占用情况
+func SendResourceUsage() {
+	for {
+		time.Sleep(time.Duration(1) * time.Second)
+		top10Cpu, top10Mem := util.GetTop10CpuAndMem()
+		top10CpuJson, _ := json.MarshalIndent(top10Cpu, "", "    ")
+		top10MemJson, _ := json.MarshalIndent(top10Mem, "", "    ")
+		responseString, err := util.HttpPostJsonWithHeaders(
+			CloudConfig.Monitor.Url,
+			map[string]string{"Authorization": "U9yKpD6kZZDDe4LFKK6myAxBUT1XRrDM"},
+			map[string]string{
+				"totalCpuUsage":    util.ToString(util.GetCpuPercent()),
+				"totalMemoryUsage": util.ToString(util.GetMemoryPercent()),
+				"top10Process":     string(top10CpuJson),
+				"top10Cpu":         string(top10CpuJson),
+				"top10Mem":         string(top10MemJson),
+				"deviceNumber":     LocalConfig.EquipmentNo,
+				"socIp":            LocalConfig.Node.Ip,
+			},
+		)
+		if err != nil {
+			c_log.GlobalLogger.Errorf("发送数据监控信息报错%v,响应信息为:%v", err, responseString)
+		}
+		//c_log.GlobalLogger.Infof("发送数据监控信息成功,响应信息为:%v", responseString)
+	}
+}

+ 39 - 0
aarch64/pjibot_patrol/common/config/c_ros.go

@@ -0,0 +1,39 @@
+package config
+
+import (
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"github.com/bluenviron/goroslib/v2"
+	"time"
+)
+
+var (
+	RosNode    *goroslib.Node
+	RosbagPath string
+	RosbagEnvs []string
+)
+
+func InitRosConfig() {
+	var err error
+	// 1
+	c_log.GlobalLogger.Info("初始化RosNode - 开始")
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		if RosNode, err = goroslib.NewNode(goroslib.NodeConf{Name: "node" + util.GetNowTimeCustom(), MasterAddress: CloudConfig.Ros.MasterAddress}); err != nil {
+			c_log.GlobalLogger.Info("初始化RosNode - 进行中:", err)
+			continue
+		}
+		break
+	}
+	c_log.GlobalLogger.Info("初始化RosNode - 成功:", CloudConfig.Ros.MasterAddress)
+	// 2 获取 rosbag 命令路径和环境变量
+	for _, host := range CloudConfig.Hosts {
+		if host.Name == LocalConfig.Node.Name {
+			RosbagPath = host.Rosbag.Path
+			RosbagEnvs = host.Rosbag.Envs
+			break
+		}
+	}
+	c_log.GlobalLogger.Infof("rosbag 命令路径为:%v,环境变量为:%v", RosbagPath, RosbagEnvs)
+
+}

+ 3 - 0
aarch64/pjibot_patrol/common/config/sh/start-control.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+chmod 777 /root/cicv-data-closedloop/pji-control.exe
+nohup /root/cicv-data-closedloop/pji-control.exe > /root/cicv-data-closedloop/log/pji-control.out 2>&1 &

+ 3 - 0
aarch64/pjibot_patrol/common/config/sh/start-master.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+chmod 777 /root/cicv-data-closedloop/pji-master.exe
+nohup /root/cicv-data-closedloop/pji-master.exe > /root/cicv-data-closedloop/log/pji-master.out 2>&1 &

+ 159 - 0
aarch64/pjibot_patrol/common/config/yaml/巡检机器人默认配置文件-cloud-config.yaml

@@ -0,0 +1,159 @@
+---
+monitor:
+  url: http://36.110.106.142:12341/web_server/monitor/insert
+platform:
+  url-device-auth: http://1.202.169.139:8081/device/auth
+  url-task-poll: http://1.202.169.139:8081/device/task/poll
+  url-task: http://1.202.169.139:8081/device/task
+
+bag-number: 60
+config-refresh-interval: 60
+disk:
+  name: /dev/nvme0n1p1 # 磁盘名称
+  used: 50000000000 # 磁盘占用阈值,单位bytes
+map-bag-path: /root/cicv-data-closedloop/map.bag
+tfstatic-bag-path: /root/cicv-data-closedloop/tf_static.bag
+costmap-bag-path: /root/cicv-data-closedloop/costmap.bag
+bag-data-dir: /root/cicv-data-closedloop/data/
+bag-copy-dir: /root/cicv-data-closedloop/copy/
+triggers-dir: /root/cicv-data-closedloop/triggers/
+time-window-send-gap: 6
+rpc-port: 12341
+ros:
+  master-address: 192.168.1.104:11311
+  nodes:
+    - /amcl
+    - /ob_camera_01/camera
+    - /ob_camera_02/camera
+    - /node_diagnostics
+    - /localization_monitor_node
+    - /move_base
+    - /sensor_fusion_node
+    - /ltme_node
+    - /scan_map_icp_amcl_node
+    - /monitor
+
+hosts:
+  - name: node1
+    ip: 192.168.1.104
+    rosbag:
+      path: "/opt/ros/melodic/bin/rosbag"
+      envs:
+        - "C_INCLUDE_PATH=/usr/include/drm:"
+        - "USER=root"
+        - "ROS_PACKAGE_PATH=/opt/ros/melodic/share"
+        - "LD_LIBRARY_PATH=/opt/ros/melodic/lib:/opt/ros/melodic/lib/aarch64-linux-gnu"
+        - "ROS_ETC_DIR=/opt/ros/melodic/etc/ros"
+        - "SHLVL=1"
+        - "HOME=/root"
+        - "ROS_PYTHON_VERSION=2"
+        - "PCMANFM_OUTLINE_MODE=on"
+        - "CPLUS_INCLUDE_PATH=/usr/include/drm:"
+        - "ROS_DISTRO=melodic"
+        - "ROS_VERSION=1"
+        - "PKG_CONFIG_PATH=/opt/ros/melodic/lib/pkgconfig"
+        - "PATH=/opt/ros/melodic/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/root/go/bin"
+        - "ROS_ROOT=/opt/ros/melodic/share/ros"
+        - "ROSLISP_PACKAGE_DIRECTORIES="
+        - "ROS_MASTER_URI=http://192.168.1.104:11311"
+        - "PYTHONPATH=/opt/ros/melodic/lib/python2.7/dist-packages"
+        - "ROS_HOSTNAME=192.168.1.104"
+        - "CMAKE_PREFIX_PATH=/opt/ros/melodic"
+    topics:
+      - /robot_pose
+      - /robot/realtime_cost_map_
+      - /tracking/objects
+      - /robot/TaskInfo
+      - /robot/targetposition
+      - /wheel
+      - /wheel_odom
+      - /robot/global_trajectory_
+      - /robot/target_trajectories
+      - /robot/evaluator_trajectories
+      - /robot/final_trajectory
+      - /nav/task_feedback_info
+      - /cmd_vel
+      - /imu
+      - /points_cluster
+      - /nav/task_feedback_info
+
+
+full-collect: true # 控制是否根据不同的触发器采集不通的topic,一般设置为true,即忽略下面的配置
+triggers:
+  - label: detectfault
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: unstabledriving
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: locationfailed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: obstacledetection
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: overspeed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: cpuoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: memoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed

+ 19 - 0
aarch64/pjibot_patrol/common/config/yaml/巡检机器人默认配置文件-local-config.yaml

@@ -0,0 +1,19 @@
+type: 3 # 机器人类型 1 引导机器人 2 配送机器人 3 巡检机器人
+node:
+  name: node1
+  ip: 192.168.1.104
+rosparam-path: /opt/ros/melodic/bin/rosparam
+# 获取oss连接信息的接口url
+#url-get-oss-config: http://36.110.106.156:18379/oss/config?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD # 国汽数据闭环
+url-get-oss-config: http://36.110.106.156:18379/oss/pji?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD # 朴津数据闭环
+# 朴津机器人数据前缀
+oss-base-prefix: pjibot-patrol/
+# oss上的配置文件的名称
+cloud-config-filename: cloud-config.yaml
+# 将oss上的配置文件下载到本地的路径
+cloud-config-local-path: /root/cicv-data-closedloop/config/cloud-config.yaml
+restart-cmd:
+  dir: "/root/cicv-data-closedloop/"
+  name: "sh"
+  args:
+    - "start-master.sh"

+ 167 - 0
aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件单摄像头-cloud-config.yaml

@@ -0,0 +1,167 @@
+---
+monitor:
+  url: http://36.110.106.142:12341/web_server/monitor/insert
+platform:
+  url-device-auth: http://1.202.169.139:8081/device/auth
+  url-task-poll: http://1.202.169.139:8081/device/task/poll
+  url-task: http://1.202.169.139:8081/device/task
+full-collect: false
+bag-number: 10
+config-refresh-interval: 60
+disk:
+  name: /dev/mmcblk0p8 # 磁盘名称
+  used: 20000000000 # 磁盘占用阈值,单位bytes
+map-bag-path: /root/cicv-data-closedloop/map.bag
+bag-data-dir: /root/cicv-data-closedloop/data/
+bag-copy-dir: /root/cicv-data-closedloop/copy/
+triggers-dir: /root/cicv-data-closedloop/triggers/
+time-window-send-gap: 6
+rpc-port: 12341
+ros:
+  master-address: 192.168.1.104:11311
+  nodes:
+    - /adapters_nav
+    - /amcl
+    - /auto_dock
+    - /camera/camera
+    - /engine
+    - /lidar_filter_bz
+    - /localization_monitor_node
+    - /ltme_node
+    - /map_merge_node
+    - /mode_manage
+    - /monitor
+    - /move_base
+    - /node_diagnostics
+    - /node_map_convert
+    - /node_ota
+    - /pointcloud_to_laserscan
+    - /robot_pose_publisher_node
+    - /robot_state_publisher
+    - /robot_static_node
+    - /rosout
+    - /scan_map_icp_amcl_node
+    - /sensor_fusion_node
+    - /slip_check_node
+    - /srf_laser_odometry_node
+    - /trii_receive
+
+hosts:
+  - name: node1
+    ip: 192.168.1.104
+    rosbag:
+      path: "/opt/ros/melodic/bin/rosbag"
+      envs:
+        - "C_INCLUDE_PATH=/usr/include/drm:"
+        - "USER=root"
+        - "ROS_PACKAGE_PATH=/opt/ros/melodic/share"
+        - "LD_LIBRARY_PATH=/opt/ros/melodic/lib:/opt/ros/melodic/lib/aarch64-linux-gnu"
+        - "ROS_ETC_DIR=/opt/ros/melodic/etc/ros"
+        - "SHLVL=1"
+        - "HOME=/root"
+        - "ROS_PYTHON_VERSION=2"
+        - "PCMANFM_OUTLINE_MODE=on"
+        - "CPLUS_INCLUDE_PATH=/usr/include/drm:"
+        - "ROS_DISTRO=melodic"
+        - "ROS_VERSION=1"
+        - "PKG_CONFIG_PATH=/opt/ros/melodic/lib/pkgconfig"
+        - "PATH=/opt/ros/melodic/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/root/go/bin"
+        - "ROS_ROOT=/opt/ros/melodic/share/ros"
+        - "ROSLISP_PACKAGE_DIRECTORIES="
+        - "ROS_MASTER_URI=http://192.168.1.104:11311"
+        - "PYTHONPATH=/opt/ros/melodic/lib/python2.7/dist-packages"
+        - "ROS_HOSTNAME=192.168.1.104"
+        - "CMAKE_PREFIX_PATH=/opt/ros/melodic"
+    topics:
+      - /amcl_pose
+      - /camera/color/image_raw
+#      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /move_base/local_costmap/costmap
+      - /move_base/local_costmap/costmap_updates
+      - /scan
+      - /scan_map_icp_amcl_node/scan_point_transformed
+      - /sys_info
+triggers:
+  - label: detectfault
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: unstabledriving
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: locationfailed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: obstacledetection
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: overspeed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: cpuoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: memoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed

+ 18 - 0
aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件单摄像头-local-config.yaml

@@ -0,0 +1,18 @@
+type: 1 # 机器人类型 1 引导机器人 2 配送机器人 3 巡检机器人
+node:
+  name: node1
+  ip: 192.168.1.104
+rosparam-path: /opt/ros/melodic/bin/rosparam
+# 获取oss连接信息的接口url
+url-get-oss-config: http://36.110.106.156:18379/oss/config?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD
+# 朴津机器人数据前缀
+oss-base-prefix: pji/
+# oss上的配置文件的名称
+cloud-config-filename: cloud-config.yaml
+# 将oss上的配置文件下载到本地的路径
+cloud-config-local-path: /root/cicv-data-closedloop/config/cloud-config.yaml
+restart-cmd:
+  dir: "/root/cicv-data-closedloop/"
+  name: "sh"
+  args:
+    - "start-master.sh"

+ 203 - 0
aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件双摄像头-cloud-config.yaml

@@ -0,0 +1,203 @@
+---
+collect-limit:
+  url: http://36.110.106.142:12341/web_server/collect_limit/can_collect
+  day: 1
+  week: 7
+  month: 31
+  year: 366
+monitor:
+  url: http://36.110.106.142:12341/web_server/monitor/insert
+platform:
+  #  url-device-auth: http://1.202.169.139:8081/device/auth
+  #  url-task-poll: http://1.202.169.139:8081/device/task/poll
+  #  url-task: http://1.202.169.139:8081/device/task
+  url-device-auth: http://36.110.106.156:11121/device/auth
+  url-task-poll: http://36.110.106.156:11121/device/task/poll
+  url-task: http://36.110.106.156:11121/device/task
+bag-number: 60
+config-refresh-interval: 60
+disk:
+  name: /dev/mmcblk0p8 # 磁盘名称
+  used: 20000000000 # 磁盘占用阈值,单位bytes
+map-buf-files:
+  - /root/pjirobot/data/mapBuf/forbid_area.json
+  - /root/pjirobot/data/mapBuf/forbid_area.yaml
+  - /root/pjirobot/data/mapBuf/forbid_area_init.json
+  - /root/pjirobot/data/mapBuf/forbid_area_init.yaml
+  - /root/pjirobot/data/mapBuf/function_area.json
+  - /root/pjirobot/data/mapBuf/function_area_init.json
+  - /root/pjirobot/data/mapBuf/function_links.json
+  - /root/pjirobot/data/mapBuf/map.json
+  - /root/pjirobot/data/mapBuf/map.pbstream
+  - /root/pjirobot/data/mapBuf/map.pgm
+  - /root/pjirobot/data/mapBuf/map.yaml
+  - /root/pjirobot/data/mapBuf/map_type.json
+  - /root/pjirobot/data/mapBuf/param.yaml
+  - /root/pjirobot/data/mapBuf/stations.json
+  - /root/pjirobot/data/mapBuf/stations_init.json
+map-bag-path: /root/cicv-data-closedloop/map.bag
+#bag-data-dir: /root/cicv-data-closedloop/data/
+#bag-copy-dir: /root/cicv-data-closedloop/copy/
+#triggers-dir: /root/cicv-data-closedloop/triggers/
+bag-data-dir: /root/pjirobot/data/cicv-data-closedloop/data/
+bag-copy-dir: /root/pjirobot/data/cicv-data-closedloop/copy/
+triggers-dir: /root/pjirobot/data/cicv-data-closedloop/triggers/
+time-window-send-gap: 6
+rpc-port: 12341
+ros:
+  master-address: 192.168.1.104:11311
+  nodes:
+    - /amcl
+    - /ob_camera_01/camera
+    - /ob_camera_02/camera
+    - /node_diagnostics
+    - /localization_monitor_node
+    - /move_base
+    - /sensor_fusion_node
+    - /ltme_node
+    - /scan_map_icp_amcl_node
+    - /monitor
+
+hosts:
+  - name: node1
+    ip: 192.168.1.104
+    rosbag:
+      path: "/opt/ros/melodic/bin/rosbag"
+      envs:
+        - "C_INCLUDE_PATH=/usr/include/drm:"
+        - "USER=root"
+        - "ROS_PACKAGE_PATH=/opt/ros/melodic/share"
+        - "LD_LIBRARY_PATH=/opt/ros/melodic/lib:/opt/ros/melodic/lib/aarch64-linux-gnu"
+        - "ROS_ETC_DIR=/opt/ros/melodic/etc/ros"
+        - "SHLVL=1"
+        - "HOME=/root"
+        - "ROS_PYTHON_VERSION=2"
+        - "PCMANFM_OUTLINE_MODE=on"
+        - "CPLUS_INCLUDE_PATH=/usr/include/drm:"
+        - "ROS_DISTRO=melodic"
+        - "ROS_VERSION=1"
+        - "PKG_CONFIG_PATH=/opt/ros/melodic/lib/pkgconfig"
+        - "PATH=/opt/ros/melodic/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/root/go/bin"
+        - "ROS_ROOT=/opt/ros/melodic/share/ros"
+        - "ROSLISP_PACKAGE_DIRECTORIES="
+        - "ROS_MASTER_URI=http://192.168.1.104:11311"
+        - "PYTHONPATH=/opt/ros/melodic/lib/python2.7/dist-packages"
+        - "ROS_HOSTNAME=192.168.1.104"
+        - "CMAKE_PREFIX_PATH=/opt/ros/melodic"
+    topics:
+      - /amcl_pose # /amcl
+      - /ob_camera_01/color/image_raw # /ob_camera_01/camera
+      #      - /ob_camera_01/depth/points # /ob_camera_01/camera
+      - /ob_camera_02/color/image_raw # /ob_camera_02/camera
+      #      - /ob_camera_02/depth/points # /ob_camera_02/camera
+      - /diagnostics # /amcl /node_diagnostics
+      - /locate_info # /localization_monitor_node
+      - /obstacle_detection # /move_base
+      - /odom # /sensor_fusion_node
+      - /move_base/global_costmap/costmap # /move_base
+      - /move_base/global_costmap/costmap_updates # /move_base
+      - /move_base/local_costmap/costmap # /move_base
+      - /move_base/local_costmap/costmap_updates # /move_base
+      - /scan # /ltme_node
+      - /scan_map_icp_amcl_node/scan_point_transformed # /scan_map_icp_amcl_node
+      - /sys_info
+      #      - /cmd_vel
+      - /imu
+      # 算法评价新增
+      - /depth_scan_02
+      - /map
+      - /scan_filtered
+      - /sonar_left
+      - /sonar_right
+      - /sonar_mid
+      - /sonar_rmid
+      - /tf
+      - /tf_static
+      - /cmd_vel
+      - /move_base/DWAPlannerROS/global_plan
+      - /move_base/DWAPlannerROS/local_plan
+      - /move_base/GlobalPlanner/plan
+      - /move_base/global_costmap/footprint
+      - /move_base/local_costmap/footprint
+      - /robot_pose_tf
+
+
+full-collect: true # 控制是否根据不同的触发器采集不通的topic,一般设置为true,即忽略下面的配置
+triggers:
+  - label: detectfault
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: unstabledriving
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: locationfailed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: obstacledetection
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: overspeed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: cpuoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: memoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed

+ 18 - 0
aarch64/pjibot_patrol/common/config/yaml/引导机器人默认配置文件双摄像头-local-config.yaml

@@ -0,0 +1,18 @@
+type: 1 # 机器人类型 1 引导机器人 2 配送机器人 3 巡检机器人
+node:
+  name: node1
+  ip: 192.168.1.104
+rosparam-path: /opt/ros/melodic/bin/rosparam
+# 获取oss连接信息的接口url
+url-get-oss-config: http://36.110.106.156:18379/oss/pji?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD
+# 朴津机器人数据前缀
+oss-base-prefix: pji-double-camera/
+# oss上的配置文件的名称
+cloud-config-filename: cloud-config.yaml
+# 将oss上的配置文件下载到本地的路径
+cloud-config-local-path: /root/cicv-data-closedloop/config/cloud-config.yaml
+restart-cmd:
+  dir: "/root/cicv-data-closedloop/"
+  name: "sh"
+  args:
+    - "start-master.sh"

+ 159 - 0
aarch64/pjibot_patrol/common/config/yaml/配送机器人默认配置文件-cloud-config.yaml

@@ -0,0 +1,159 @@
+---
+monitor:
+  url: http://36.110.106.142:12341/web_server/monitor/insert
+platform:
+  url-device-auth: http://1.202.169.139:8081/device/auth
+  url-task-poll: http://1.202.169.139:8081/device/task/poll
+  url-task: http://1.202.169.139:8081/device/task
+
+bag-number: 60
+config-refresh-interval: 60
+disk:
+  name: /dev/sda1 # 磁盘名称
+  used: 60000000000 # 磁盘占用阈值,单位bytes
+map-bag-path: /root/cicv-data-closedloop/map.bag
+tfstatic-bag-path: /root/cicv-data-closedloop/tf_static.bag
+costmap-bag-path: /root/cicv-data-closedloop/costmap.bag
+bag-data-dir: /root/cicv-data-closedloop/data/
+bag-copy-dir: /root/cicv-data-closedloop/copy/
+triggers-dir: /root/cicv-data-closedloop/triggers/
+time-window-send-gap: 6
+rpc-port: 12341
+ros:
+  master-address: 192.168.1.104:11311
+  nodes:
+    - /amcl
+    - /ob_camera_01/camera
+    - /ob_camera_02/camera
+    - /node_diagnostics
+    - /localization_monitor_node
+    - /move_base
+    - /sensor_fusion_node
+    - /ltme_node
+    - /scan_map_icp_amcl_node
+    - /monitor
+
+hosts:
+  - name: node1
+    ip: 192.168.1.104
+    rosbag:
+      path: "/opt/ros/melodic/bin/rosbag"
+      envs:
+        - "C_INCLUDE_PATH=/usr/include/drm:"
+        - "USER=root"
+        - "ROS_PACKAGE_PATH=/opt/ros/melodic/share"
+        - "LD_LIBRARY_PATH=/opt/ros/melodic/lib:/opt/ros/melodic/lib/aarch64-linux-gnu"
+        - "ROS_ETC_DIR=/opt/ros/melodic/etc/ros"
+        - "SHLVL=1"
+        - "HOME=/root"
+        - "ROS_PYTHON_VERSION=2"
+        - "PCMANFM_OUTLINE_MODE=on"
+        - "CPLUS_INCLUDE_PATH=/usr/include/drm:"
+        - "ROS_DISTRO=melodic"
+        - "ROS_VERSION=1"
+        - "PKG_CONFIG_PATH=/opt/ros/melodic/lib/pkgconfig"
+        - "PATH=/opt/ros/melodic/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/root/go/bin"
+        - "ROS_ROOT=/opt/ros/melodic/share/ros"
+        - "ROSLISP_PACKAGE_DIRECTORIES="
+        - "ROS_MASTER_URI=http://192.168.1.104:11311"
+        - "PYTHONPATH=/opt/ros/melodic/lib/python2.7/dist-packages"
+        - "ROS_HOSTNAME=192.168.1.104"
+        - "CMAKE_PREFIX_PATH=/opt/ros/melodic"
+    topics:
+      - /robot_pose
+      - /robot/realtime_cost_map_
+      - /tracking/objects
+      - /robot/TaskInfo
+      - /robot/targetposition
+      - /wheel
+      - /wheel_odom
+      - /robot/global_trajectory_
+      - /robot/target_trajectories
+      - /robot/evaluator_trajectories
+      - /robot/final_trajectory
+      - /nav/task_feedback_info
+      - /cmd_vel
+      - /imu
+      - /points_cluster
+      - /nav/task_feedback_info
+
+
+full-collect: true # 控制是否根据不同的触发器采集不通的topic,一般设置为true,即忽略下面的配置
+triggers:
+  - label: detectfault
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: unstabledriving
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: locationfailed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: obstacledetection
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: overspeed
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: cpuoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed
+  - label: memoveroccupied
+    topics:
+      - /camera/color/image_raw
+      - /camera/depth/points
+      - /diagnostics
+      - /locate_info
+      - /obstacle_detection
+      - /odom
+      - /move_base/global_costmap/costmap
+      - /move_base/global_costmap/costmap_updates
+      - /scan_map_icp_amcl_node/scan_point_transformed

+ 19 - 0
aarch64/pjibot_patrol/common/config/yaml/配送机器人默认配置文件-local-config.yaml

@@ -0,0 +1,19 @@
+type: 2 # 机器人类型 1 引导机器人 2 配送机器人 3 巡检机器人
+node:
+  name: node1
+  ip: 192.168.1.104
+rosparam-path: /opt/ros/melodic/bin/rosparam
+# 获取oss连接信息的接口url
+#url-get-oss-config: http://36.110.106.156:18379/oss/config?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD # 国汽数据闭环
+url-get-oss-config: http://36.110.106.156:18379/oss/pji?token=nXonLUcMtGcrQqqKiyygIwyVbvizE0wD # 朴津数据闭环
+# 朴津机器人数据前缀
+oss-base-prefix: pjibot-delivery/
+# oss上的配置文件的名称
+cloud-config-filename: cloud-config.yaml
+# 将oss上的配置文件下载到本地的路径
+cloud-config-local-path: /root/cicv-data-closedloop/config/cloud-config.yaml
+restart-cmd:
+  dir: "/root/cicv-data-closedloop/"
+  name: "sh"
+  args:
+    - "start-master.sh"

+ 90 - 0
aarch64/pjibot_patrol/common/service/disk_clean.go

@@ -0,0 +1,90 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_patrol/master/package/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/domain"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	"time"
+)
+
+// DiskClean 如果磁盘占用过高,则删除timeWindow和对应的文件
+func DiskClean() {
+	c_log.GlobalLogger.Info("清理timeWindow,启动!")
+	/*
+		TTL(0, "删除旧数据");
+		STOP(1, "停止缓存");
+		LRU(2, "保留高优先级")
+	*/
+	policyToDescription := map[string]string{
+		"TTL":  "删除旧数据",
+		"STOP": "停止缓存",
+		"LRU":  "保留高优先级",
+	}
+
+	for {
+		time.Sleep(1000 * time.Millisecond)
+		// 1 获取磁盘占用
+		diskUsed, _ := util.GetDiskUsed(commonConfig.CloudConfig.Disk.Name)
+		if diskUsed > commonConfig.CloudConfig.Disk.Used {
+			policy := commonConfig.PlatformConfig.TaskCachePolicy
+			c_log.GlobalLogger.Errorf("磁盘占用 %v 超过 %v,触发删除规则 %v", diskUsed, commonConfig.CloudConfig.Disk.Used, policyToDescription[policy])
+			// 2 获取策略
+			if policy == "TTL" {
+				// 1 获取时间窗口队列中的第二个
+				if len(entity.TimeWindowConsumerQueue) > 2 {
+					deleteTimeWindow(1)
+				}
+			} else if policy == "STOP" {
+				// 2 获取时间窗口队列中的倒数第一个
+				if len(entity.TimeWindowConsumerQueue) > 2 {
+					deleteTimeWindow(len(entity.TimeWindowConsumerQueue) - 1)
+				}
+			} else if policy == "LRU" {
+				// 3 获取优先级最低的时间窗口
+				if len(entity.TimeWindowConsumerQueue) > 2 {
+					indexToRemove := getIndexToRemoveForLRU()
+					if indexToRemove != -1 {
+						deleteTimeWindow(indexToRemove)
+					}
+				}
+			} else {
+				c_log.GlobalLogger.Error("未知的缓存策略:", policy)
+			}
+
+		}
+	}
+}
+
+func deleteTimeWindow(indexToRemove int) {
+	timeWindowToRemove := entity.TimeWindowConsumerQueue[indexToRemove]
+	// 1 删除队列中的窗口。使用切片的特性删除指定位置的元素
+	entity.TimeWindowConsumerQueueMutex.Lock()
+	entity.TimeWindowConsumerQueue = append(entity.TimeWindowConsumerQueue[:indexToRemove], entity.TimeWindowConsumerQueue[indexToRemove+1:]...)
+	entity.TimeWindowConsumerQueueMutex.Unlock()
+	// 2 删除该窗口对应的文件目录。
+	faultTime := timeWindowToRemove.FaultTime
+	dir := domain.GetCopyDir(commonConfig.CloudConfig.BagCopyDir, faultTime)
+	err := util.RemoveDir(dir)
+	if err != nil {
+		c_log.GlobalLogger.Error("删除目录", dir, "失败:", err)
+	}
+}
+
+func getIndexToRemoveForLRU() int {
+	lru := commonConfig.PlatformConfig.Lru
+	i := len(lru) - 1
+	for i >= 0 {
+		for i2, window := range entity.TimeWindowConsumerQueue {
+			for _, label := range window.Labels {
+				if masterConfig.LabelMapTriggerId[label] == lru[i] {
+					return i2
+				}
+			}
+		}
+	}
+	return -1
+
+}

+ 118 - 0
aarch64/pjibot_patrol/common/service/kill_self.go

@@ -0,0 +1,118 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"net/rpc"
+	"os"
+	"sync"
+	"time"
+)
+
+var (
+	ChannelKillRosRecord  = make(chan int)
+	ChannelKillDiskClean  = make(chan int)
+	ChannelKillSubscriber = make(chan int)
+	ChannelKillMove       = make(chan int)
+	ChannelKillConsume    = make(chan int)
+
+	KillChannel = 5
+	KillTimes   = 0
+	MutexKill   sync.Mutex
+)
+
+// KillSignal 停止信号,主从节点接收到数据后准备重启
+type KillSignal struct {
+	NodeName       string
+	DropUploadData bool
+	Restart        bool
+}
+
+// KillService 定义要远程调用的类型和方法
+type KillService struct{}
+
+// Kill 杀死自身程序,通过通道实现 方法必须满足RPC规范:函数有两个参数,第一个参数是请求,第二个是响应
+func (m *KillService) Kill(args KillSignal, reply *int) error {
+	c_log.GlobalLogger.Info("接收到自杀信号:", args)
+	// 1 杀死 rosbag record 命令
+	ChannelKillRosRecord <- 1
+	// 2 杀死所有 ros 订阅者
+	ChannelKillSubscriber <- 1
+	// 3 杀死上传任任务
+	if args.DropUploadData == true {
+		// 3-1 等待上传结束再杀死
+		ChannelKillMove <- 1
+		ChannelKillConsume <- 1
+	} else {
+		// 3-2 直接杀死
+		ChannelKillMove <- 2
+		ChannelKillConsume <- 2
+	}
+	go killDone(args.Restart)
+	return nil
+}
+func WaitKillSelf() {
+	killService := new(KillService)
+	err := rpc.Register(killService)
+	if err != nil {
+		c_log.GlobalLogger.Error("注册rpc服务失败:", err)
+		return
+	}
+
+	// 等待并处理远程调用请求
+	for {
+		conn, err := commonConfig.KillSignalListener.Accept()
+		if err != nil {
+			continue
+		}
+		go rpc.ServeConn(conn)
+	}
+}
+
+func AddKillTimes(info string) {
+	MutexKill.Lock()
+	defer MutexKill.Unlock()
+	switch info {
+	case "1":
+		ChannelKillDiskClean <- 1
+		close(ChannelKillRosRecord)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 record 打包 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "2":
+		close(ChannelKillDiskClean)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 bag 包数量维护 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "3":
+		close(ChannelKillSubscriber)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 rosnode 和ros 订阅者 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "4":
+		close(ChannelKillMove)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 bag 包移动 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	case "5":
+		close(ChannelKillConsume)
+		KillTimes++
+		c_log.GlobalLogger.Infof("已杀死 bag 包消费 goroutine,当前自杀进度 %v / %v", KillTimes, KillChannel)
+	}
+}
+
+func killDone(restart bool) {
+	for {
+		time.Sleep(time.Duration(1) * time.Second)
+		if KillChannel == KillTimes {
+			if restart {
+				_, err := util.ExecuteWithPath(commonConfig.LocalConfig.RestartCmd.Dir, commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args...)
+				if err != nil {
+					c_log.GlobalLogger.Info("启动新程序失败,【path】=", commonConfig.LocalConfig.RestartCmd.Dir, "【cmd】=", commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args, ":", err)
+					os.Exit(-1)
+				}
+				c_log.GlobalLogger.Info("数据采集任务更新,正常退出当前程序。")
+			} else {
+				c_log.GlobalLogger.Info("数据采集任务终止,正常退出当前程序。")
+			}
+			os.Exit(0)
+		}
+	}
+}

+ 36 - 0
aarch64/pjibot_patrol/common/service/rosbag_clean.go

@@ -0,0 +1,36 @@
+package service
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"time"
+)
+
+// BagCacheClean 保证本地缓存的包数量不超过设定值
+func BagCacheClean() {
+	c_log.GlobalLogger.Info("启动清理缓存的 goroutine 维护目录【", config.CloudConfig.BagDataDir, "】的 bag 包数量:", config.CloudConfig.BagNumber)
+	for {
+		// 收到自杀信号
+		select {
+		case signal := <-ChannelKillDiskClean:
+			if signal == 1 {
+				AddKillTimes("2")
+				return
+			}
+		default:
+		}
+
+		// 1 ------- 每10秒清理一次 -------
+		time.Sleep(time.Duration(10) * time.Second)
+		// 2 ------- 获取目录下所有bag包 -------
+		bags, _ := util.ListAbsolutePathWithSuffixAndSort(config.CloudConfig.BagDataDir, ".bag")
+		// 3 如果打包数量超过n个,删除最旧的包{
+		if len(bags) > config.CloudConfig.BagNumber {
+			diff := len(bags) - config.CloudConfig.BagNumber
+			for i := 0; i < diff; i++ {
+				_ = util.DeleteFile(bags[i])
+			}
+		}
+	}
+}

+ 131 - 0
aarch64/pjibot_patrol/common/service/rosbag_record.go

@@ -0,0 +1,131 @@
+package service
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"github.com/bluenviron/goroslib/v2"
+	"os"
+	"os/exec"
+	"time"
+)
+
+// BagRecord 打包rosbag
+func BagRecord(nodeName string) {
+	var err error
+	c_log.GlobalLogger.Info("rosbag record goroutine - 启动")
+	for {
+		c_log.GlobalLogger.Info("校验必需的 rosnode 是否全部启动。")
+		canRecord := false
+		for !canRecord {
+			time.Sleep(time.Duration(2) * time.Second)
+			canRecord = isCanRecord(config.RosNode)
+		}
+		c_log.GlobalLogger.Info("rosnode 启动完成,正在启动 rosbag record 命令。")
+
+		var command []string
+		command = append(command, "record")
+		command = append(command, "--split")
+		command = append(command, "--duration=1")
+		for _, host := range config.CloudConfig.Hosts {
+			if host.Name == nodeName {
+				for _, topic := range host.Topics {
+					command = append(command, topic)
+				}
+			}
+		}
+
+		// 2 ------- 调用 rosbag 打包命令,该命令自动阻塞 -------
+		// 不在此处压缩,因为 rosbag filter 时会报错。在上传到oss之前压缩即可。
+		// 包名格式:2023-11-15-17-35-20_0.bag
+		_ = util.CreateParentDir(config.CloudConfig.BagDataDir)
+		var recordProcessPid int
+		var recordSubProcessPid int
+		var cmd *exec.Cmd
+		systemEnv := os.Environ()
+		c_log.GlobalLogger.Info("系统环境变量为:", systemEnv)
+	parent:
+		for {
+			c_log.GlobalLogger.Info("record 环境变量为:", config.RosbagEnvs)
+			cmd, err = util.ExecuteWithEnvAndDirAsync(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+			if err != nil {
+				c_log.GlobalLogger.Error("执行record命令", command, "出错:", err)
+				continue
+			}
+			recordProcessPid = cmd.Process.Pid
+		sub:
+			for {
+				time.Sleep(time.Duration(2) * time.Second)
+				process, err := os.FindProcess(recordProcessPid)
+				if process == nil {
+					continue parent
+				}
+				recordSubProcessPid, err = util.GetSubProcessPid(recordProcessPid)
+				if err != nil {
+					c_log.GlobalLogger.Info("正在等待获取进程 ", recordProcessPid, " 的子进程的pid。")
+					continue sub
+				}
+				if recordSubProcessPid != 0 {
+					c_log.GlobalLogger.Info("获取进程 ", recordProcessPid, " 的子进程的pid:", recordSubProcessPid)
+					break parent
+				}
+			}
+		}
+		// 等待自杀信号
+		c_log.GlobalLogger.Info("启动record命令成功。等待自杀信号。")
+		select {
+		case signal := <-ChannelKillRosRecord:
+			if signal == 1 {
+				if err := util.KillProcessByPid(recordSubProcessPid); err != nil {
+					c_log.GlobalLogger.Errorf("程序阻塞,杀死record命令子进程出错,【pid】=%v,【err】=%v。", recordSubProcessPid, err)
+					select {} // 此处阻塞防止record命令一直录包占满存储
+				}
+				if err = cmd.Process.Kill(); err != nil {
+					c_log.GlobalLogger.Error("程序阻塞,杀死record命令进程", recordProcessPid, "出错:", err)
+					select {} // 此处阻塞防止record命令一直录包占满存储
+				}
+				AddKillTimes("1")
+				return
+			}
+		}
+
+		// TODO 暂时不放开该逻辑。如果监控rosnode来判断是否杀死record,太麻烦,生成窗口的线程也需要关闭
+		//{
+		//	commonCfg.GlobalLogger.Info("正在监控rosnode是否全部关闭。")
+		//	for canRecord {
+		//		time.Sleep(time.Duration(1) * time.Second)
+		//		canRecord = isCanRecord(commonCfg.RosNode)
+		//	}
+		//	commonCfg.GlobalLogger.Info("rosnode已全部关闭,正在结束record进程。")
+		//
+		//	err = cmd.Process.Kill()
+		//	if err != nil {
+		//		commonCfg.GlobalLogger.Error("杀死record进程错误:", err)
+		//		continue
+		//	}
+		//}
+	}
+}
+
+func isCanRecord(n *goroslib.Node) bool {
+	time.Sleep(time.Duration(1) * time.Second)
+	// 获取
+	nodes, err := n.MasterGetNodes()
+	if err != nil {
+		c_log.GlobalLogger.Error("获取rosnode出错:", err)
+		return false
+	}
+	// 创建一个示例的map
+	myMap := nodes
+	// 创建一个切片,包含要检查的元素
+	mySlice := config.CloudConfig.Ros.Nodes
+
+	// 判断map的键是否包含切片中的所有元素
+	for _, element := range mySlice {
+		if _, ok := myMap[element]; !ok {
+			c_log.GlobalLogger.Info("rosnode:", element, " 未启动,需等待启动后才可启动record。")
+			return false
+		}
+	}
+	return true
+}

+ 160 - 0
aarch64/pjibot_patrol/common/service/rosbag_upload.go

@@ -0,0 +1,160 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_patrol/master/package/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/domain"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+func RunTimeWindowConsumerQueue(nodeName string) {
+	c_log.GlobalLogger.Info("处理消费者队列goroutine - 启动")
+outLoop:
+	for {
+
+		// 收到自杀信号
+		select {
+		case signal := <-ChannelKillConsume:
+			if signal == 1 {
+				ChannelKillConsume <- 1
+				if len(entity.TimeWindowConsumerQueue) == 0 {
+					AddKillTimes("5")
+					return
+				}
+			} else { //signal == 2
+				AddKillTimes("5")
+				return
+			}
+		default:
+		}
+		// 每一秒扫一次
+		time.Sleep(time.Duration(1) * time.Second)
+
+		waitLength := len(entity.TimeWindowConsumerQueue)
+		if waitLength == 0 {
+			continue outLoop
+		}
+		c_log.GlobalLogger.Infof("待处理窗口个数为:%v", len(entity.TimeWindowConsumerQueue))
+		// 1 获取即将处理的窗口
+		currentTimeWindow := entity.TimeWindowConsumerQueue[0]
+		entity.RemoveHeadOfTimeWindowConsumerQueue()
+		c_log.GlobalLogger.Infof("开始处理窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", currentTimeWindow.Labels, currentTimeWindow.FaultTime, currentTimeWindow.Length)
+		// 2 获取目录
+		dir := domain.GetCopyDir(commonConfig.CloudConfig.BagCopyDir, currentTimeWindow.FaultTime)
+		bags, _ := util.ListAbsolutePathWithSuffixAndSort(dir, ".bag")
+		bagNumber := len(bags)
+		if bagNumber > currentTimeWindow.Length {
+			bagNumber = currentTimeWindow.Length
+			bags = bags[0:currentTimeWindow.Length]
+		}
+
+		// 3 如果不是全量采集,则使用 filter 命令对 bag 包进行主题过滤。
+		if commonConfig.CloudConfig.FullCollect == false {
+			var filterTopics []string
+			if nodeName == commonConfig.CloudConfig.Hosts[0].Name {
+				filterTopics = currentTimeWindow.MasterTopics
+			} else {
+				filterTopics = currentTimeWindow.SlaveTopics
+			}
+			var topicsFilterSlice []string
+			for _, topic := range filterTopics {
+				topicsFilterSlice = append(topicsFilterSlice, "topic=='"+topic+"'")
+			}
+			for i, bag := range bags {
+				oldName := bag
+				newName := bag + "_filter"
+				filterCommand := []string{"filter", oldName, newName, "\"" + strings.Join(topicsFilterSlice, " or ") + "\""}
+				_, output, err := util.ExecuteWithEnvSync(commonConfig.RosbagEnvs, commonConfig.RosbagPath, filterCommand...)
+				c_log.GlobalLogger.Info("正在过滤中,【FaultTime】=", currentTimeWindow.FaultTime, "【Label】=", currentTimeWindow.Labels, ",进度", i+1, "/", bagNumber, "。")
+				if err != nil {
+					c_log.GlobalLogger.Errorf("filter命令执行出错【命令】=%v,【输出】=%v,【err】=%v", filterCommand, output, err)
+					continue
+				}
+				// 删除旧文件
+				util.DeleteFile(oldName)
+				// 将新文件改回旧文件名
+				if err = os.Rename(newName, oldName); err != nil {
+					c_log.GlobalLogger.Info("修改文件名", oldName, "失败,放弃当前时间窗口", currentTimeWindow.FaultTime, ",错误为:", err)
+					continue outLoop
+				}
+			}
+		}
+
+		// 4 compress包,必须顺序执行,此时每个包会对应生成一个压缩过的包和原始包,原始包后缀为.orig.bag
+		// 5 todo 机器人去掉压缩过程,防止cpu跑满
+		//c_log.GlobalLogger.Info("压缩 bag 数据包,故障时间为:", currentTimeWindow.FaultTime)
+		//for i, bag := range bags {
+		//	oldName := bag
+		//	compressCommand := []string{"compress", "--bz2", oldName}
+		//	c_log.GlobalLogger.Info("正在压缩中,【FaultTime】=", currentTimeWindow.FaultTime, "【Label】=", currentTimeWindow.Labels, ",进度", i+1, "/", bagNumber, "。")
+		//	if _, output, err := util.ExecuteWithEnvSync(commonConfig.RosbagEnvs, commonConfig.RosbagPath, compressCommand...); err != nil {
+		//		c_log.GlobalLogger.Errorf("compress命令执行出错【命令】=%v,【输出】=%v,【err】=%v", compressCommand, output, err)
+		//		continue
+		//	}
+		//}
+		// 5 upload,必须顺序执行
+		c_log.GlobalLogger.Info("发送bag数据包,故障时间为:", currentTimeWindow.FaultTime)
+		start := time.Now()
+		objectKey1 := commonConfig.LocalConfig.OssBasePrefix + commonConfig.LocalConfig.EquipmentNo + "/data/" + currentTimeWindow.FaultTime + "_" + strings.Join(currentTimeWindow.Labels, "_") + "_" + fmt.Sprintf("%d", bagNumber) + "/"
+		objectKey2 := commonConfig.LocalConfig.OssBasePrefix + commonConfig.LocalConfig.EquipmentNo + "/data_merge/" + currentTimeWindow.FaultTime + "_" + strings.Join(currentTimeWindow.Labels, "_") + "_" + fmt.Sprintf("%d", bagNumber) + ".bag"
+		objectKey3 := commonConfig.LocalConfig.OssBasePrefix + commonConfig.LocalConfig.EquipmentNo + "/data_parse/" + currentTimeWindow.FaultTime + "_" + strings.Join(currentTimeWindow.Labels, "_") + "_" + fmt.Sprintf("%d", bagNumber) + "/"
+		for i, bag := range bags {
+			startOne := time.Now()
+			bagSlice := strings.Split(bag, "/")
+			for {
+				commonConfig.OssMutex.Lock()
+				err := commonConfig.OssBucket.PutObjectFromFile(objectKey1+bagSlice[len(bagSlice)-1], bag)
+				commonConfig.OssMutex.Unlock()
+				if err != nil {
+					c_log.GlobalLogger.Info("因网络原因上传包 ", bag, " 时报错,需要等待网络恢复后重新上传:", err)
+					continue
+				}
+				c_log.GlobalLogger.Info("上传耗时 ", time.Since(startOne), ",【FaultTime】=", currentTimeWindow.FaultTime, "【Label】=", currentTimeWindow.Labels, ",进度", i+1, "/", bagNumber, "。【", bag, "】-------【", objectKey1+bagSlice[len(bagSlice)-1], "】")
+				break
+			}
+		}
+		c_log.GlobalLogger.Info("上传完成,花费时间:", time.Since(start))
+		// 在上传完成的包目录同级下添加一个目录同名的json
+		triggerIds := make([]string, 0)
+		for _, label := range currentTimeWindow.Labels {
+			triggerIds = append(triggerIds, masterConfig.LabelMapTriggerId[label])
+		}
+		callBackMap := map[string]interface{}{
+			"dataName":    currentTimeWindow.FaultTime, // 云端callback程序会将该值加8小时,因为UTC和CSV时区相差8小时
+			"dataSize":    "",                          // 由合并程序补充
+			"equipmentNo": commonConfig.LocalConfig.EquipmentNo,
+			"secretKey":   commonConfig.LocalConfig.SecretKey,
+			"rosBagPath":  objectKey2,
+			"filePath":    objectKey3,
+			"taskId":      commonConfig.PlatformConfig.TaskConfigId,
+			"triggerId":   triggerIds,
+		}
+		callBackJson, err := util.MapToJsonString(callBackMap)
+		if err != nil {
+			c_log.GlobalLogger.Error("callBackMap", callBackMap, "转json失败:", err)
+		}
+		commonConfig.OssMutex.Lock()
+		err = commonConfig.OssBucket.PutObject(objectKey3+"callback.json", strings.NewReader(callBackJson))
+		for _, file := range commonConfig.CloudConfig.MapBufFiles {
+			err = commonConfig.OssBucket.PutObjectFromFile(objectKey3+filepath.Base(file), file)
+		}
+		commonConfig.OssMutex.Unlock()
+		if err != nil {
+			c_log.GlobalLogger.Error("上传 callback.json 或 mapBuf 文件失败:", err)
+		}
+
+		// 删除本地所有已上传的bag文件
+		c_log.GlobalLogger.Infof("结束处理窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", currentTimeWindow.Labels, currentTimeWindow.FaultTime, currentTimeWindow.Length)
+		if err = util.RemoveDir(dir); err != nil {
+			continue outLoop
+		}
+
+	}
+}

+ 6 - 0
aarch64/pjibot_patrol/common/variable/application.go

@@ -0,0 +1,6 @@
+package variable
+
+var (
+	LogDir          = "/root/cicv-data-closedloop/log/"
+	LocalConfigPath = "/root/cicv-data-closedloop/config/local-config.yaml"
+)

+ 103 - 0
aarch64/pjibot_patrol/control/main.go

@@ -0,0 +1,103 @@
+package main
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_patrol/common/service"
+	"cicv-data-closedloop/aarch64/pjibot_patrol/common/variable"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"net/rpc"
+	"os"
+	"runtime"
+	"time"
+)
+
+var applicationName = "pji-control"
+
+func init() {
+	runtime.GOMAXPROCS(1)
+	// 初始化日志配置
+	c_log.InitLog(variable.LogDir, applicationName)
+	// 初始化本地配置文件(第1处配置,在本地文件)
+	commonConfig.InitLocalConfig(variable.LocalConfigPath)
+	// 初始化Oss连接信息
+	commonConfig.InitOssConfig()
+	// 初始化业务逻辑配置信息,配置文件在oss上(第2处配置,在oss文件)
+	commonConfig.InitCloudConfig()
+	// 初始化rpc客户端,用于杀死旧的采集程序
+}
+
+func main() {
+	lastStatus := "NONE"
+	//  轮询任务接口判断是否有更新
+	for {
+		time.Sleep(time.Duration(2) * time.Second)
+		// 1 获取当前设备的任务的 status
+		status, err := commonConfig.GetStatus(commonConfig.PlatformConfig.TaskConfigId)
+		if err != nil {
+			c_log.GlobalLogger.Error("获取配置status失败:", err)
+			continue
+		}
+		// 2 判断 status
+		// UN_CHANGE 没有新的任务,无需更改
+		// CHANGE 有新的任务,需要杀死旧的任务并重启
+		// NONE 设备没有配置任务,需要杀死旧的任务
+		if status == "UN_CHANGE" {
+			lastStatus = "UN_CHANGE"
+			continue
+		} else if status == "CHANGE" || status == "NONE" {
+			if lastStatus == "CHANGE" && status == "CHANGE" { // 供更新使用
+				commonConfig.InitPlatformConfig()
+				continue
+			}
+			if lastStatus == "NONE" && status == "NONE" {
+				continue
+			}
+			// 3 发送rpc信号杀死采集程序
+			if lastStatus == "NONE" && status == "CHANGE" {
+				if _, err := util.ExecuteWithPath(commonConfig.LocalConfig.RestartCmd.Dir, commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args...); err != nil {
+					c_log.GlobalLogger.Info("启动新程序失败,【path】=", commonConfig.LocalConfig.RestartCmd.Dir, "【cmd】=", commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args, ":", err)
+					os.Exit(-1)
+				}
+				c_log.GlobalLogger.Info("启动任务,本地执行启动命令:【path】=", commonConfig.LocalConfig.RestartCmd.Dir, "【cmd】=", commonConfig.LocalConfig.RestartCmd.Name, commonConfig.LocalConfig.RestartCmd.Args)
+				lastStatus = status
+				c_log.GlobalLogger.Info("获取数据闭环平台最新配置。")
+				commonConfig.InitPlatformConfig()
+				continue
+			}
+			var killArgs commonService.KillSignal
+			if lastStatus == "UN_CHANGE" && status == "CHANGE" {
+				killArgs = commonService.KillSignal{NodeName: "master", DropUploadData: commonConfig.PlatformConfig.DropUploadData, Restart: true}
+				c_log.GlobalLogger.Info("更新任务,发送rpc重启信号:", killArgs)
+			}
+			if lastStatus == "UN_CHANGE" && status == "NONE" {
+				killArgs = commonService.KillSignal{NodeName: "master", DropUploadData: commonConfig.PlatformConfig.DropUploadData, Restart: false}
+				c_log.GlobalLogger.Info("杀死任务,发送rpc结束信号:", killArgs)
+			}
+
+			KillRpcClient, err := rpc.Dial("tcp", commonConfig.LocalConfig.Node.Ip+":"+commonConfig.CloudConfig.RpcPort)
+			if err != nil {
+				// 此处如果连接失败说明采集程序已经停止了
+				lastStatus = "NONE"
+				c_log.GlobalLogger.Error("采集程序已经停止:", err)
+				continue
+			}
+
+			reply := 0
+			if err = KillRpcClient.Call("KillService.Kill", killArgs, &reply); err != nil {
+				c_log.GlobalLogger.Error("发送 rpc 请求到 master 报错:", err)
+				// 这里可能会报错unexpected EOF但是不影响,先注释 close 和 continue
+				//KillRpcClient.Close()
+				//continue
+			}
+			lastStatus = status
+			c_log.GlobalLogger.Info("结束任务后,将数据闭环平台配置置空。")
+			commonConfig.PlatformConfig = commonConfig.PlatformConfigStruct{}
+			if err = KillRpcClient.Close(); err != nil {
+				// 不做处理
+			}
+		} else {
+			c_log.GlobalLogger.Error("未知的采集任务状态。【status】=", status)
+		}
+	}
+}

+ 62 - 0
aarch64/pjibot_patrol/master/main.go

@@ -0,0 +1,62 @@
+package main
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_patrol/common/service"
+	"cicv-data-closedloop/aarch64/pjibot_patrol/common/variable"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_patrol/master/package/config"
+	masterService "cicv-data-closedloop/aarch64/pjibot_patrol/master/package/service"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"runtime"
+	"time"
+)
+
+var applicationName = "pji-master"
+
+func init() {
+	runtime.GOMAXPROCS(1)
+	// 初始化日志配置
+	c_log.InitLog(variable.LogDir, applicationName)
+	// 初始化本地配置文件(第1处配置,在本地文件)
+	commonConfig.InitLocalConfig(variable.LocalConfigPath)
+	// 初始化Oss连接信息
+	commonConfig.InitOssConfig()
+	// 初始化业务逻辑配置信息,配置文件在oss上(第2处配置,在oss文件)
+	commonConfig.InitCloudConfig()
+	_ = util.RemoveSubFiles(commonConfig.CloudConfig.BagDataDir)
+	_ = util.RemoveSubFiles(commonConfig.CloudConfig.BagCopyDir)
+	go commonConfig.RefreshCloudConfig()
+	// 初始化数据闭环平台的配置(第3处配置,在数据闭环平台接口)
+	commonConfig.InitPlatformConfig()
+	// 初始化ros节点
+	commonConfig.InitRosConfig()
+	// 发送资源占用信息
+	go commonConfig.SendResourceUsage()
+	// 维护data目录缓存的包数量
+	go commonService.BagCacheClean()
+	// 磁盘占用过高时根据缓存策略处理copy目录
+	go commonService.DiskClean()
+	masterConfig.InitTriggerConfig()
+	commonConfig.InitKillSignalListener(commonConfig.CloudConfig.Hosts[0].Ip)
+	// 等待重启,接收到重启信号,会把信号分发给以下channel
+	go commonService.WaitKillSelf()
+	// 先采集地图bag包
+	masterService.CollectOneMsg()
+}
+
+func main() {
+
+	// 1 负责打包数据到data目录
+	go commonService.BagRecord(commonConfig.CloudConfig.Hosts[0].Name)
+	time.Sleep(time.Duration(10) * time.Second)
+	// 2 负责监控故障,并修改timeWindow
+	go masterService.PrepareTimeWindowProducerQueue()
+	// 3 将时间窗口内的包全部move出去,并等待当前时间窗口结束触发上传
+	go masterService.RunTimeWindowProducerQueue()
+	// 4 排队运行时间窗口
+	go commonService.RunTimeWindowConsumerQueue(commonConfig.CloudConfig.Hosts[0].Name)
+
+	// 阻塞主线程,等待其他线程执行。
+	select {}
+}

+ 140 - 0
aarch64/pjibot_patrol/master/package/config/master_trigger_cfg.go

@@ -0,0 +1,140 @@
+package config
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"cicv-data-closedloop/pji_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/diagnostic_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/nav_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/sensor_msgs"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/std_msgs"
+	"plugin"
+	"strconv"
+)
+
+var (
+	LabelMapTriggerId = make(map[string]string)
+
+	// 1
+	TopicOfDiagnostics = "/diagnostics"
+	RuleOfDiagnostics  []func(data *diagnostic_msgs.DiagnosticArray) string
+	// 2
+	TopicOfImu = "/imu"
+	RuleOfImu  []func(data *sensor_msgs.Imu) string
+	// 3
+	TopicOfLocateInfo = "/locate_info"
+	RuleOfLocateInfo  []func(data *pji_msgs.LocateInfo) string
+	// 4
+	TopicOfObstacleDetection = "/obstacle_detection"
+	RuleOfObstacleDetection  []func(data *std_msgs.UInt8) string
+	// 5
+	TopicOfOdom = "/odom"
+	RuleOfOdom  []func(data *nav_msgs.Odometry) string
+	// 6
+	TopicOfSysInfo = "/sys_info"
+	RuleOfSysInfo  []func(data *pji_msgs.SysInfo) string
+)
+
+func InitTriggerConfig() {
+	loadSuccess := 0
+	// 下载所有触发器的文件
+	for _, trigger := range config.PlatformConfig.TaskTriggers {
+		// 下载
+		triggerLocalPath := config.CloudConfig.TriggersDir + trigger.TriggerScriptPath
+		_ = util.CreateParentDir(triggerLocalPath)
+		c_log.GlobalLogger.Info("下载触发器插件从", trigger.TriggerScriptPath, "到", triggerLocalPath)
+		config.OssMutex.Lock()
+		err := config.OssBucket.GetObjectToFile(trigger.TriggerScriptPath, triggerLocalPath)
+		config.OssMutex.Unlock()
+		if err != nil {
+			c_log.GlobalLogger.Errorf("下载oss上的触发器插件失败【%v】->【%v】:%v", trigger.TriggerScriptPath, triggerLocalPath, err)
+			continue
+		}
+		// 载入插件到数组
+		open, err := plugin.Open(triggerLocalPath)
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "失败。", err)
+		}
+		topic0, err := open.Lookup("Topic")
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "中的Topic方法失败。", err)
+			continue
+		}
+		topic1, ok := topic0.(func() string)
+		if ok != true {
+			c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func() string):", err)
+			continue
+		}
+		topic2 := topic1()
+		rule, err := open.Lookup("Rule")
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "中的Rule方法失败。", err)
+			continue
+		}
+		// 判断topic
+		// todo 如果是未知的topic,可以添加一个循环,更新平台配置,同理金龙车和多功能车
+		if TopicOfDiagnostics == topic2 { // 1
+			f, ok := rule.(func(*diagnostic_msgs.DiagnosticArray) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *diagnostic_msgs.DiagnosticArray) string):", err)
+				continue
+			}
+			RuleOfDiagnostics = append(RuleOfDiagnostics, f)
+		} else if TopicOfImu == topic2 { // 2
+			f, ok := rule.(func(data *sensor_msgs.Imu) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *sensor_msgs.Imu) string):", err)
+				continue
+			}
+			RuleOfImu = append(RuleOfImu, f)
+		} else if TopicOfLocateInfo == topic2 { // 3
+			f, ok := rule.(func(data *pji_msgs.LocateInfo) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *pji_msgs.LocateInfo) string):", err)
+				continue
+			}
+			RuleOfLocateInfo = append(RuleOfLocateInfo, f)
+		} else if TopicOfObstacleDetection == topic2 { // 4
+			f, ok := rule.(func(data *std_msgs.UInt8) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *std_msgs.UInt8) string):", err)
+				continue
+			}
+			RuleOfObstacleDetection = append(RuleOfObstacleDetection, f)
+		} else if TopicOfOdom == topic2 { // 5
+			f, ok := rule.(func(data *nav_msgs.Odometry) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *nav_msgs.Odometry) string):", err)
+				continue
+			}
+			RuleOfOdom = append(RuleOfOdom, f)
+		} else if TopicOfSysInfo == topic2 { // 6
+			f, ok := rule.(func(data *pji_msgs.SysInfo) string)
+			if ok != true {
+				c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Topic方法必须是(func(data *pji_msgs.SysInfo) string):", err)
+				continue
+			}
+			RuleOfSysInfo = append(RuleOfSysInfo, f)
+		} else {
+			c_log.GlobalLogger.Error("未知的topic:", topic2)
+			continue
+		}
+
+		label, err := open.Lookup("Label")
+		if err != nil {
+			c_log.GlobalLogger.Error("加载本地插件", triggerLocalPath, "中的TriggerName方法失败。", err)
+			continue
+		}
+		labelFunc, ok := label.(func() string)
+		if ok != true {
+			c_log.GlobalLogger.Error("插件", triggerLocalPath, "中的Label方法必须是(func() string):", err)
+			continue
+		}
+		labelString := labelFunc()
+		LabelMapTriggerId[labelString] = strconv.Itoa(trigger.TriggerId)
+		loadSuccess++
+		c_log.GlobalLogger.Info("主节点加载触发器插件:【ros topic】=", topic2, ",【触发器label】=", labelString, "【触发器ID】=", trigger.TriggerId)
+	}
+	c_log.GlobalLogger.Infof("一共有%v个触发器,加载成功了%v个,【label和id映射关系】=%v", len(config.PlatformConfig.TaskTriggers), loadSuccess, LabelMapTriggerId)
+}

+ 96 - 0
aarch64/pjibot_patrol/master/package/service/collect_one_msg.go

@@ -0,0 +1,96 @@
+package service
+
+import (
+	"cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/util"
+	"os"
+)
+
+func CollectOneMsg() {
+	collectMap()
+	collectTfStatic()
+	collectCostmap()
+}
+
+func collectMap() {
+
+	// rosbag record -O /root/cicv-data-closedloop/map_data.bag -l 1 /map
+	ossMapBagObjectKey := config.LocalConfig.OssBasePrefix + config.LocalConfig.EquipmentNo + "/map.bag"
+
+	var command []string
+	command = append(command, "record")
+	command = append(command, "-O")
+	command = append(command, config.CloudConfig.MapBagPath)
+	command = append(command, "-l")
+	command = append(command, "1")
+	command = append(command, "/map")
+	_, s, err := util.ExecuteWithEnvAndDir(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。采集/map包", command, "出错:", s, "----", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("采集/map包", command, "完成。")
+	config.OssMutex.Lock()
+	err = config.OssBucket.PutObjectFromFile(ossMapBagObjectKey, config.CloudConfig.MapBagPath)
+	config.OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。上传/map包", config.CloudConfig.MapBagPath, "->", ossMapBagObjectKey, "出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("上传/map包", config.CloudConfig.MapBagPath, "------", ossMapBagObjectKey, "成功。")
+}
+func collectTfStatic() {
+
+	// rosbag record -O /root/cicv-data-closedloop/map_data.bag -l 1 /map
+	ossMapBagObjectKey := config.LocalConfig.OssBasePrefix + config.LocalConfig.EquipmentNo + "/tfstatic.bag"
+
+	var command []string
+	command = append(command, "record")
+	command = append(command, "-O")
+	command = append(command, config.CloudConfig.MapBagPath)
+	command = append(command, "-l")
+	command = append(command, "1")
+	command = append(command, "/tf_static")
+	_, s, err := util.ExecuteWithEnvAndDir(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。采集/tf_static包", command, "出错:", s, "----", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("采集/tf_static包", command, "完成。")
+	config.OssMutex.Lock()
+	err = config.OssBucket.PutObjectFromFile(ossMapBagObjectKey, config.CloudConfig.MapBagPath)
+	config.OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。上传/tf_static包", config.CloudConfig.MapBagPath, "->", ossMapBagObjectKey, "出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("上传/tf_static包", config.CloudConfig.MapBagPath, "------", ossMapBagObjectKey, "成功。")
+}
+func collectCostmap() {
+
+	// rosbag record -O /root/cicv-data-closedloop/map_data.bag -l 1 /map
+	ossMapBagObjectKey := config.LocalConfig.OssBasePrefix + config.LocalConfig.EquipmentNo + "/costmap.bag"
+
+	var command []string
+	command = append(command, "record")
+	command = append(command, "-O")
+	command = append(command, config.CloudConfig.MapBagPath)
+	command = append(command, "-l")
+	command = append(command, "1")
+	command = append(command, "/move_base/global_costmap/costmap")
+	_, s, err := util.ExecuteWithEnvAndDir(config.RosbagEnvs, config.CloudConfig.BagDataDir, config.RosbagPath, command...)
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。采集/move_base/global_costmap/costmap包", command, "出错:", s, "----", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("采集/move_base/global_costmap/costmap包", command, "完成。")
+	config.OssMutex.Lock()
+	err = config.OssBucket.PutObjectFromFile(ossMapBagObjectKey, config.CloudConfig.MapBagPath)
+	config.OssMutex.Unlock()
+	if err != nil {
+		c_log.GlobalLogger.Error("程序异常退出。上传/move_base/global_costmap/costmap包", config.CloudConfig.MapBagPath, "->", ossMapBagObjectKey, "出错:", err)
+		os.Exit(-1)
+	}
+	c_log.GlobalLogger.Info("上传/move_base/global_costmap/costmap包", config.CloudConfig.MapBagPath, "------", ossMapBagObjectKey, "成功。")
+}

+ 68 - 0
aarch64/pjibot_patrol/master/package/service/move_bag_and_send_window.go

@@ -0,0 +1,68 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_patrol/common/service"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/domain"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	"time"
+)
+
+// RunTimeWindowProducerQueue 将时间窗口内的包全部move出去,并等待当前时间窗口结束触发上传
+func RunTimeWindowProducerQueue() {
+	c_log.GlobalLogger.Info("生产者队列goroutine - 启动")
+	for {
+		// 收到自杀信号
+		select {
+		case signal := <-commonService.ChannelKillMove:
+			if signal == 1 {
+				commonService.ChannelKillMove <- 1
+				if len(entity.TimeWindowProducerQueue) == 0 {
+					commonService.AddKillTimes("4")
+					return
+				}
+			} else { //signal == 2
+				commonService.AddKillTimes("4")
+				return
+			}
+		default:
+		}
+
+		// 处理
+		time.Sleep(time.Duration(1) * time.Second)
+		if len(entity.TimeWindowProducerQueue) > 0 {
+			bags, _ := util.ListAbsolutePathWithSuffixAndSort(commonConfig.CloudConfig.BagDataDir, ".bag")
+			currentTimeWindow := entity.TimeWindowProducerQueue[0]
+			move := false
+			bigger := false
+			for _, bag := range bags {
+				bagTime := util.GetBagTime(bag)
+				// 2 如果bag不小于timeWindowBegin不大于timeWindowEnd,则移动
+				compare1 := util.TimeCustom1GreaterEqualThanTimeCustom2(bagTime, currentTimeWindow.TimeWindowBegin)
+				compare2 := util.TimeCustom1LessEqualThanTimeCustom2(bagTime, currentTimeWindow.TimeWindowEnd)
+				if compare1 && compare2 {
+					// 将bag包移动到Copy目录
+					domain.MoveFromDataToCopy(currentTimeWindow.FaultTime, commonConfig.CloudConfig.BagDataDir, bag, commonConfig.CloudConfig.BagCopyDir)
+					move = true
+				} else {
+					if util.TimeCustom1GreaterEqualThanTimeCustom2(bagTime, currentTimeWindow.TimeWindowBegin) {
+						// 必须已经生成了窗口之后的包才算窗口结束了
+						bigger = true
+						break
+					}
+				}
+			}
+			// 如果没有包可以供当前窗口移动,且已经生成了更新的包,则当前窗口已经可以上传
+			if !move && bigger {
+				domain.SupplyCopyBags(commonConfig.CloudConfig.BagDataDir, commonConfig.CloudConfig.BagCopyDir, currentTimeWindow)
+				// 将时间窗口移出准备队列
+				entity.RemoveHeadOfTimeWindowProducerQueue()
+				// 将时间窗口加入运行队列
+				entity.AddTimeWindowToTimeWindowConsumerQueue(currentTimeWindow)
+				continue
+			}
+		}
+	}
+}

+ 156 - 0
aarch64/pjibot_patrol/master/package/service/produce_window.go

@@ -0,0 +1,156 @@
+package service
+
+import (
+	commonConfig "cicv-data-closedloop/aarch64/pjibot_patrol/common/config"
+	commonService "cicv-data-closedloop/aarch64/pjibot_patrol/common/service"
+	masterConfig "cicv-data-closedloop/aarch64/pjibot_patrol/master/package/config"
+	"cicv-data-closedloop/common/config/c_log"
+	"cicv-data-closedloop/common/entity"
+	"cicv-data-closedloop/common/util"
+	commonUtil "cicv-data-closedloop/common/util"
+	"encoding/json"
+	"github.com/bluenviron/goroslib/v2"
+	"github.com/bluenviron/goroslib/v2/pkg/msgs/std_msgs"
+	"sync"
+	"time"
+)
+
+// PrepareTimeWindowProducerQueue 负责监听所有主题并修改时间窗口
+func PrepareTimeWindowProducerQueue() {
+
+	var err error
+	subscribers := make([]*goroslib.Subscriber, len(commonConfig.SubscribeTopics))
+	subscribersTimes := make([]time.Time, len(commonConfig.SubscribeTopics))
+	subscribersTimeMutexes := make([]sync.Mutex, len(commonConfig.SubscribeTopics))
+	subscribersMutexes := make([]sync.Mutex, len(commonConfig.SubscribeTopics))
+	for i, topic := range commonConfig.SubscribeTopics {
+		c_log.GlobalLogger.Info("创建订阅者订阅话题:" + topic)
+		if topic == masterConfig.TopicOfObstacleDetection && len(masterConfig.RuleOfObstacleDetection) > 0 {
+			subscribers[i], err = goroslib.NewSubscriber(goroslib.SubscriberConf{
+				Node:  commonConfig.RosNode,
+				Topic: topic,
+				Callback: func(data *std_msgs.UInt8) {
+					subscribersTimeMutexes[i].Lock()
+					if time.Since(subscribersTimes[i]).Seconds() > 1 {
+						subscribersMutexes[i].Lock()
+						faultHappenTime := commonUtil.GetNowTimeCustom() // 获取当前故障发生时间
+						lastTimeWindow := entity.GetLastTimeWindow()     // 获取最后一个时间窗口
+						var faultLabel string
+						for _, f := range masterConfig.RuleOfObstacleDetection {
+							faultLabel = f(data)
+							if faultLabel != "" {
+								if canCollect() {
+									saveTimeWindow(faultLabel, faultHappenTime, lastTimeWindow)
+									subscribersTimes[i] = time.Now()
+									break
+								}
+							}
+						}
+						subscribersMutexes[i].Unlock()
+					}
+					subscribersTimeMutexes[i].Unlock()
+				},
+			})
+		}
+		if err != nil {
+			c_log.GlobalLogger.Info("创建订阅者", masterConfig.TopicOfObstacleDetection, "发生故障:", err)
+			//TODO 如何回传日志
+			continue
+		}
+	}
+
+	select {
+	case signal := <-commonService.ChannelKillSubscriber:
+		if signal == 1 {
+			commonConfig.RosNode.Close()
+			commonService.AddKillTimes("3")
+			return
+		}
+	}
+}
+
+func saveTimeWindow(faultLabel string, faultHappenTime string, lastTimeWindow *entity.TimeWindow) {
+	masterTopics, slaveTopics := getTopicsOfNode(faultLabel)
+	if lastTimeWindow == nil || commonUtil.TimeCustom1GreaterTimeCustom2(faultHappenTime, lastTimeWindow.TimeWindowEnd) {
+		// 2-1 如果是不在旧故障窗口内,添加一个新窗口
+		newTimeWindow := entity.TimeWindow{
+			FaultTime:       faultHappenTime,
+			TimeWindowBegin: commonUtil.TimeCustomChange(faultHappenTime, -commonConfig.PlatformConfig.TaskBeforeTime),
+			TimeWindowEnd:   commonUtil.TimeCustomChange(faultHappenTime, commonConfig.PlatformConfig.TaskAfterTime),
+			Length:          commonConfig.PlatformConfig.TaskBeforeTime + commonConfig.PlatformConfig.TaskAfterTime + 1,
+			Labels:          []string{faultLabel},
+			MasterTopics:    masterTopics,
+			SlaveTopics:     slaveTopics,
+		}
+		c_log.GlobalLogger.Infof("不在旧故障窗口内,向生产者队列添加一个新窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", newTimeWindow.Labels, newTimeWindow.FaultTime, newTimeWindow.Length)
+		entity.AddTimeWindowToTimeWindowProducerQueue(newTimeWindow)
+	} else {
+		// 2-2 如果在旧故障窗口内
+		entity.TimeWindowProducerQueueMutex.RLock()
+		defer entity.TimeWindowProducerQueueMutex.RUnlock()
+		// 2-2-1 更新故障窗口end时间
+		maxEnd := commonUtil.TimeCustomChange(lastTimeWindow.TimeWindowBegin, commonConfig.PlatformConfig.TaskMaxTime)
+		expectEnd := commonUtil.TimeCustomChange(faultHappenTime, commonConfig.PlatformConfig.TaskAfterTime)
+		if commonUtil.TimeCustom1GreaterTimeCustom2(expectEnd, maxEnd) {
+			lastTimeWindow.TimeWindowEnd = maxEnd
+			lastTimeWindow.Length = commonConfig.PlatformConfig.TaskMaxTime
+		} else {
+			if commonUtil.TimeCustom1GreaterTimeCustom2(expectEnd, lastTimeWindow.TimeWindowEnd) {
+				lastTimeWindow.TimeWindowEnd = expectEnd
+				lastTimeWindow.Length = commonUtil.CalculateDifferenceOfTimeCustom(lastTimeWindow.TimeWindowBegin, expectEnd)
+			}
+		}
+		// 2-2-2 更新label
+		labels := lastTimeWindow.Labels
+		lastTimeWindow.Labels = commonUtil.AppendIfNotExists(labels, faultLabel)
+		// 2-2-3 更新 topic
+		sourceMasterTopics := lastTimeWindow.MasterTopics
+		lastTimeWindow.MasterTopics = commonUtil.MergeSlice(sourceMasterTopics, masterTopics)
+		sourceSlaveTopics := lastTimeWindow.SlaveTopics
+		lastTimeWindow.SlaveTopics = commonUtil.MergeSlice(sourceSlaveTopics, slaveTopics)
+		c_log.GlobalLogger.Infof("在旧故障窗口内,更新生产者队列最新的窗口,【Lable】=%v,【FaultTime】=%v,【Length】=%v", lastTimeWindow.Labels, lastTimeWindow.FaultTime, lastTimeWindow.Length)
+	}
+}
+
+func getTopicsOfNode(faultLabel string) (masterTopics []string, slaveTopics []string) {
+	// 获取所有需要采集的topic
+	var faultCodeTopics []string
+	for _, code := range commonConfig.CloudConfig.Triggers {
+		if code.Label == faultLabel {
+			faultCodeTopics = code.Topics
+		}
+	}
+	return faultCodeTopics, nil
+}
+
+// 判断采集包数量是否超过限额
+func canCollect() bool {
+	responseString, err := commonUtil.HttpPostJsonWithHeaders(
+		commonConfig.CloudConfig.CollectLimit.Url,
+		map[string]string{"Authorization": "U9yKpD6kZZDDe4LFKK6myAxBUT1XRrDM"},
+		map[string]string{
+			"snCode":            commonConfig.LocalConfig.SecretKey,
+			"collectLimitDay":   util.ToString(commonConfig.CloudConfig.CollectLimit.Day),
+			"collectLimitWeek":  util.ToString(commonConfig.CloudConfig.CollectLimit.Week),
+			"collectLimitMonth": util.ToString(commonConfig.CloudConfig.CollectLimit.Month),
+			"collectLimitYear":  util.ToString(commonConfig.CloudConfig.CollectLimit.Year),
+		},
+	)
+	if err != nil {
+		c_log.GlobalLogger.Error("发送http请求获取是否允许采集失败:", err)
+		return false
+	}
+	// 解析JSON字符串到Response结构体
+	var resp entity.Response
+	err = json.Unmarshal([]byte(responseString), &resp)
+	if err != nil {
+		c_log.GlobalLogger.Error("解析是否允许采集接口返回结果失败:", err)
+		return false
+	}
+	if resp.Code != 200 { // 不是200 代表不允许采集
+		c_log.GlobalLogger.Info("采集数量已超过限额,当前周期内不再采集。", resp.Code)
+		return false
+	}
+	c_log.GlobalLogger.Info("允许采集。")
+	return true
+}