@@ -6741,27 +6741,46 @@ class VwWeconnect extends utils.Adapter {
67416741 /**
67426742 * Adaptive poll for one VIN: fetch, then schedule the next call based on
67436743 * when the portal is expected to publish the next 15-min dataset.
6744+ *
6745+ * Uses a per-VIN in-flight flag so the manual refresh button (or rapid
6746+ * external scheduling calls) cannot start a second concurrent fetch — the
6747+ * second invocation is deferred until the running one finishes.
67446748 */
67456749 _scheduleEuDataActPoll ( vin , delayMs ) {
67466750 if ( ! this . euDataActTimers ) this . euDataActTimers = { } ;
6751+ if ( ! this . euDataActInFlight ) this . euDataActInFlight = { } ;
67476752 if ( this . euDataActTimers [ vin ] ) {
67486753 clearTimeout ( this . euDataActTimers [ vin ] ) ;
67496754 }
67506755 this . euDataActTimers [ vin ] = setTimeout ( async ( ) => {
6751- const next = await this . getEuDataActStatus ( vin )
6752- . catch ( ( err ) => {
6753- this . log . error ( `EU Data Act status for ${ vin } failed: ${ err . message || err } ` ) ;
6754- return null ;
6755- } ) ;
6756- // If the fetch didn't return a target time (NO_CONTENT, error,
6757- // metadata still missing), fall back to a 1-min retry so we pick up
6758- // the very next drop without waiting a full interval.
6756+ // Drop the timer handle now that the callback has been entered, so
6757+ // onUnload's cleanup loop doesn't try to clear an already-fired timer.
6758+ delete this . euDataActTimers [ vin ] ;
6759+ if ( this . euDataActInFlight [ vin ] ) {
6760+ // A previous fetch is still running (likely because the refresh
6761+ // button fired during a slow request). Re-arm in 5s and let the
6762+ // running call complete its own re-schedule.
6763+ this . log . debug ( `EU Data Act: ${ vin } fetch already in flight, deferring` ) ;
6764+ this . _scheduleEuDataActPoll ( vin , 5000 ) ;
6765+ return ;
6766+ }
6767+ this . euDataActInFlight [ vin ] = true ;
6768+ let next = null ;
6769+ try {
6770+ next = await this . getEuDataActStatus ( vin ) ;
6771+ } catch ( err ) {
6772+ this . log . error ( `EU Data Act status for ${ vin } failed: ${ err . message || err } ` ) ;
6773+ } finally {
6774+ this . euDataActInFlight [ vin ] = false ;
6775+ }
6776+ // 1-min fallback covers NO_CONTENT, hard error, missing metadata.
67596777 const fallbackMs = 60 * 1000 ;
6760- const userMaxMs = Math . max ( 60 , ( this . config . interval || 15 ) * 60 ) * 1000 ;
67616778 let nextMs = typeof next === "number" && next > 0 ? next : fallbackMs ;
6762- // Never poll faster than 30s and never slower than the user-configured
6763- // ceiling — the latter caps schedule drift if createdOn is way off.
6764- nextMs = Math . max ( 30 * 1000 , Math . min ( nextMs , userMaxMs ) ) ;
6779+ // Floor: never poll faster than 30s. No ceiling — the portal publishes
6780+ // exactly every 15 min, so any cap below that hammers the API for no
6781+ // benefit. config.interval is intentionally NOT used as a clamp on the
6782+ // success path; it would silently break the drop-locked schedule.
6783+ nextMs = Math . max ( 30 * 1000 , nextMs ) ;
67656784 this . log . debug ( `EU Data Act: ${ vin } next poll in ${ Math . round ( nextMs / 1000 ) } s` ) ;
67666785 this . _scheduleEuDataActPoll ( vin , nextMs ) ;
67676786 } , delayMs ) ;
@@ -6864,13 +6883,28 @@ class VwWeconnect extends utils.Adapter {
68646883 if ( result . datasetCreatedOn ) {
68656884 payload . _dataset_created_on = result . datasetCreatedOn ;
68666885 }
6867- if ( this . config . rawJson ) {
6868- payload . _raw_json = JSON . stringify ( result . raw ) ;
6869- }
68706886 await this . json2iob . parse ( vin + ".statuseudata" , payload , {
68716887 forceIndex : true ,
68726888 channelName : "EU Data Act 15-min dataset" ,
68736889 } ) ;
6890+ // The optional rawJson dump goes through extendObject/setState directly
6891+ // with role=json so it (a) gets the right role for the admin UI and
6892+ // (b) is easy to exclude from history adapters — at hundreds of KB
6893+ // every 15 min this is otherwise a serious DB write amplifier.
6894+ if ( this . config . rawJson ) {
6895+ await this . extendObjectAsync ( vin + ".statuseudata.rawJson" , {
6896+ type : "state" ,
6897+ common : {
6898+ name : "Raw EU Data Act dataset JSON" ,
6899+ role : "json" ,
6900+ type : "string" ,
6901+ read : true ,
6902+ write : false ,
6903+ } ,
6904+ native : { } ,
6905+ } ) ;
6906+ await this . setStateAsync ( vin + ".statuseudata.rawJson" , JSON . stringify ( result . raw ) , true ) ;
6907+ }
68746908 // Drop-locked rescheduling: the portal publishes one dataset per
68756909 // 15-min slot, timestamped in createdOn. Aim for createdOn + 15min +
68766910 // 45s buffer so we hit the new file as soon as it's provisioned. If
@@ -6924,6 +6958,7 @@ class VwWeconnect extends utils.Adapter {
69246958 for ( const t of Object . values ( this . euDataActTimers ) ) clearTimeout ( t ) ;
69256959 this . euDataActTimers = { } ;
69266960 }
6961+ this . euDataActInFlight = { } ;
69276962 this . mqttClient && this . mqttClient . end ( ) ;
69286963 callback ( ) ;
69296964 } catch ( e ) {
0 commit comments