@@ -180,6 +180,14 @@ private static async Task<int> RunStreamingSessionAsync(CliOptions options)
180180 }
181181
182182 var messageCount = 0 ;
183+
184+ // The device sends analog and digital data in separate protobuf
185+ // messages that share the same timestamp. We buffer the pending
186+ // analog message and merge it with the subsequent digital message
187+ // before writing a single combined output row.
188+ DaqifiOutMessage ? pendingAnalog = null ;
189+ var pendingLock = new object ( ) ;
190+
183191 device . MessageReceived += ( _ , eventArgs ) =>
184192 {
185193 if ( stopCts . IsCancellationRequested )
@@ -192,24 +200,50 @@ private static async Task<int> RunStreamingSessionAsync(CliOptions options)
192200 return ;
193201 }
194202
195- var currentCount = Interlocked . Increment ( ref messageCount ) ;
196- if ( options . MessageLimit > 0 && currentCount > options . MessageLimit )
203+ if ( options . ShowStatusMessages && ProtobufProtocolHandler . DetectMessageType ( message ) == ProtobufMessageType . Status )
197204 {
205+ WriteStatusSummary ( outputWriter , message ) ;
198206 return ;
199207 }
200208
201- if ( IsStreamLikeMessage ( message ) )
209+ if ( ! IsStreamLikeMessage ( message ) )
202210 {
203- WriteStreamSample ( outputWriter , message , options . OutputFormat ) ;
204- }
205- else if ( options . ShowStatusMessages && ProtobufProtocolHandler . DetectMessageType ( message ) == ProtobufMessageType . Status )
206- {
207- WriteStatusSummary ( outputWriter , message ) ;
211+ return ;
208212 }
209213
210- if ( options . MessageLimit > 0 && currentCount >= options . MessageLimit )
214+ lock ( pendingLock )
211215 {
212- stopCts . Cancel ( ) ;
216+ var hasAnalog = message . AnalogInData . Count > 0 || message . AnalogInDataFloat . Count > 0 ;
217+ var hasDigital = message . DigitalData . Length > 0 ;
218+
219+ if ( hasAnalog && ! hasDigital )
220+ {
221+ // Flush any stale pending message before buffering the new one
222+ if ( pendingAnalog != null )
223+ {
224+ WriteMergedSample ( outputWriter , pendingAnalog , null , options . OutputFormat , ref messageCount , options . MessageLimit , stopCts ) ;
225+ }
226+
227+ pendingAnalog = message ;
228+ return ;
229+ }
230+
231+ if ( hasDigital && pendingAnalog != null && pendingAnalog . MsgTimeStamp == message . MsgTimeStamp )
232+ {
233+ // Matching pair — merge and write
234+ WriteMergedSample ( outputWriter , pendingAnalog , message , options . OutputFormat , ref messageCount , options . MessageLimit , stopCts ) ;
235+ pendingAnalog = null ;
236+ return ;
237+ }
238+
239+ // Digital-only with no matching analog, or timestamp mismatch
240+ if ( pendingAnalog != null )
241+ {
242+ WriteMergedSample ( outputWriter , pendingAnalog , null , options . OutputFormat , ref messageCount , options . MessageLimit , stopCts ) ;
243+ pendingAnalog = null ;
244+ }
245+
246+ WriteMergedSample ( outputWriter , message , null , options . OutputFormat , ref messageCount , options . MessageLimit , stopCts ) ;
213247 }
214248 } ;
215249
@@ -249,6 +283,16 @@ private static async Task<int> RunStreamingSessionAsync(CliOptions options)
249283 device . Send ( ScpiMessageProducer . StopStreaming ) ;
250284 Console . WriteLine ( "Streaming stopped." ) ;
251285
286+ // Flush any buffered analog-only message that never got a matching digital
287+ lock ( pendingLock )
288+ {
289+ if ( pendingAnalog != null )
290+ {
291+ WriteMergedSample ( outputWriter , pendingAnalog , null , options . OutputFormat , ref messageCount , options . MessageLimit , stopCts ) ;
292+ pendingAnalog = null ;
293+ }
294+ }
295+
252296 if ( options . MinSamples > 0 && messageCount < options . MinSamples )
253297 {
254298 Console . Error . WriteLine (
@@ -663,8 +707,39 @@ private static async Task<int> RunSdCardOperationAsync(CliOptions options)
663707 {
664708 streamingDevice . StreamingFrequency = options . SampleRate ;
665709
710+ // Enable channels before starting SD card logging. Core's
711+ // StartSdCardLoggingAsync only forwards the channel mask — it does
712+ // not enable channels itself. Without an explicit mask we enable all
713+ // ADC channels (the device reports AnalogInputChannels in its
714+ // capabilities after InitializeAsync) and DIO ports so the log
715+ // file is not empty.
716+ var channelMask = options . ChannelMask ;
717+ if ( ! string . IsNullOrWhiteSpace ( channelMask ) && ! IsValidChannelMask ( channelMask ) )
718+ {
719+ Console . Error . WriteLine ( $ "Invalid channel mask: { channelMask } ") ;
720+ return 1 ;
721+ }
722+
723+ if ( string . IsNullOrWhiteSpace ( channelMask ) )
724+ {
725+ var adcCount = streamingDevice . Metadata . Capabilities . AnalogInputChannels ;
726+ if ( adcCount > 0 )
727+ {
728+ channelMask = new string ( '1' , adcCount ) ;
729+ }
730+ }
731+
732+ if ( ! string . IsNullOrWhiteSpace ( channelMask ) )
733+ {
734+ streamingDevice . Send ( ScpiMessageProducer . EnableAdcChannels ( channelMask ) ) ;
735+ await Task . Delay ( 100 ) ;
736+ }
737+
738+ streamingDevice . Send ( ScpiMessageProducer . EnableDioPorts ( ) ) ;
739+ await Task . Delay ( 100 ) ;
740+
666741 await streamingDevice . StartSdCardLoggingAsync (
667- channelMask : options . ChannelMask ,
742+ channelMask : channelMask ,
668743 format : options . SdLogFormat ) ;
669744 Console . WriteLine ( "SD card logging started." ) ;
670745
@@ -1071,20 +1146,38 @@ private static bool IsStreamLikeMessage(DaqifiOutMessage message)
10711146 message . DigitalData . Length > 0 ;
10721147 }
10731148
1074- private static void WriteStreamSample ( TextWriter writer , DaqifiOutMessage message , OutputFormat format )
1149+ private static void WriteMergedSample (
1150+ TextWriter writer ,
1151+ DaqifiOutMessage analogMessage ,
1152+ DaqifiOutMessage ? digitalMessage ,
1153+ OutputFormat format ,
1154+ ref int messageCount ,
1155+ int messageLimit ,
1156+ CancellationTokenSource stopCts )
10751157 {
1158+ var currentCount = Interlocked . Increment ( ref messageCount ) ;
1159+ if ( messageLimit > 0 && currentCount > messageLimit )
1160+ {
1161+ return ;
1162+ }
1163+
10761164 switch ( format )
10771165 {
10781166 case OutputFormat . Jsonl :
1079- writer . WriteLine ( ToJsonLine ( message ) ) ;
1167+ writer . WriteLine ( ToJsonLine ( analogMessage , digitalMessage ) ) ;
10801168 break ;
10811169 case OutputFormat . Csv :
1082- writer . WriteLine ( ToCsvLine ( message ) ) ;
1170+ writer . WriteLine ( ToCsvLine ( analogMessage , digitalMessage ) ) ;
10831171 break ;
10841172 default :
1085- writer . WriteLine ( ToTextLine ( message ) ) ;
1173+ writer . WriteLine ( ToTextLine ( analogMessage , digitalMessage ) ) ;
10861174 break ;
10871175 }
1176+
1177+ if ( messageLimit > 0 && currentCount >= messageLimit )
1178+ {
1179+ stopCts . Cancel ( ) ;
1180+ }
10881181 }
10891182
10901183 private static void WriteStatusSummary ( TextWriter writer , DaqifiOutMessage message )
@@ -1094,19 +1187,19 @@ private static void WriteStatusSummary(TextWriter writer, DaqifiOutMessage messa
10941187 $ "fw={ message . DeviceFwRev ?? "unknown" } sn={ message . DeviceSn } ") ;
10951188 }
10961189
1097- private static string ToTextLine ( DaqifiOutMessage message )
1190+ private static string ToTextLine ( DaqifiOutMessage analogMsg , DaqifiOutMessage ? digitalMsg )
10981191 {
10991192 var builder = new StringBuilder ( ) ;
1100- if ( message . MsgTimeStamp != 0 )
1193+ if ( analogMsg . MsgTimeStamp != 0 )
11011194 {
11021195 builder . Append ( "ts=" ) ;
1103- builder . Append ( message . MsgTimeStamp . ToString ( CultureInfo . InvariantCulture ) ) ;
1196+ builder . Append ( analogMsg . MsgTimeStamp . ToString ( CultureInfo . InvariantCulture ) ) ;
11041197 builder . Append ( ' ' ) ;
11051198 }
11061199
1107- var analogValues = message . AnalogInDataFloat . Count > 0
1108- ? message . AnalogInDataFloat . Select ( value => value . ToString ( "F3" , CultureInfo . InvariantCulture ) ) . ToList ( )
1109- : message . AnalogInData . Select ( value => value . ToString ( CultureInfo . InvariantCulture ) ) . ToList ( ) ;
1200+ var analogValues = analogMsg . AnalogInDataFloat . Count > 0
1201+ ? analogMsg . AnalogInDataFloat . Select ( value => value . ToString ( "F3" , CultureInfo . InvariantCulture ) ) . ToList ( )
1202+ : analogMsg . AnalogInData . Select ( value => value . ToString ( CultureInfo . InvariantCulture ) ) . ToList ( ) ;
11101203
11111204 if ( analogValues . Count > 0 )
11121205 {
@@ -1119,43 +1212,48 @@ private static string ToTextLine(DaqifiOutMessage message)
11191212 builder . Append ( ']' ) ;
11201213 }
11211214
1122- if ( message . DigitalData . Length > 0 )
1215+ // Prefer the paired digital message; fall back to analog message's own digital data
1216+ var digitalSource = digitalMsg ?? analogMsg ;
1217+ if ( digitalSource . DigitalData . Length > 0 )
11231218 {
1124- var digital = BitConverter . ToString ( message . DigitalData . ToByteArray ( ) ) ;
1219+ var digital = BitConverter . ToString ( digitalSource . DigitalData . ToByteArray ( ) ) ;
11251220 builder . Append ( " digital=" ) ;
11261221 builder . Append ( digital ) ;
11271222 }
11281223
11291224 return builder . ToString ( ) ;
11301225 }
11311226
1132- private static string ToCsvLine ( DaqifiOutMessage message )
1227+ private static string ToCsvLine ( DaqifiOutMessage analogMsg , DaqifiOutMessage ? digitalMsg )
11331228 {
1134- var analogValues = message . AnalogInDataFloat . Count > 0
1135- ? message . AnalogInDataFloat . Select ( value => value . ToString ( "F6" , CultureInfo . InvariantCulture ) ) . ToList ( )
1136- : message . AnalogInData . Select ( value => value . ToString ( CultureInfo . InvariantCulture ) ) . ToList ( ) ;
1229+ var analogValues = analogMsg . AnalogInDataFloat . Count > 0
1230+ ? analogMsg . AnalogInDataFloat . Select ( value => value . ToString ( "F6" , CultureInfo . InvariantCulture ) ) . ToList ( )
1231+ : analogMsg . AnalogInData . Select ( value => value . ToString ( CultureInfo . InvariantCulture ) ) . ToList ( ) ;
11371232
1138- var timestamp = message . MsgTimeStamp . ToString ( CultureInfo . InvariantCulture ) ;
1233+ var timestamp = analogMsg . MsgTimeStamp . ToString ( CultureInfo . InvariantCulture ) ;
11391234 var analog = string . Join ( "," , analogValues ) ;
1140- var digital = message . DigitalData . Length > 0
1141- ? BitConverter . ToString ( message . DigitalData . ToByteArray ( ) )
1235+
1236+ var digitalSource = digitalMsg ?? analogMsg ;
1237+ var digital = digitalSource . DigitalData . Length > 0
1238+ ? BitConverter . ToString ( digitalSource . DigitalData . ToByteArray ( ) )
11421239 : string . Empty ;
11431240
11441241 return $ "{ timestamp } ,{ analog } ,{ digital } ";
11451242 }
11461243
1147- private static string ToJsonLine ( DaqifiOutMessage message )
1244+ private static string ToJsonLine ( DaqifiOutMessage analogMsg , DaqifiOutMessage ? digitalMsg )
11481245 {
1149- var analogValues = message . AnalogInDataFloat . Count > 0
1150- ? message . AnalogInDataFloat . Select ( value => value . ToString ( "F6" , CultureInfo . InvariantCulture ) ) . ToList ( )
1151- : message . AnalogInData . Select ( value => value . ToString ( CultureInfo . InvariantCulture ) ) . ToList ( ) ;
1246+ var analogValues = analogMsg . AnalogInDataFloat . Count > 0
1247+ ? analogMsg . AnalogInDataFloat . Select ( value => value . ToString ( "F6" , CultureInfo . InvariantCulture ) ) . ToList ( )
1248+ : analogMsg . AnalogInData . Select ( value => value . ToString ( CultureInfo . InvariantCulture ) ) . ToList ( ) ;
11521249
1153- var digitalBytes = message . DigitalData . Length > 0
1154- ? BitConverter . ToString ( message . DigitalData . ToByteArray ( ) )
1250+ var digitalSource = digitalMsg ?? analogMsg ;
1251+ var digitalBytes = digitalSource . DigitalData . Length > 0
1252+ ? BitConverter . ToString ( digitalSource . DigitalData . ToByteArray ( ) )
11551253 : string . Empty ;
11561254
11571255 return "{" +
1158- $ "\" ts\" :{ message . MsgTimeStamp . ToString ( CultureInfo . InvariantCulture ) } ," +
1256+ $ "\" ts\" :{ analogMsg . MsgTimeStamp . ToString ( CultureInfo . InvariantCulture ) } ," +
11591257 $ "\" analog\" :[{ string . Join ( "," , analogValues ) } ]," +
11601258 $ "\" digital\" :\" { digitalBytes } \" " +
11611259 "}" ;
0 commit comments