2222import java .util .HashMap ;
2323import java .util .Map ;
2424import java .util .Objects ;
25+ import java .util .concurrent .ConcurrentHashMap ;
2526import java .util .concurrent .ExecutorService ;
2627import java .util .concurrent .Executors ;
2728
@@ -41,6 +42,10 @@ final class SensComWorker {
4142
4243 // map from device id to sensor.community id
4344 private final Map <AppDeviceId , String > sensComIds = new HashMap <>();
45+ // map with only *latest* measurement per AppDeviceId
46+ private final Map <AppDeviceId , SensorData > measurements = new ConcurrentHashMap <>();
47+ // map from senscom id -> last upload time
48+ private final Map <String , Instant > lastUploadTimes = new HashMap <>();
4449
4550 SensComWorker (ObjectMapper mapper , ISensComApi restClient , String softwareVersion , String appId ) {
4651 this .mapper = Objects .requireNonNull (mapper );
@@ -76,18 +81,49 @@ void stop() {
7681
7782 // schedules an upload to all pins
7883 void scheduleUpload (AppDeviceId appDeviceId , SensorData data ) {
79- executor .execute (new CatchingRunnable (LOG , () -> performUpload (appDeviceId , data )));
84+ // store measurement in cache of most-recent data and schedule upload
85+ SensorData oldData = measurements .put (appDeviceId , data );
86+ if (oldData != null ) {
87+ LOG .warn ("Replaced old sensor data for '{}'" , appDeviceId );
88+ }
89+ executor .execute (new CatchingRunnable (LOG , () -> performUpload (appDeviceId )));
8090 }
8191
8292 // uploads to all pins, runs on our executor
83- private void performUpload (AppDeviceId appDeviceId , SensorData data ) {
93+ private void performUpload (AppDeviceId appDeviceId ) {
94+ // check if we still have data to upload, or it was already sent
95+ SensorData data = measurements .remove (appDeviceId );
96+ if (data == null ) {
97+ return ;
98+ }
99+
84100 // look up custom sensor.community id
85101 String sensorId = sensComIds .getOrDefault (appDeviceId , "" );
86102 if (sensorId .isEmpty ()) {
87- // no sensor.community id found, so no upload
88103 return ;
89104 }
90105
106+ // drop stale data
107+ Duration age = Duration .between (data .getCreationTime (), Instant .now ());
108+ LOG .info ("Sensor data age {} before upload for {}" , age , appDeviceId );
109+ if (age .toSeconds () > 3600 ) {
110+ LOG .warn ("Dropped stale data for {}: {}" , appDeviceId , data );
111+ return ;
112+ }
113+
114+ // rate limit on sensor.community id
115+ Instant now = Instant .now ();
116+ Instant lastUpload = lastUploadTimes .get (sensorId );
117+ if (lastUpload != null ) {
118+ Duration interval = Duration .between (lastUpload , now );
119+ LOG .info ("Upload interval for {}: {}" , appDeviceId , interval );
120+ if (interval .toSeconds () < 60 ) {
121+ LOG .warn ("Dropped by rate limit: {}" , appDeviceId );
122+ return ;
123+ }
124+ }
125+ lastUploadTimes .put (sensorId , now );
126+
91127 // pin 1 (dust sensors)
92128 if (data .hasValue (ESensorItem .PM10 ) || data .hasValue (ESensorItem .PM2_5 ) || data .hasValue (ESensorItem .PM1_0 )
93129 || data .hasValue (ESensorItem .PM4_0 )) {
0 commit comments