66__all__ = [
77 'parse_comment_telemetry' ,
88 'parse_telemetry_config' ,
9+ 'parse_telemetry_report' ,
910 ]
1011
1112
@@ -85,9 +86,11 @@ def parse_telemetry_config(body):
8586 't%s' % form : teqns
8687 })
8788 elif form == "BITS" :
88- match = re .findall (r"^([01]{8}),(.{0,23})$" , body .rstrip ())
89+ # APRS spec says 23 chars, but real-world packets may be longer
90+ # Accept any reasonable length (up to 100 chars to be safe)
91+ match = re .findall (r"^([01]{8}),(.{0,100})$" , body .rstrip ())
8992 if not match :
90- raise ParseError ("incorrect format of %s (title too long?) " % form )
93+ raise ParseError ("incorrect format of %s" % form )
9194
9295 bits , title = match [0 ]
9396
@@ -98,3 +101,111 @@ def parse_telemetry_config(body):
98101
99102 return (body , parsed )
100103
104+
105+ def parse_telemetry_report (body ):
106+ """
107+ Parses APRS 1.2 telemetry report format: T#sss,aaa,bbb,ccc,ddd,eee,bbbbbbbb,comment
108+
109+ Format:
110+ - T# indicates telemetry report
111+ - sss is sequence number (000-999)
112+ - aaa to eee are 5 analog values (000-999)
113+ - bbbbbbbb is 8 binary digits (digital I/O)
114+ - comment is optional text
115+
116+ Returns (remaining_body, parsed_dict)
117+ """
118+ parsed = {}
119+
120+ # Check if body starts with '#'
121+ if not body .startswith ('#' ):
122+ raise ParseError ("telemetry report must start with '#'" )
123+
124+ # Remove the '#' prefix
125+ body = body [1 :]
126+
127+ # Split by comma - need at least sequence number
128+ # Some real-world packets are incomplete (missing analog values or digital I/O)
129+ parts = body .split (',' , 7 )
130+
131+ if len (parts ) < 1 :
132+ raise ParseError ("telemetry report must have at least a sequence number" )
133+
134+ seq_str = parts [0 ]
135+ # Extract analog values (up to 5, pad with empty strings if missing)
136+ analog_strs = parts [1 :6 ] if len (parts ) > 1 else []
137+ # Pad to 5 analog values if we have fewer
138+ while len (analog_strs ) < 5 :
139+ analog_strs .append ('' )
140+
141+ # Digital I/O field (may be missing)
142+ digital_field = parts [6 ] if len (parts ) > 6 else '00000000'
143+ comment = parts [7 ] if len (parts ) > 7 else ''
144+
145+ # Validate and parse sequence number (allow any positive integer)
146+ # APRS spec says 000-999, but real-world packets use larger numbers
147+ if not re .match (r'^\d+$' , seq_str ):
148+ raise ParseError ("telemetry sequence number must be numeric" )
149+ seq = int (seq_str )
150+
151+ # Parse analog values (can be 000-999, allow decimals and negatives per APRS 1.2)
152+ # Empty values are allowed and treated as 0
153+ analog_vals = []
154+ for i , val_str in enumerate (analog_strs ):
155+ # Allow empty values (treated as 0)
156+ if not val_str or val_str .strip () == '' :
157+ analog_vals .append (0.0 )
158+ continue
159+
160+ # Allow integers, decimals, and negative numbers
161+ if not re .match (r'^-?\d+\.?\d*$' , val_str ):
162+ raise ParseError ("telemetry analog value %d has invalid format" % (i + 1 ))
163+ try :
164+ val = float (val_str )
165+ except ValueError :
166+ raise ParseError ("telemetry analog value %d is not a valid number" % (i + 1 ))
167+ analog_vals .append (val )
168+
169+ # Validate digital I/O (must be binary digits, pad to 8 if shorter)
170+ # Some packets have comment concatenated without comma separator
171+ # Some packets have shorter binary strings (pad with leading zeros)
172+ # Check if field is entirely binary digits
173+ if re .match (r'^[01]+$' , digital_field ):
174+ # Pure binary string (all 0s and 1s)
175+ if len (digital_field ) < 8 :
176+ # Pad shorter binary strings to 8 digits
177+ digital_str = digital_field .zfill (8 )
178+ elif len (digital_field ) == 8 :
179+ digital_str = digital_field
180+ else :
181+ # Longer than 8, use first 8
182+ digital_str = digital_field [:8 ]
183+ elif re .match (r'^[01]{8,}[^01]' , digital_field ):
184+ # Starts with 8+ binary digits followed by non-binary (concatenated comment)
185+ digital_str = digital_field [:8 ]
186+ if not comment :
187+ comment = digital_field [8 :]
188+ elif re .match (r'^[01]{1,7}[^01]' , digital_field ):
189+ # Starts with 1-7 binary digits followed by non-binary
190+ # This is invalid - need at least 8 binary digits before comment
191+ raise ParseError ("telemetry digital I/O must be binary digits" )
192+ else :
193+ # No valid binary digits found or invalid format
194+ raise ParseError ("telemetry digital I/O must be binary digits" )
195+
196+ parsed .update ({
197+ 'format' : 'telemetry' ,
198+ 'telemetry' : {
199+ 'seq' : seq ,
200+ 'vals' : analog_vals ,
201+ 'bits' : digital_str
202+ }
203+ })
204+
205+ # Add comment if present
206+ if comment :
207+ parsed ['comment' ] = comment .strip (' ' )
208+
209+ # Return empty remaining body since we consumed everything
210+ return ('' , parsed )
211+
0 commit comments