15
15
import logging
16
16
from copy import deepcopy
17
17
from inspect import signature
18
+ from logging import Logger
18
19
from time import sleep
19
20
20
21
import paho .mqtt .client as paho
@@ -163,79 +164,80 @@ def get(self):
163
164
164
165
class RateLimit :
165
166
def __init__ (self , rate_limit , name = None , percentage = 80 ):
166
- self .__no_limit = False
167
- self .__rate_limit_dict = {}
167
+ self ._no_limit = False
168
+ self ._rate_limit_dict = {}
168
169
self .__lock = RLock ()
169
- self .__minimal_timeout = DEFAULT_TIMEOUT
170
- self .__minimal_limit = 1000000000
170
+ self ._minimal_timeout = DEFAULT_TIMEOUT
171
+ self ._minimal_limit = 1000000000
171
172
from_dict = isinstance (rate_limit , dict )
172
173
if from_dict :
173
- self .__rate_limit_dict = rate_limit .get ('rateLimit' , rate_limit )
174
+ self ._rate_limit_dict = rate_limit .get ('rateLimit' , rate_limit )
174
175
name = rate_limit .get ('name' , name )
175
176
percentage = rate_limit .get ('percentage' , percentage )
176
- self .no_limit = rate_limit .get ('no_limit' , False )
177
+ self ._no_limit = rate_limit .get ('no_limit' , False )
177
178
self .name = name
178
179
self .percentage = percentage
179
180
self .__start_time = int (monotonic ())
180
181
if not from_dict :
181
182
if '' .join (c for c in rate_limit if c not in [' ' , ',' , ';' ]) in ("" , "0:0" ):
182
- self .__no_limit = True
183
+ self ._no_limit = True
184
+ return
183
185
rate_configs = rate_limit .split (";" )
184
186
if "," in rate_limit :
185
187
rate_configs = rate_limit .split ("," )
186
188
for rate in rate_configs :
187
189
if rate == "" :
188
190
continue
189
191
rate = rate .split (":" )
190
- self .__rate_limit_dict [int (rate [1 ])] = {"counter" : 0 ,
192
+ self ._rate_limit_dict [int (rate [1 ])] = {"counter" : 0 ,
191
193
"start" : int (monotonic ()),
192
194
"limit" : int (int (rate [0 ]) * self .percentage / 100 )}
193
195
log .debug ("Rate limit %s set to values: " % self .name )
194
196
with self .__lock :
195
- if not self .__no_limit :
196
- for rate_limit_time in self .__rate_limit_dict :
197
+ if not self ._no_limit :
198
+ for rate_limit_time in self ._rate_limit_dict :
197
199
log .debug ("Time: %s, Limit: %s" , rate_limit_time ,
198
- self .__rate_limit_dict [rate_limit_time ]["limit" ])
199
- if self .__rate_limit_dict [rate_limit_time ]["limit" ] < self .__minimal_limit :
200
- self .__minimal_limit = self .__rate_limit_dict [rate_limit_time ]["limit" ]
201
- if rate_limit_time < self .__minimal_limit :
202
- self .__minimal_timeout = rate_limit_time + 1
200
+ self ._rate_limit_dict [rate_limit_time ]["limit" ])
201
+ if self ._rate_limit_dict [rate_limit_time ]["limit" ] < self ._minimal_limit :
202
+ self ._minimal_limit = self ._rate_limit_dict [rate_limit_time ]["limit" ]
203
+ if rate_limit_time < self ._minimal_limit :
204
+ self ._minimal_timeout = rate_limit_time + 1
203
205
else :
204
206
log .debug ("No rate limits." )
205
207
206
208
def increase_rate_limit_counter (self , amount = 1 ):
207
- if self .__no_limit :
209
+ if self ._no_limit :
208
210
return
209
211
with self .__lock :
210
- for rate_limit_time in self .__rate_limit_dict :
211
- self .__rate_limit_dict [rate_limit_time ]["counter" ] += amount
212
+ for rate_limit_time in self ._rate_limit_dict :
213
+ self ._rate_limit_dict [rate_limit_time ]["counter" ] += amount
212
214
213
215
def check_limit_reached (self , amount = 1 ):
214
- if self .__no_limit :
216
+ if self ._no_limit :
215
217
return False
216
218
with self .__lock :
217
219
current_time = int (monotonic ())
218
- for rate_limit_time , rate_limit_info in self .__rate_limit_dict .items ():
219
- if self .__rate_limit_dict [rate_limit_time ]["start" ] + rate_limit_time <= current_time :
220
- self .__rate_limit_dict [rate_limit_time ]["start" ] = current_time
221
- self .__rate_limit_dict [rate_limit_time ]["counter" ] = 0
220
+ for rate_limit_time , rate_limit_info in self ._rate_limit_dict .items ():
221
+ if self ._rate_limit_dict [rate_limit_time ]["start" ] + rate_limit_time <= current_time :
222
+ self ._rate_limit_dict [rate_limit_time ]["start" ] = current_time
223
+ self ._rate_limit_dict [rate_limit_time ]["counter" ] = 0
222
224
if rate_limit_info ['counter' ] + amount > rate_limit_info ['limit' ]:
223
225
return rate_limit_time
224
226
return False
225
227
226
228
def get_minimal_limit (self ):
227
- return self .__minimal_limit
229
+ return self ._minimal_limit if self . has_limit () else 0
228
230
229
231
def get_minimal_timeout (self ):
230
- return self .__minimal_timeout
232
+ return self ._minimal_timeout if self . has_limit () else 0
231
233
232
234
def has_limit (self ):
233
- return not self .__no_limit
235
+ return not self ._no_limit
234
236
235
237
def set_limit (self , rate_limit , percentage = 80 ):
236
238
with self .__lock :
237
- old_rate_limit_dict = deepcopy (self .__rate_limit_dict )
238
- self .__rate_limit_dict = {}
239
+ old_rate_limit_dict = deepcopy (self ._rate_limit_dict )
240
+ self ._rate_limit_dict = {}
239
241
self .percentage = percentage if percentage != 0 else self .percentage
240
242
rate_configs = rate_limit .split (";" )
241
243
if "," in rate_limit :
@@ -246,26 +248,27 @@ def set_limit(self, rate_limit, percentage=80):
246
248
rate = rate .split (":" )
247
249
rate_limit_time = int (rate [1 ])
248
250
limit = int (int (rate [0 ]) * percentage / 100 )
249
- self .__rate_limit_dict [int (rate [1 ])] = {
251
+ self ._rate_limit_dict [int (rate [1 ])] = {
250
252
"counter" : old_rate_limit_dict .get (rate_limit_time , {}).get ('counter' , 0 ),
251
- "start" : self .__rate_limit_dict .get (rate_limit_time , {}).get ('start' , int (monotonic ())),
253
+ "start" : self ._rate_limit_dict .get (rate_limit_time , {}).get ('start' , int (monotonic ())),
252
254
"limit" : limit }
253
- if rate_limit_time < self .__minimal_limit :
254
- self .__minimal_timeout = rate_limit_time + 1
255
- if limit < self .__minimal_limit :
256
- self .__minimal_limit = limit
257
- if self .__rate_limit_dict :
258
- self .__no_limit = False
255
+ if rate_limit_time < self ._minimal_limit :
256
+ self ._minimal_timeout = rate_limit_time + 1
257
+ if limit < self ._minimal_limit :
258
+ self ._minimal_limit = limit
259
+ if self ._rate_limit_dict :
260
+ self ._no_limit = False
259
261
log .debug ("Rate limit set to values: " )
260
- for rate_limit_time in self .__rate_limit_dict :
261
- log .debug ("Time: %s, Limit: %s" , rate_limit_time , self .__rate_limit_dict [rate_limit_time ]["limit" ])
262
+ for rate_limit_time in self ._rate_limit_dict :
263
+ log .debug ("Time: %s, Limit: %s" , rate_limit_time , self ._rate_limit_dict [rate_limit_time ]["limit" ])
262
264
265
+ @property
263
266
def __dict__ (self ):
264
267
return {
265
- "rateLimit" : self .__rate_limit_dict ,
268
+ "rateLimit" : self ._rate_limit_dict ,
266
269
"name" : self .name ,
267
270
"percentage" : self .percentage ,
268
- "no_limit" : self .__no_limit
271
+ "no_limit" : self ._no_limit
269
272
}
270
273
271
274
@staticmethod
@@ -380,9 +383,31 @@ def __init__(self, host, port=1883, username=None, password=None, quality_of_ser
380
383
self .__firmware_request_id = 0
381
384
self .__chunk_size = chunk_size
382
385
self .firmware_received = False
383
- self .__updating_thread = Thread (target = self .__update_thread , name = "Updating thread" )
384
- self .__updating_thread .daemon = True
385
386
self .rate_limits_received = False
387
+ self .__request_service_configuration_required = False
388
+ self .__service_loop = Thread (target = self .__service_loop , name = "Service loop" , daemon = True )
389
+ self .__service_loop .start ()
390
+
391
+ def __service_loop (self ):
392
+ while not self .stopped :
393
+ if self .__request_service_configuration_required :
394
+ self .request_service_configuration (self .service_configuration_callback )
395
+ self .__request_service_configuration_required = False
396
+ elif self .firmware_received :
397
+ self .current_firmware_info [FW_STATE_ATTR ] = "UPDATING"
398
+ self .send_telemetry (self .current_firmware_info )
399
+ sleep (1 )
400
+
401
+ self .__on_firmware_received (self .firmware_info .get (FW_VERSION_ATTR ))
402
+
403
+ self .current_firmware_info = {
404
+ "current_" + FW_TITLE_ATTR : self .firmware_info .get (FW_TITLE_ATTR ),
405
+ "current_" + FW_VERSION_ATTR : self .firmware_info .get (FW_VERSION_ATTR ),
406
+ FW_STATE_ATTR : "UPDATED"
407
+ }
408
+ self .send_telemetry (self .current_firmware_info )
409
+ self .firmware_received = False
410
+ sleep (0.05 )
386
411
387
412
def _on_publish (self , client , userdata , mid ):
388
413
# log.debug("Message %s was published, by client with id: %r", mid ,id(client))
@@ -413,7 +438,7 @@ def _on_connect(self, client, userdata, flags, result_code, *extra_params):
413
438
self ._subscribe_to_topic (ATTRIBUTES_TOPIC + "/response/+" , qos = self .quality_of_service )
414
439
self ._subscribe_to_topic (RPC_REQUEST_TOPIC + '+' , qos = self .quality_of_service )
415
440
self ._subscribe_to_topic (RPC_RESPONSE_TOPIC + '+' , qos = self .quality_of_service )
416
- self .request_service_configuration ( self . service_configuration_callback )
441
+ self .__request_service_configuration_required = True
417
442
else :
418
443
if isinstance (result_code , int ):
419
444
if result_code in RESULT_CODES :
@@ -602,25 +627,6 @@ def __on_firmware_received(self, version_to):
602
627
firmware_file .write (self .firmware_data )
603
628
log .info ('Firmware is updated!\n Current firmware version is: %s' % version_to )
604
629
605
- def __update_thread (self ):
606
- while not self .stopped :
607
- if self .firmware_received :
608
- self .current_firmware_info [FW_STATE_ATTR ] = "UPDATING"
609
- self .send_telemetry (self .current_firmware_info )
610
- sleep (1 )
611
-
612
- self .__on_firmware_received (self .firmware_info .get (FW_VERSION_ATTR ))
613
-
614
- self .current_firmware_info = {
615
- "current_" + FW_TITLE_ATTR : self .firmware_info .get (FW_TITLE_ATTR ),
616
- "current_" + FW_VERSION_ATTR : self .firmware_info .get (FW_VERSION_ATTR ),
617
- FW_STATE_ATTR : "UPDATED"
618
- }
619
- self .send_telemetry (self .current_firmware_info )
620
- self .firmware_received = False
621
-
622
- sleep (0.2 )
623
-
624
630
@staticmethod
625
631
def _decode (message ):
626
632
try :
@@ -701,10 +707,22 @@ def on_service_configuration(self, _, response, *args, **kwargs):
701
707
self ._telemetry_rate_limit .set_limit (rate_limits_config .get ('telemetryMessages' ), percentage = 80 )
702
708
if rate_limits_config .get ('telemetryDataPoints' ):
703
709
self ._telemetry_dp_rate_limit .set_limit (rate_limits_config .get ('telemetryDataPoints' ), percentage = 80 )
710
+
704
711
if service_config .get ('maxInflightMessages' ):
705
- max_inflight_messages = int (min (self ._messages_rate_limit .get_minimal_limit (),
706
- self ._telemetry_rate_limit .get_minimal_limit (),
707
- service_config .get ('maxInflightMessages' , 100 )) * 80 / 100 )
712
+ use_messages_rate_limit_factor = self ._messages_rate_limit .has_limit ()
713
+ use_telemetry_rate_limit_factor = self ._telemetry_rate_limit .has_limit ()
714
+ if use_messages_rate_limit_factor and use_telemetry_rate_limit_factor :
715
+ max_inflight_messages = int (min (self ._messages_rate_limit .get_minimal_limit (),
716
+ self ._telemetry_rate_limit .get_minimal_limit (),
717
+ service_config .get ('maxInflightMessages' , 100 )) * 80 / 100 )
718
+ elif use_messages_rate_limit_factor :
719
+ max_inflight_messages = int (min (self ._messages_rate_limit .get_minimal_limit (),
720
+ service_config .get ('maxInflightMessages' , 100 )) * 80 / 100 )
721
+ elif use_telemetry_rate_limit_factor :
722
+ max_inflight_messages = int (min (self ._telemetry_rate_limit .get_minimal_limit (),
723
+ service_config .get ('maxInflightMessages' , 100 )) * 80 / 100 )
724
+ else :
725
+ max_inflight_messages = int (service_config .get ('maxInflightMessages' , 100 ) * 80 / 100 )
708
726
self .max_inflight_messages_set (max_inflight_messages )
709
727
self .max_queued_messages_set (max_inflight_messages )
710
728
if service_config .get ('maxPayloadSize' ):
@@ -718,6 +736,8 @@ def set_server_side_rpc_request_handler(self, handler):
718
736
self .__device_on_server_side_rpc_response = handler
719
737
720
738
def _wait_for_rate_limit_released (self , timeout , message_rate_limit , dp_rate_limit = None , amount = 1 ):
739
+ if not message_rate_limit .has_limit () and not (dp_rate_limit is None or dp_rate_limit .has_limit ()):
740
+ return
721
741
start_time = int (monotonic ())
722
742
dp_rate_limit_timeout = dp_rate_limit .get_minimal_timeout () if dp_rate_limit is not None else 0
723
743
timeout = max (message_rate_limit .get_minimal_timeout (), dp_rate_limit_timeout , timeout ) + 10
@@ -779,7 +799,7 @@ def _wait_until_current_queued_messages_processed(self):
779
799
connection_was_lost = True
780
800
if current_out_messages >= inflight_messages :
781
801
sleep (.001 )
782
- if int (monotonic ()) - waiting_started > timeout_for_break and not connection_was_lost :
802
+ if int (monotonic ()) - waiting_started > timeout_for_break and not connection_was_lost or self . stopped :
783
803
break
784
804
785
805
def _send_request (self , _type , kwargs , timeout = DEFAULT_TIMEOUT , device = None ,
@@ -863,29 +883,33 @@ def __send_publish_with_limitations(self, kwargs, timeout, device=None, msg_rate
863
883
864
884
def __send_split_message (self , results , part , kwargs , timeout , device , msg_rate_limit , dp_rate_limit ,
865
885
topic ):
866
- dp_rate_limit .increase_rate_limit_counter (part ['datapoints' ])
867
- rate_limited = self ._wait_for_rate_limit_released (timeout ,
868
- message_rate_limit = msg_rate_limit ,
869
- dp_rate_limit = dp_rate_limit ,
870
- amount = part ['datapoints' ])
871
- if rate_limited :
872
- return rate_limited
873
- msg_rate_limit .increase_rate_limit_counter ()
886
+ if msg_rate_limit .has_limit () or dp_rate_limit .has_limit ():
887
+ dp_rate_limit .increase_rate_limit_counter (part ['datapoints' ])
888
+ rate_limited = self ._wait_for_rate_limit_released (timeout ,
889
+ message_rate_limit = msg_rate_limit ,
890
+ dp_rate_limit = dp_rate_limit ,
891
+ amount = part ['datapoints' ])
892
+ if rate_limited :
893
+ return rate_limited
894
+ if msg_rate_limit .has_limit () or dp_rate_limit .has_limit ():
895
+ msg_rate_limit .increase_rate_limit_counter ()
874
896
kwargs ["payload" ] = dumps (part ['message' ])
875
897
self ._wait_until_current_queued_messages_processed ()
876
898
if not self .stopped :
877
899
if device is not None :
878
900
log .debug ("Device: %s, Sending message to topic: %s " , device , topic )
879
- if part ['datapoints' ] > 0 :
880
- log .debug ("Sending message with %i datapoints" , part ['datapoints' ])
881
- log .debug ("Message payload: %r" , kwargs ["payload" ])
882
- log .debug ("Rate limits after sending message: %r" , msg_rate_limit .__dict__ )
883
- log .debug ("Data points rate limits after sending message: %r" , dp_rate_limit .__dict__ )
884
- else :
885
- log .debug ("Sending message with %r" , kwargs ["payload" ])
886
- log .debug ("Message payload: %r" , kwargs ["payload" ])
887
- log .debug ("Rate limits after sending message: %r" , msg_rate_limit .__dict__ )
888
- log .debug ("Data points rate limits after sending message: %r" , dp_rate_limit .__dict__ )
901
+ if msg_rate_limit .has_limit () or dp_rate_limit .has_limit ():
902
+ if part ['datapoints' ] > 0 :
903
+ log .debug ("Sending message with %i datapoints" , part ['datapoints' ])
904
+ if log .isEnabledFor (5 ) and hasattr (log , 'trace' ):
905
+ log .trace ("Message payload: %r" , kwargs ["payload" ])
906
+ log .debug ("Rate limits after sending message: %r" , msg_rate_limit .__dict__ )
907
+ log .debug ("Data points rate limits after sending message: %r" , dp_rate_limit .__dict__ )
908
+ else :
909
+ if log .isEnabledFor (5 ) and hasattr (log , 'trace' ):
910
+ log .trace ("Sending message with %r" , kwargs ["payload" ])
911
+ log .debug ("Rate limits after sending message: %r" , msg_rate_limit .__dict__ )
912
+ log .debug ("Data points rate limits after sending message: %r" , dp_rate_limit .__dict__ )
889
913
result = self ._client .publish (** kwargs )
890
914
if result .rc == MQTT_ERR_QUEUE_SIZE :
891
915
while not self .stopped and result .rc == MQTT_ERR_QUEUE_SIZE :
@@ -1124,7 +1148,8 @@ def _split_message(message_pack, datapoints_max_count, max_payload_size):
1124
1148
if not isinstance (message_pack , list ):
1125
1149
message_pack = [message_pack ]
1126
1150
1127
- datapoints_max_count = max (datapoints_max_count - 1 , 1 )
1151
+ datapoints_max_count = max (datapoints_max_count - 1 , 0 )
1152
+
1128
1153
append_split_message = split_messages .append
1129
1154
1130
1155
final_message_item = {'data' : [], 'datapoints' : 0 }
@@ -1155,11 +1180,13 @@ def _split_message(message_pack, datapoints_max_count, max_payload_size):
1155
1180
value = values [data_key ]
1156
1181
data_key_size = len (data_key ) + len (str (value ))
1157
1182
1158
- if len (message_item_values_with_allowed_size ) < datapoints_max_count and current_size + data_key_size < max_payload_size :
1183
+ if ((datapoints_max_count == 0 or len (message_item_values_with_allowed_size ) < datapoints_max_count )
1184
+ and current_size + data_key_size < max_payload_size ):
1159
1185
message_item_values_with_allowed_size [data_key ] = value
1160
1186
current_size += data_key_size
1161
1187
1162
- if len (message_item_values_with_allowed_size ) >= datapoints_max_count + current_size // 1024 or current_size + data_key_size >= max_payload_size :
1188
+ if ((datapoints_max_count > 0 and len (message_item_values_with_allowed_size ) >= datapoints_max_count + current_size // 1024 )
1189
+ or current_size + data_key_size >= max_payload_size ):
1163
1190
if ts :
1164
1191
message_chunk = {"ts" : ts , "values" : message_item_values_with_allowed_size .copy ()}
1165
1192
if 'metadata' in message :
0 commit comments