github.com/bluenviron/gomavlib/v2@v2.2.1-0.20240308101627-2c07e3da629c/node_streamrequest.go (about) 1 package gomavlib 2 3 import ( 4 "reflect" 5 "sync" 6 "time" 7 8 "github.com/bluenviron/gomavlib/v2/pkg/message" 9 ) 10 11 const ( 12 streamRequestPeriod = 30 * time.Second 13 ) 14 15 type streamNode struct { 16 Channel *Channel 17 SystemID byte 18 ComponentID byte 19 } 20 21 type nodeStreamRequest struct { 22 n *Node 23 msgHeartbeat message.Message 24 msgRequestDataStream message.Message 25 lastRequestsMutex sync.Mutex 26 lastRequests map[streamNode]time.Time 27 28 // in 29 terminate chan struct{} 30 31 // out 32 done chan struct{} 33 } 34 35 func newNodeStreamRequest(n *Node) *nodeStreamRequest { 36 // module is disabled 37 if !n.conf.StreamRequestEnable { 38 return nil 39 } 40 41 // dialect must be enabled 42 if n.conf.Dialect == nil { 43 return nil 44 } 45 46 // heartbeat message must exist in dialect and correspond to standard 47 msgHeartbeat := func() message.Message { 48 for _, m := range n.conf.Dialect.Messages { 49 if m.GetID() == 0 { 50 return m 51 } 52 } 53 return nil 54 }() 55 if msgHeartbeat == nil { 56 return nil 57 } 58 mde, err := message.NewReadWriter(msgHeartbeat) 59 if err != nil || mde.CRCExtra() != 50 { 60 return nil 61 } 62 63 // request data stream message must exist in dialect and correspond to standard 64 msgRequestDataStream := func() message.Message { 65 for _, m := range n.conf.Dialect.Messages { 66 if m.GetID() == 66 { 67 return m 68 } 69 } 70 return nil 71 }() 72 if msgRequestDataStream == nil { 73 return nil 74 } 75 mde, err = message.NewReadWriter(msgRequestDataStream) 76 if err != nil || mde.CRCExtra() != 148 { 77 return nil 78 } 79 80 sr := &nodeStreamRequest{ 81 n: n, 82 msgHeartbeat: msgHeartbeat, 83 msgRequestDataStream: msgRequestDataStream, 84 lastRequests: make(map[streamNode]time.Time), 85 terminate: make(chan struct{}), 86 done: make(chan struct{}), 87 } 88 89 return sr 90 } 91 92 func (sr *nodeStreamRequest) close() { 93 close(sr.terminate) 94 <-sr.done 95 } 96 97 func (sr *nodeStreamRequest) run() { 98 defer close(sr.done) 99 100 ticker := time.NewTicker(30 * time.Second) 101 defer ticker.Stop() 102 103 for { 104 select { 105 // periodic cleanup 106 case now := <-ticker.C: 107 func() { 108 sr.lastRequestsMutex.Lock() 109 defer sr.lastRequestsMutex.Unlock() 110 111 for rnode, t := range sr.lastRequests { 112 if now.Sub(t) >= streamRequestPeriod { 113 delete(sr.lastRequests, rnode) 114 } 115 } 116 }() 117 118 case <-sr.terminate: 119 return 120 } 121 } 122 } 123 124 func (sr *nodeStreamRequest) onEventFrame(evt *EventFrame) { 125 // message must be heartbeat and sender must be an ardupilot device 126 if evt.Message().GetID() != 0 || 127 reflect.ValueOf(evt.Message()).Elem().FieldByName("Autopilot").Uint() != 3 { 128 return 129 } 130 131 rnode := streamNode{ 132 Channel: evt.Channel, 133 SystemID: evt.SystemID(), 134 ComponentID: evt.ComponentID(), 135 } 136 137 // request streams if sender is new or a request has not been sent in some time 138 request := false 139 func() { 140 sr.lastRequestsMutex.Lock() 141 defer sr.lastRequestsMutex.Unlock() 142 143 now := time.Now() 144 145 if _, ok := sr.lastRequests[rnode]; !ok { 146 sr.lastRequests[rnode] = time.Now() 147 request = true 148 } else if now.Sub(sr.lastRequests[rnode]) >= streamRequestPeriod { 149 request = true 150 sr.lastRequests[rnode] = now 151 } 152 }() 153 154 if request { 155 // https://github.com/mavlink/qgroundcontrol/blob/08f400355a8f3acf1dd8ed91f7f1c757323ac182/src 156 // /FirmwarePlugin/APM/APMFirmwarePlugin.cc#L626 157 streams := []int{ 158 1, // common.MAV_DATA_STREAM_RAW_SENSORS, 159 2, // common.MAV_DATA_STREAM_EXTENDED_STATUS, 160 3, // common.MAV_DATA_STREAM_RC_CHANNELS, 161 6, // common.MAV_DATA_STREAM_POSITION, 162 10, // common.MAV_DATA_STREAM_EXTRA1, 163 11, // common.MAV_DATA_STREAM_EXTRA2, 164 12, // common.MAV_DATA_STREAM_EXTRA3, 165 } 166 167 for _, stream := range streams { 168 m := reflect.New(reflect.TypeOf(sr.msgRequestDataStream).Elem()) 169 m.Elem().FieldByName("TargetSystem").SetUint(uint64(evt.SystemID())) 170 m.Elem().FieldByName("TargetComponent").SetUint(uint64(evt.ComponentID())) 171 m.Elem().FieldByName("ReqStreamId").SetUint(uint64(stream)) 172 m.Elem().FieldByName("ReqMessageRate").SetUint(uint64(sr.n.conf.StreamRequestFrequency)) 173 m.Elem().FieldByName("StartStop").SetUint(uint64(1)) 174 sr.n.WriteMessageTo(evt.Channel, m.Interface().(message.Message)) //nolint:errcheck 175 } 176 177 sr.n.pushEvent(&EventStreamRequested{ 178 Channel: evt.Channel, 179 SystemID: evt.SystemID(), 180 ComponentID: evt.ComponentID(), 181 }) 182 } 183 }