@@ -109,6 +109,22 @@ class AirGradientPlatform implements DynamicPlatformPlugin {
109109 }
110110}
111111
112+ function isAirGradientData ( x : unknown ) : x is AirGradientData {
113+ if ( x === null || typeof x !== 'object' ) {
114+ return false ;
115+ }
116+ const o = x as Record < string , unknown > ;
117+
118+ // Minimal required fields you actually rely on elsewhere
119+ return (
120+ typeof o . pm02 === 'number' &&
121+ typeof o . pm10 === 'number' &&
122+ typeof o . rco2 === 'number' &&
123+ typeof o . atmp === 'number' &&
124+ typeof o . rhum === 'number'
125+ ) ;
126+ }
127+
112128class AirGradientSensor {
113129 private readonly platform : AirGradientPlatform ;
114130 private readonly accessory : PlatformAccessory ;
@@ -200,18 +216,51 @@ class AirGradientSensor {
200216
201217 private async fetchData ( ) {
202218 try {
203- const response = await axios . get ( this . apiUrl ) ;
204- this . data = response . data ;
219+ // Strongly type the expected payload
220+ const response = await axios . get < AirGradientData > ( this . apiUrl , {
221+ timeout : 5000 , // optional: avoid hanging forever
222+ headers : { 'Accept' : 'application/json' } ,
223+ // validateStatus: (s) => s >= 200 && s < 400, // optional: treat 3xx as ok if your devices redirect
224+ } ) ;
225+
226+ const payload = response . data ;
227+
228+ // Runtime validation: ensures critical numeric fields exist
229+ if ( ! isAirGradientData ( payload ) ) {
230+ this . log . error ( 'AirGradient API returned unexpected data format:' , payload ) ;
231+ return ; // keep previous this.data (so we don’t overwrite with bad data)
232+ }
233+
234+ // All good—commit and log
235+ this . data = payload ;
205236 this . log . info ( 'Data fetched successfully:' , this . data ) ;
206237
207- // Log the full response for debugging
238+ // Optional extra debug
208239 this . log . debug ( 'API response:' , this . data ) ;
209- } catch ( error ) {
210- this . log . error ( 'Error fetching data from AirGradient API:' , error ) ;
211- throw error ;
240+
241+ } catch ( err ) {
242+ // Make axios/network errors readable without losing detail
243+ const e = err as unknown ;
244+ if ( axios . isAxiosError ( e ) ) {
245+ this . log . error (
246+ `Axios error fetching AirGradient data: ${ e . message } ` +
247+ ( e . response ? ` (status ${ e . response . status } )` : '' ) +
248+ ( e . code ? ` [code ${ e . code } ]` : '' ) ,
249+ ) ;
250+ if ( e . response ?. data ) {
251+ this . log . debug ( 'Error response body:' , e . response . data ) ;
252+ }
253+ } else if ( e instanceof Error ) {
254+ this . log . error ( 'Error fetching data from AirGradient API:' , e . message ) ;
255+ this . log . debug ( e . stack || 'no stack' ) ;
256+ } else {
257+ this . log . error ( 'Unknown error fetching data from AirGradient API:' , e ) ;
258+ }
259+ throw err ; // keep existing control flow in updateData()
212260 }
213261 }
214262
263+
215264 private async updateData ( ) {
216265 try {
217266 await this . fetchData ( ) ;
0 commit comments