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  }