/// <summary> /// Asks the agent to observe an <see cref="IDatum"/> object that was generated by Sensus, either during /// <see cref="SensingAgentState.OpportunisticObservation"/> or during <see cref="SensingAgentState.ActiveObservation"/>. /// </summary> /// <returns>A <see cref="ControlCompletionCheck"/> to be configured upon return, or <c>null</c> for no such check.</returns> /// <param name="datum">Datum.</param> /// <param name="cancellationToken">Cancellation token.</param> public async Task <ControlCompletionCheck> ObserveAsync(IDatum datum, CancellationToken cancellationToken) { // certain probes (e.g., ios activity polling) return a null datum to signal that polling occurred // but no data were returned. ignore any such null readings when observing. if (datum == null) { return(null); } // accumulate observed data by type for later analysis lock (_typeData) { Type datumType = datum.GetType(); if (!_typeData.TryGetValue(datumType, out List <IDatum> data)) { data = new List <IDatum>(); _typeData.Add(datumType, data); } data.Add(datum); UpdateObservedData(_typeData); } // run opportunistic observation and control if warranted ControlCompletionCheck opportunisticControlCompletionCheck = null; try { // the current method is called at high rates during normal operation (e.g., when observing the // accelerometer). we want to avoid flooding the local data store with an agent-state datum for // each call, so don't write them here. if (await TransitionToNewStateAsync(SensingAgentState.OpportunisticObservation, false, cancellationToken)) { if (ObservedDataMeetControlCriterion()) { opportunisticControlCompletionCheck = await BeginControlAsync(SensingAgentState.OpportunisticControl, cancellationToken); } else { await TransitionToNewStateAsync(SensingAgentState.Idle, false, cancellationToken); } } } catch (Exception ex) { await ReturnToIdle(cancellationToken); throw ex; } return(opportunisticControlCompletionCheck); }