@@ -48,10 +48,7 @@ function parseOIF411(xdata, pressure){
4848 // https://www.vaisala.com/sites/default/files/documents/Ozone%20Sounding%20with%20Vaisala%20Radiosonde%20RS41%20User%27s%20Guide%20M211486EN-C.pdf
4949 //
5050 // Sample data: 0501036402B958B07500 (length = 20 characters)
51- // More sample data: 0501R20234850000006EI (length = 21 characters)
52-
53- // Cast to string if not already
54- xdata = String ( xdata ) ;
51+ // More sample data: 0501R20234850000006EI (length = 21 characters)
5552
5653 // Run some checks over the input
5754 if ( xdata . length < 20 ) {
@@ -91,8 +88,6 @@ function parseOIF411(xdata, pressure){
9188
9289 // Version number
9390 _output [ 'oif411_version' ] = ( parseInt ( xdata . substr ( 16 , 4 ) , 16 ) / 100 ) . toFixed ( 2 ) ;
94-
95- return _output
9691 } else if ( xdata . length == 20 ) {
9792 // Measurement Data (Table 18)
9893 // Ozone pump temperature - signed int16
@@ -128,12 +123,9 @@ function parseOIF411(xdata, pressure){
128123
129124 _O3_partial_pressure = ( 4.30851e-4 ) * ( _output [ 'oif411_ozone_current_uA' ] - Ibg ) * ( _output [ 'oif411_ozone_pump_temp' ] + 273.15 ) * FlowRate * Cef ; // mPa
130125 _output [ 'oif411_O3_partial_pressure' ] = Math . round ( _O3_partial_pressure * 1000 ) / 1000 ; // 3 DP
126+ }
131127
132- return _output
133-
134- } else {
135- return { }
136- }
128+ return _output
137129}
138130
139131function parseCFH ( xdata ) {
@@ -145,9 +137,6 @@ function parseCFH(xdata) {
145137 //
146138 // Sample data: 0802E21FFD85C8CE078A0193 (length = 24 characters)
147139
148- // Cast to string if not already
149- xdata = String ( xdata ) ;
150-
151140 // Run some checks over the input
152141 if ( xdata . length != 24 ) {
153142 // Invalid CFH dataset
@@ -176,9 +165,6 @@ function parseCOBALD(xdata) {
176165 //
177166 // Sample data: 190213fffe005fcf00359943912cca (length = 30 characters)
178167
179- // Cast to string if not already
180- xdata = String ( xdata ) ;
181-
182168 // Run some checks over the input
183169 if ( xdata . length != 30 ) {
184170 // Invalid COBALD dataset
@@ -246,9 +232,6 @@ function parseSKYDEW(xdata) {
246232 //
247233 // Sample data: 3F0141DF73B940F600150F92FF27D5C8304102 (length = 38 characters)
248234
249- // Cast to string if not already
250- xdata = String ( xdata ) ;
251-
252235 // Run some checks over the input
253236 if ( xdata . length != 38 ) {
254237 // Invalid SKYDEW dataset
@@ -280,6 +263,183 @@ function parseSKYDEW(xdata) {
280263 return _output
281264}
282265
266+ function getPCFHdate ( code ) {
267+ // months reference list
268+ var PCFHmonths = [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" ] ;
269+
270+ // Get year from first character
271+ var year = parseInt ( code . charAt ( 0 ) , 16 ) ;
272+ year = year + 2016 ;
273+
274+ // Get month from second character
275+ var month = parseInt ( code . charAt ( 1 ) , 16 ) ;
276+ month = PCFHmonths [ month - 1 ] ;
277+
278+ // Generate string
279+ _part_date = month + " " + year ;
280+ return _part_date ;
281+ }
282+
283+ function parsePCFH ( xdata ) {
284+ // Attempt to parse an XDATA string from a Peltier Cooled Frost point Hygrometer (PCFH)
285+ // Returns an object with parameters to be added to the sondes telemetry.
286+ //
287+ // References:
288+ // Peltier Cooled Frost point Hygrometer (PCFH) Telemetry Interface PDF
289+ //
290+ // Sample data: 3c0101434a062c5cd4a5747b81486c93 (length = 32 characters)
291+ // 3c0103456076175ec5fc9df9b1 (length = 26 characters)
292+ // 3c0104a427104e203a9861a8ab6a65 (length = 30 characters)
293+ // 3c010000011b062221 (length = 18 characters)
294+
295+ // Run some checks over the input
296+ if ( xdata . length > 32 ) {
297+ // Invalid PCFH dataset
298+ return { } ;
299+ }
300+
301+ if ( xdata . substr ( 0 , 2 ) !== '3C' ) {
302+ // Not a PCFH (shouldn't get here)
303+ return { } ;
304+ }
305+
306+ var _output = { } ;
307+
308+ // Instrument number is common to all XDATA types.
309+ _output [ 'pcfh_instrument_number' ] = parseInt ( xdata . substr ( 2 , 2 ) , 16 ) ;
310+
311+ // Packet ID
312+ var packetID = xdata . substr ( 4 , 2 ) ;
313+
314+ // Packet type
315+ if ( packetID == "00" ) { // Individual instrument identification (10 s)
316+ // Serial number
317+ _output [ "pcfh_serial_number" ] = parseInt ( xdata . substr ( 6 , 4 ) ) ;
318+
319+ // Temperature PCB date
320+ _output [ "pcfh_temperature_pcb_date" ] = getPCFHdate ( xdata . substr ( 10 , 2 ) ) ;
321+
322+ // Main PCB date
323+ _output [ "pcfh_main_pcb_date" ] = getPCFHdate ( xdata . substr ( 12 , 2 ) ) ;
324+
325+ // Controller FW date
326+ _output [ "pcfh_controller_fw_date" ] = getPCFHdate ( xdata . substr ( 14 , 2 ) ) ;
327+
328+ // FPGA FW date
329+ _output [ "pcfh_fpga_fw_date" ] = getPCFHdate ( xdata . substr ( 16 , 2 ) ) ;
330+ } else if ( packetID == "01" || packetID == "02" ) { // Regular one second data, sub-sensor 1/2
331+ // Frost point mirror temperature
332+ _frost_point_mirror_temperature = parseInt ( xdata . substr ( 8 , 3 ) , 16 ) ;
333+ _frost_point_mirror_temperature = ( _frost_point_mirror_temperature * 0.05 ) - 125 ;
334+ _output [ 'pcfh_frost_point_mirror_temperature_' + packetID ] = Math . round ( _frost_point_mirror_temperature * 100 ) / 100 ; // 2 DP
335+
336+ // Peltier hot side temperature
337+ _peltier_hot_side_temperature = parseInt ( xdata . substr ( 11 , 3 ) , 16 ) ;
338+ _peltier_hot_side_temperature = ( _peltier_hot_side_temperature * 0.05 ) - 125 ;
339+ _output [ 'pcfh_peltier_hot_side_temperature_' + packetID ] = Math . round ( _peltier_hot_side_temperature * 100 ) / 100 ; // 2 DP
340+
341+ // Air temperature
342+ _air_temperature = parseInt ( xdata . substr ( 14 , 3 ) , 16 ) ;
343+ _air_temperature = ( _air_temperature * 0.05 ) - 125 ;
344+ _output [ 'pcfh_air_temperature_' + packetID ] = Math . round ( _air_temperature * 100 ) / 100 ; // 2 DP
345+
346+ // Anticipated frost point mirror temperature
347+ _anticipated_frost_point_mirror_temperature = parseInt ( xdata . substr ( 17 , 3 ) , 16 ) ;
348+ _anticipated_frost_point_mirror_temperature = ( _anticipated_frost_point_mirror_temperature * 0.05 ) - 125 ;
349+ _output [ 'pcfh_anticipated_frost_point_mirror_temperature_' + packetID ] = Math . round ( _anticipated_frost_point_mirror_temperature * 100 ) / 100 ; // 2 DP
350+
351+ // Frost point mirror reflectance
352+ _frost_point_mirror_reflectance = parseInt ( xdata . substr ( 20 , 4 ) , 16 ) ;
353+ _frost_point_mirror_reflectance = _frost_point_mirror_reflectance / 32768 ;
354+ _output [ 'pcfh_frost_point_mirror_reflectance_' + packetID ] = Math . round ( _frost_point_mirror_reflectance * 1000 ) / 1000 ; // 3 DP
355+
356+ // Reference surface reflectance
357+ _reference_surface_reflectance = parseInt ( xdata . substr ( 24 , 4 ) , 16 ) ;
358+ _reference_surface_reflectance = _reference_surface_reflectance / 32768 ;
359+ _output [ 'pcfh_reference_surface_reflectance_' + packetID ] = Math . round ( _reference_surface_reflectance * 1000 ) / 1000 ; // 3 DP
360+
361+ // Reference surface heating current
362+ _reference_surface_heating_current = parseInt ( xdata . substr ( 28 , 2 ) , 16 ) ;
363+ _reference_surface_heating_current = _reference_surface_heating_current / 2.56 ;
364+ _output [ 'pcfh_reference_surface_heating_current_' + packetID ] = Math . round ( _reference_surface_heating_current * 100 ) / 100 ; // 2 DP
365+
366+ // Peltier current
367+ _peltier_current = parseInt ( xdata . substr ( 30 , 2 ) , 16 ) ;
368+ if ( ( _peltier_current & 0x80 ) > 0 ) {
369+ _peltier_current = _peltier_current - 0x100 ;
370+ }
371+ _peltier_current = _peltier_current / 64 ;
372+ _output [ 'pcfh_peltier_current_' + packetID ] = Math . round ( _peltier_current * 1000 ) / 1000 ; // 3 DP
373+ } else if ( packetID == "03" ) { // Regular five second data
374+ // Heat sink temperature 1
375+ _heat_sink_temperature = parseInt ( xdata . substr ( 8 , 3 ) , 16 ) ;
376+ _heat_sink_temperature = ( _heat_sink_temperature * 0.05 ) - 125 ;
377+ _output [ 'pcfh_heat_sink_temperature_01' ] = Math . round ( _heat_sink_temperature * 100 ) / 100 ; // 2 DP
378+
379+ // Reference surface temperature 1
380+ _reference_surface_temperature = parseInt ( xdata . substr ( 11 , 3 ) , 16 ) ;
381+ _reference_surface_temperature = ( _reference_surface_temperature * 0.05 ) - 125 ;
382+ _output [ 'pcfh_reference_surface_temperature_01' ] = Math . round ( _reference_surface_temperature * 100 ) / 100 ; // 2 DP
383+
384+ // Heat sink temperature 2
385+ _heat_sink_temperature = parseInt ( xdata . substr ( 14 , 3 ) , 16 ) ;
386+ _heat_sink_temperature = ( _heat_sink_temperature * 0.05 ) - 125 ;
387+ _output [ 'pcfh_heat_sink_temperature_02' ] = Math . round ( _heat_sink_temperature * 100 ) / 100 ; // 2 DP
388+
389+ // Reference surface temperature 2
390+ _reference_surface_temperature = parseInt ( xdata . substr ( 17 , 3 ) , 16 ) ;
391+ _reference_surface_temperature = ( _reference_surface_temperature * 0.05 ) - 125 ;
392+ _output [ 'pcfh_reference_surface_temperature_02' ] = Math . round ( _reference_surface_temperature * 100 ) / 100 ; // 2 DP
393+
394+ // Thermocouple reference temperature
395+ _thermocouple_reference_temperature = parseInt ( xdata . substr ( 20 , 3 ) , 16 ) ;
396+ _thermocouple_reference_temperature = ( _thermocouple_reference_temperature * 0.05 ) - 125 ;
397+ _output [ 'pcfh_thermocouple_reference_temperature' ] = Math . round ( _thermocouple_reference_temperature * 100 ) / 100 ; // 2 DP
398+
399+ // Reserved temperature
400+ _reserved_temperature = parseInt ( xdata . substr ( 23 , 3 ) , 16 ) ;
401+ _reserved_temperature = ( _reserved_temperature * 0.05 ) - 125 ;
402+ _output [ 'pcfh_reserved_temperature' ] = Math . round ( _reserved_temperature * 100 ) / 100 ; // 2 DP
403+ } else if ( packetID == "04" ) { // Instrument status (10 s)
404+ // Clean frost point mirror reflectance 1
405+ _clean_frost_point_mirror_reflectance = parseInt ( xdata . substr ( 8 , 4 ) , 16 ) ;
406+ _clean_frost_point_mirror_reflectance = _clean_frost_point_mirror_reflectance * 0.001 ;
407+ _output [ 'pcfh_clean_frost_point_mirror_reflectance_01' ] = Math . round ( _clean_frost_point_mirror_reflectance * 1000 ) / 1000 ; // 3 DP
408+
409+ // Clean reference surface reflectance 1
410+ _clean_reference_surface_reflectance = parseInt ( xdata . substr ( 12 , 4 ) , 16 ) ;
411+ _clean_reference_surface_reflectance = _clean_reference_surface_reflectance * 0.001 ;
412+ _output [ 'pcfh_clean_reference_surface_reflectance_01' ] = Math . round ( _clean_reference_surface_reflectance * 1000 ) / 1000 ; // 3 DP
413+
414+ // Clean frost point mirror reflectance 2
415+ _clean_frost_point_mirror_reflectance = parseInt ( xdata . substr ( 16 , 4 ) , 16 ) ;
416+ _clean_frost_point_mirror_reflectance = _clean_frost_point_mirror_reflectance * 0.001 ;
417+ _output [ 'pcfh_clean_frost_point_mirror_reflectance_02' ] = Math . round ( _clean_frost_point_mirror_reflectance * 1000 ) / 1000 ; // 3 DP
418+
419+ // Clean reference surface reflectance 2
420+ _clean_reference_surface_reflectance = parseInt ( xdata . substr ( 20 , 4 ) , 16 ) ;
421+ _clean_reference_surface_reflectance = _clean_reference_surface_reflectance * 0.001 ;
422+ _output [ 'pcfh_clean_reference_surface_reflectance_02' ] = Math . round ( _clean_reference_surface_reflectance * 1000 ) / 1000 ; // 3 DP
423+
424+ // 6V Analog supply battery voltage
425+ _6v_analog_supply_battery_voltage = parseInt ( xdata . substr ( 24 , 2 ) , 16 ) ;
426+ _6v_analog_supply_battery_voltage = ( _6v_analog_supply_battery_voltage * 0.02 ) + 2.5 ;
427+ _output [ 'pcfh_6v_analog_supply_battery_voltage' ] = Math . round ( _6v_analog_supply_battery_voltage * 100 ) / 100 ; // 2 DP
428+
429+ // 4.5V Logic supply battery voltage
430+ _45v_logic_supply_battery_voltage = parseInt ( xdata . substr ( 26 , 2 ) , 16 ) ;
431+ _45v_logic_supply_battery_voltage = ( _45v_logic_supply_battery_voltage * 0.02 ) + 2.5 ;
432+ _output [ 'pcfh_45v_logic_supply_battery_voltage' ] = Math . round ( _45v_logic_supply_battery_voltage * 100 ) / 100 ; // 2 DP
433+
434+ // 4.5V Peltier and heater supply battery voltage
435+ _45v_peltier_and_heater_supply_battery_voltage = parseInt ( xdata . substr ( 28 , 2 ) , 16 ) ;
436+ _45v_peltier_and_heater_supply_battery_voltage = ( _45v_peltier_and_heater_supply_battery_voltage * 0.02 ) + 2.5 ;
437+ _output [ 'pcfh_45v_peltier_and_heater_supply_battery_voltage' ] = Math . round ( _45v_peltier_and_heater_supply_battery_voltage * 100 ) / 100 ; // 2 DP
438+ }
439+
440+ return _output
441+ }
442+
283443function parseXDATA ( data , pressure ) {
284444 // Accept an XDATA string, or multiple XDATA entries, delimited by '#'
285445 // Attempt to parse each one, and return an object
@@ -302,6 +462,8 @@ function parseXDATA(data, pressure){
302462 for ( xdata_i = 0 ; xdata_i < data_split . length ; xdata_i ++ ) {
303463 _current_xdata = data_split [ xdata_i ] ;
304464
465+ _current_xdata = String ( _current_xdata ) . toUpperCase ( ) ;
466+
305467 // Get Instrument ID
306468 // https://gml.noaa.gov/aftp/user/jordan/XDATA%20Instrument%20ID%20Allocation.pdf
307469 // https://www.gruan.org/gruan/editor/documents/gruan/GRUAN-TN-11_GruanToolRs92_v1.0_2020-10-01.pdf
@@ -341,11 +503,8 @@ function parseXDATA(data, pressure){
341503 if ( ! _instruments . includes ( "OPC" ) ) _instruments . push ( 'OPC' ) ;
342504 } else if ( _instrument === '3C' ) {
343505 // PCFH
344- // SRNO, H0, H1, F0, F1
345- // 3c010000184b4b5754
346- // 3c0103ce7b58647a98748befff
347- // 3c010148719fff8e54b9af627e249fe0
348- // 3c01028d696fff8db4b7865980cdbbb3
506+ _xdata_temp = parsePCFH ( _current_xdata ) ;
507+ _output = Object . assign ( _output , _xdata_temp ) ;
349508 if ( ! _instruments . includes ( "PCFH" ) ) _instruments . push ( 'PCFH' ) ;
350509 } else if ( _instrument === '3D' ) {
351510 // FLASH-B
0 commit comments