├── .gitignore ├── README.md ├── ppmessage_mqtt ├── __init__.py ├── ppauth.py ├── ppmt.py ├── ppmtdb.py ├── test │ ├── __init__.py │ ├── main.py │ └── mqttclient.py └── yourauth.py ├── pypi.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | dist 4 | ppmessage.egg-info 5 | ppmessage_mqtt.egg-info 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ppmessage-mqtt 2 | Python mqtt server at PPMESSSAGE (http://www.ppmessage.com) 3 | 4 | # install 5 | pip install ppmessage-mqtt 6 | 7 | # test 8 | 9 | ## start server 10 | ``` 11 | from ppmessage_mqtt import mqtt_server 12 | from tornado.options import parse_command_line() 13 | 14 | if __name__ == "__main__": 15 | # initial tornado options (logging used) 16 | parse_command_line() 17 | mqtt_server() 18 | ``` 19 | 20 | ## client to publish message with paho-mqtt 21 | cd test 22 | pip install paho-mqtt 23 | 24 | cd test 25 | python test/main.py 26 | 27 | 28 | ## overwrite anthenticate 29 | ``` 30 | from ppmessage_mqtt import authenticate 31 | from ppmessage_mqtt import mqtt_authenticate 32 | 33 | class your_authenticate(authenticate): 34 | def verify_client_id(self, client_id): 35 | return True 36 | def verify_user_password(self, user_id, password): 37 | return True 38 | 39 | if __name__ == "__main__": 40 | # initial tornado options (logging used) 41 | parse_command_line() 42 | mqtt_authenticate(your_authenticate) 43 | mqtt_server() 44 | ``` 45 | -------------------------------------------------------------------------------- /ppmessage_mqtt/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical, PPMessage 4 | # Ding Guijin, guijin.ding@ppmessage.com 5 | # Ning Ben, ben.ning@yvertical.com 6 | # 7 | # All rights reserved 8 | # 9 | 10 | from .ppmt import ppmt_main 11 | from .ppmt import ppmt_set_authenticate 12 | from .ppauth import authenticate 13 | 14 | def mqtt_server(): 15 | ppmt_main() 16 | return 17 | 18 | def mqtt_authenticate(_class): 19 | ppmt_set_authenticate(_class) 20 | return 21 | 22 | __version__ = "1.0.9" 23 | -------------------------------------------------------------------------------- /ppmessage_mqtt/ppauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical, PPMessage. 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # Ning Ben, ben.ning@yvertical.com 6 | # 7 | # All rights are reserved. 8 | # 9 | 10 | 11 | # CONNACK codes 12 | 13 | CONNACK_ACCEPTED = 0 14 | CONNACK_REFUSED_PROTOCOL_VERSION = 1 15 | CONNACK_REFUSED_IDENTIFIER_REJECTED = 2 16 | CONNACK_REFUSED_SERVER_UNAVAILABLE = 3 17 | CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4 18 | CONNACK_REFUSED_NOT_AUTHORIZED = 5 19 | 20 | class authenticate: 21 | def auth(self, client_id, user_id=None, password=None): 22 | 23 | if not self.verify_client_id(client_id): 24 | return CONNACK_REFUSED_IDENTIFIER_REJECTED 25 | 26 | if not self.verify_user_password(user_id, password): 27 | return CONNACK_REFUSED_BAD_USERNAME_PASSWORD 28 | 29 | return CONNACK_ACCEPTED 30 | 31 | def verify_client_id(self, client_id): 32 | # to do verify the client id. return True is verify ok, else False 33 | return True 34 | 35 | def verify_user_password(self, user_id, password): 36 | # to do verify the user id and password. return True is verify ok, else False 37 | return True 38 | 39 | def pub_acl_list(self, client_id): 40 | # to implement your get acl list code at here 41 | # acl = [client_id+"/+"] 42 | acl = ["+/#"] 43 | return acl 44 | 45 | def sub_acl_list(self, client_id): 46 | # to implement your get acl list code at here 47 | acl = ["$SYS/stat/#", "+/#"] 48 | return acl 49 | -------------------------------------------------------------------------------- /ppmessage_mqtt/ppmt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical, PPMESSAGE 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # Ning Ben, ben.ning@yvertical.com 6 | # 7 | # All rights are reserved. 8 | # 9 | 10 | import sys 11 | import time 12 | import struct 13 | import logging 14 | import functools 15 | import threading 16 | 17 | try: 18 | from Queue import Queue 19 | except: 20 | from queue import Queue 21 | 22 | from tornado.web import Application 23 | from tornado.ioloop import PollIOLoop 24 | from tornado.tcpserver import TCPServer 25 | 26 | from .ppmtdb import worker 27 | from .ppmtdb import sender 28 | from .ppmtdb import ppmtdb 29 | from .ppmtdb import mqtt3_message 30 | from .ppmtdb import yvmq_msg_state 31 | 32 | #from .ppauth import authenticate 33 | from .yourauth import your_authenticate as authenticate 34 | 35 | if sys.version_info[0] < 3: 36 | MQTT3_PROTOCOL_NAME = "MQIsdp" 37 | else: 38 | MQTT3_PROTOCOL_NAME = b"MQIsdp" 39 | 40 | PROTOCOL_VERSION = 3 41 | 42 | # db cmd code for yvdbase maintain 43 | DBCMD_INVALID = 0 44 | DBCMD_DEL_I_MSG = 1 45 | DBCMD_DEL_O_MSG = 2 46 | DBCMD_ADD_I_MSG = 3 47 | DBCMD_UPD_O_MSG = 4 48 | DBCMD_FINISH = 5 49 | 50 | # Message types 51 | MQTT3_CNNECT = 0x10 52 | MQTT3_CONNACK = 0x20 53 | MQTT3_PUBLISH = 0x30 54 | MQTT3_PUBACK = 0x40 55 | MQTT3_PUBREC = 0x50 56 | MQTT3_PUBREL = 0x60 57 | MQTT3_PUBCOMP = 0x70 58 | MQTT3_SUBSCRIBE = 0x80 59 | MQTT3_SUBACK = 0x90 60 | MQTT3_UNSUBSCRIBE = 0xA0 61 | MQTT3_UNSUBACK = 0xB0 62 | MQTT3_PINGREQ = 0xC0 63 | MQTT3_PINGRESP = 0xD0 64 | MQTT3_DISCONNCT = 0xE0 65 | 66 | MQTT3_ANY = 0xF0 67 | MQTT3_NEW = 0x00 68 | 69 | # Log levels 70 | MQ_LOG_INFO = 0x01 71 | MQ_LOG_NOTICE = 0x02 72 | MQ_LOG_WARNING = 0x04 73 | MQ_LOG_ERR = 0x08 74 | MQ_LOG_DEBUG = 0x10 75 | 76 | # CONNACK codes 77 | CONNACK_ACCEPTED = 0 78 | CONNACK_REFUSED_PROTOCOL_VERSION = 1 79 | CONNACK_REFUSED_IDENTIFIER_REJECTED = 2 80 | CONNACK_REFUSED_SERVER_UNAVAILABLE = 3 81 | CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4 82 | CONNACK_REFUSED_NOT_AUTHORIZED = 5 83 | 84 | # Connection state 85 | mq_cs_new = 0 86 | mq_cs_connected = 1 87 | mq_cs_disconnecting = 2 88 | mq_cs_connect_async = 3 89 | mq_cs_reconnected = 4 90 | 91 | 92 | # Error values 93 | MQ_ERR_AGAIN = -1 94 | MQ_ERR_SUCCESS = 0 95 | MQ_ERR_NOMEM = 1 96 | MQ_ERR_PROTOCOL = 2 97 | MQ_ERR_INVAL = 3 98 | MQ_ERR_NO_CONN = 4 99 | MQ_ERR_CONN_REFUSED = 5 100 | MQ_ERR_NOT_FOUND = 6 101 | MQ_ERR_CONN_LOST = 7 102 | MQ_ERR_TLS = 8 103 | MQ_ERR_PAYLOAD_SIZE = 9 104 | MQ_ERR_NOT_SUPPORTED = 10 105 | MQ_ERR_AUTH = 11 106 | MQ_ERR_ACL_DENIED = 12 107 | MQ_ERR_UNKNOWN = 13 108 | MQ_ERR_ERRNO = 14 109 | 110 | gMQbase = ppmtdb() 111 | gAuth = authenticate() 112 | 113 | def read_callback(function): 114 | @functools.wraps(function) 115 | def wrapper(self,*args, **keyargs): 116 | received_bytes = function(self,*args, **keyargs) 117 | self._ppmtdb.work_queue.put( (self._ppmtdb.add_received_bytes, (received_bytes,)) ) # put add pub task into queue 118 | return wrapper 119 | 120 | class mqtt3context(object): 121 | 122 | def __init__(self, stream, ppmtdb=gMQbase, auth=gAuth): 123 | self._ppmtdb = ppmtdb 124 | self._stream = stream 125 | self._auth = auth 126 | self._sock = None 127 | self._message_retry = 20 128 | self._last_retry_check = 0 129 | self._client_id = None 130 | self._will_topic = None 131 | self._will_message = None 132 | self._user_name = None 133 | self._password = None 134 | self._connect_state = mq_cs_new 135 | 136 | # self._total_received_bytes = 0 137 | # self._total_sent_bytes = 0 138 | 139 | self._pub_acl = None 140 | self._sub_acl = None 141 | 142 | # self._semaphore = threading.BoundedSemaphore(1) 143 | # self.res_queue = Queue() # result queue for worker thread 144 | self._wait_pub_queue = Queue() # wait to pubish with qos 1 or qos 2 message 145 | self._curr_pub_msg = None # current publish message what waiting to be completed 146 | 147 | ''' for incoming packet from client ''' 148 | self._msgtype = 0 149 | self._dup = False 150 | self._out_mid = 0 151 | self._last_mid = 0 152 | self._qos = 0 153 | self._retain = False 154 | self._username = "" 155 | self._password = "" 156 | self._buff = B"" 157 | self._fixed_header_byte1 = 0 158 | self._len_bytes_count = 0 159 | self._error_code = 0 160 | 161 | self._keep_alive = 60 162 | self._timeout_handle = PollIOLoop.instance().call_later(1.5 * self._keep_alive, self.keep_alive_timeout) 163 | self._stream.set_close_callback(self.close_callback) 164 | 165 | # for remaining len 166 | self._multiplier = 1 167 | self._remaining_len = 0 168 | self._cnn_flag = 0 169 | 170 | 171 | self.handler = { 172 | MQTT3_CNNECT: lambda:self.mqtt3_connect_handler(), 173 | # MQTT3_CONNACK: lambda: self.mqtt3_connack_handler(), 174 | MQTT3_PUBLISH: lambda: self.mqtt3_publish_handler(), 175 | MQTT3_PUBACK: lambda: self.mqtt3_puback_handler(), 176 | MQTT3_PUBREC: lambda: self.mqtt3_pubrec_handler(), 177 | MQTT3_PUBREL: lambda: self.mqtt3_pubrel_handler(), 178 | MQTT3_PUBCOMP: lambda: self.mqtt3_pubcomp_handler(), 179 | MQTT3_SUBSCRIBE: lambda: self.mqtt3_subscribe_handler(), 180 | # MQTT3_SUBACK: lambda: self.suback_handler(), 181 | MQTT3_UNSUBSCRIBE: lambda: self.mqtt3_unsubscribe_handler(), 182 | # MQTT3_UNSUBACK: lambda: self.unsuback_handler(), 183 | MQTT3_PINGREQ: lambda: self.mqtt3_pingreq_handler(), 184 | # MQTT3_PINGRESP: lambda: self.pingresp_handler(), 185 | MQTT3_DISCONNCT: lambda: self.mqtt3_disconnect_handler() 186 | } 187 | 188 | self.mqtt3_recv_packet() 189 | 190 | 191 | def is_mqtt3_message(self, msg_type): 192 | 193 | ret = False; 194 | 195 | if msg_type == MQTT3_CNNECT: 196 | if self._connect_state == mq_cs_new: 197 | ret = True 198 | elif self._connect_state == mq_cs_connected: 199 | ret = True 200 | 201 | if not ret: 202 | self.close() 203 | 204 | return ret 205 | 206 | @read_callback 207 | def mqtt3_recv_msgtype_callback(self, data): 208 | 209 | byte1, = struct.unpack('B', data) 210 | msg_type = byte1 & 0xF0 211 | 212 | self._fixed_header_byte1 = byte1 213 | 214 | if self.is_mqtt3_message(msg_type): 215 | self._msgtype = msg_type 216 | self._dup = (byte1 & 0x08) > 0 217 | self._qos = (byte1 & 0x06) >> 1 218 | self._retain = (byte1 & 0x01) > 1 219 | self.mqtt3_recv_remaining_len() 220 | else: 221 | self._error_code = 1 222 | 223 | return len(data) 224 | 225 | @read_callback 226 | def mqtt3_recv_buff_callback(self, data): 227 | 228 | self._buff = data 229 | self.mqtt3_msg_handler(self._msgtype) 230 | 231 | if not self._stream.closed(): 232 | self.mqtt3_recv_packet() 233 | 234 | return len(data) 235 | 236 | def mqtt3_recv_buff(self): 237 | self._stream.read_bytes(self._remaining_len, self.mqtt3_recv_buff_callback) 238 | 239 | 240 | # get remaining len (1 ~ 4 bytes) 241 | @read_callback 242 | def mqtt3_recv_remaining_len_callback(self, data): 243 | 244 | tmpb, = struct.unpack('B', data) 245 | self._len_bytes_count += 1 246 | self._remaining_len += (tmpb & 0x7F) * self._multiplier 247 | self._multiplier *= 128 248 | 249 | if (tmpb & 0x80): 250 | if self._len_bytes_count<5: # must be less or eq. 4 bytes 251 | self.mqtt3_recv_remaining_len() 252 | else: 253 | self._error_code = 2 254 | self.mqtt3_recv_packet() 255 | else: 256 | self.mqtt3_recv_buff() 257 | 258 | return len(data) 259 | 260 | 261 | def mqtt3_recv_remaining_len(self): 262 | self._stream.read_bytes(1, self.mqtt3_recv_remaining_len_callback) 263 | 264 | 265 | def mqtt3_recv_packet(self): 266 | self._remaining_len = 0 267 | self._multiplier = 1 268 | self._len_bytes_count = 0 269 | self._stream.read_bytes(1, self.mqtt3_recv_msgtype_callback) 270 | 271 | def mqtt3_msg_handler(self, msg_type): 272 | fun = self.handler.get(msg_type) 273 | if not fun: 274 | logging.error("no mqtt handler for msg_type: %d" % msg_type) 275 | return 276 | 277 | self._ppmtdb.work_queue.put( (self._ppmtdb.add_received_message, (1,)) ) 278 | fun() 279 | self.reset_timeout() 280 | return 281 | 282 | def mqtt3_puback_handler(self): 283 | if self._remaining_len != len(self._buff) and self._remaining_len != 2: 284 | self.close() 285 | return 1 286 | mid, = struct.unpack("!H", self._buff) 287 | if self._curr_pub_msg and self._curr_pub_msg.mid == mid and self._curr_pub_msg.state == yvmq_msg_state.wait_puback: 288 | self._ppmtdb.work_queue.put( (self._ppmtdb.unpub, (self._curr_pub_msg.topic, self._curr_pub_msg.qos)) ) # unpub when be send ok 289 | topic = self._curr_pub_msg.topic 290 | qos = self._curr_pub_msg.qos 291 | device_id = self._client_id 292 | logging.info(" ack unpub --> topic: %s qos: %d device_id: %s " % ( topic, qos, device_id)) 293 | self._curr_pub_msg = None 294 | self._ppmtdb.work_queue.put( (self._ppmtdb.dec_in_flight_o, ()) ) 295 | self.wait_pub_queue_handler_callback() 296 | return 297 | 298 | def mqtt3_pubrec_handler(self): 299 | if self._remaining_len != len(self._buff) and self._remaining_len != 2: 300 | self.close() 301 | return 1 302 | 303 | mid, = struct.unpack("!H", self._buff) 304 | logging.info("mqtt3_pubrec_handler..................mid: %d" % mid) 305 | 306 | # if self._curr_pub_msg: 307 | # if self._curr_pub_msg.mid == mid and self._curr_pub_msg.state == yvmq_msg_state.wait_pubrec: 308 | # PollIOLoop.instance().remove_timeout(self._curr_pub_msg.timeout) 309 | # self._curr_pub_msg = None 310 | # self._ppmtdb.work_queue.put( (self._ppmtdb.dec_in_flight_o, ()) ) 311 | 312 | self.mqtt3_send_pubrel(mid) 313 | 314 | 315 | def mqtt3_pubrel_handler(self): 316 | 317 | logging.info("mqtt3_pubrel_handler..................") 318 | 319 | if self._remaining_len != len(self._buff) and self._remaining_len != 2: 320 | self.close() 321 | return 1 322 | 323 | mid, = struct.unpack("!H", self._buff) 324 | self.mqtt3_send_pubcomp(mid) 325 | 326 | if self._dup and self._qos>0 and self._last_mid==mid: 327 | return 328 | 329 | self._last_mid = mid 330 | 331 | # self._ppmtdb.work_queue.put( (self, WQ_DEL_I_MSG, mid) ) 332 | self._ppmtdb.work_queue.put( (self._ppmtdb.dec_in_flight_i, ()) ) 333 | 334 | 335 | def mqtt3_pubcomp_handler(self): 336 | if self._remaining_len != len(self._buff) and self._remaining_len != 2: 337 | self.close() 338 | return 1 339 | 340 | mid, = struct.unpack("!H", self._buff) 341 | if self._curr_pub_msg and self._curr_pub_msg.mid == mid and self._curr_pub_msg.state == yvmq_msg_state.wait_pubcomp: 342 | self._ppmtdb.work_queue.put( (self._ppmtdb.unpub, (self._curr_pub_msg.topic, self._curr_pub_msg.qos)) ) # unpub when be send ok 343 | self._curr_pub_msg = None 344 | self._ppmtdb.work_queue.put( (self._ppmtdb.dec_in_flight_o, ()) ) 345 | self.wait_pub_queue_handler_callback() 346 | return 347 | 348 | def mqtt3_connect_handler(self): 349 | if (self._remaining_len != len(self._buff)): 350 | return 1 351 | 352 | remain_len = self._remaining_len - 12 353 | fmt = "!H6sBBH" + str(remain_len) + "s" 354 | proto_len, proto_name, ver, cnn_flag, keep_alive, payload = struct.unpack(fmt, self._buff) 355 | 356 | self._cnn_flag = cnn_flag 357 | 358 | if proto_name != "MQIsdp" or proto_len != 6 or ver != 3: 359 | self._error_code = MQ_ERR_PROTOCOL 360 | # self.close() 361 | self.mqtt3_send_connack( CONNACK_REFUSED_PROTOCOL_VERSION ) 362 | return MQ_ERR_PROTOCOL 363 | 364 | remain_len = len(payload) - 2 365 | fmt = "!H" + str(remain_len) + "s" 366 | id_len, payload = struct.unpack(fmt, payload) 367 | 368 | remain_len = len(payload) - id_len 369 | 370 | if remain_len: 371 | fmt = "!" + str(id_len) + "s" + str(remain_len) + "s" 372 | client_id, payload = struct.unpack(fmt, payload) 373 | else: 374 | fmt = "!" + str(id_len) + "s" 375 | client_id, = struct.unpack(fmt, payload) 376 | 377 | self._client_id = client_id 378 | 379 | # for will flag 380 | remain_len = len(payload) - 2 381 | if cnn_flag & 0x04 and remain_len>0: 382 | 383 | # get will topic 384 | fmt = "!H" + str(remain_len) + "s" 385 | will_topic_len, payload = struct.unpack(fmt, payload) 386 | 387 | remain_len = len(payload) - will_topic_len 388 | 389 | if remain_len: 390 | fmt = "!" + str(will_topic_len) + "s" + str(remain_len) + "s" 391 | will_topic, payload = struct.unpack(fmt, payload) 392 | remain_len = len(payload) 393 | else: 394 | fmt = "!" + str(will_topic_len) + "s" 395 | will_topic, = struct.unpack(fmt, payload) 396 | 397 | self._will_topic = will_topic 398 | 399 | # get will message 400 | remain_len -= 2 401 | 402 | if remain_len: 403 | fmt = "!H" + str(remain_len) + "s" 404 | will_message_len, payload = struct.unpack(fmt, payload) 405 | remain_len = len(payload) - will_message_len 406 | 407 | if remain_len: 408 | fmt = "!" + str(will_message_len) + "s" + str(remain_len) + "s" 409 | will_message, payload = struct.unpack(fmt, payload) 410 | remain_len = len(payload) 411 | else: 412 | fmt = "!" + str(will_message_len) + "s" 413 | will_message, = struct.unpack(fmt, payload) 414 | 415 | self._will_message = will_message 416 | 417 | 418 | # get user name 419 | remain_len = len(payload) - 2 420 | if cnn_flag & 0x80 and remain_len>0: 421 | # get will topic 422 | fmt = "!H" + str(remain_len) + "s" 423 | user_name_len, payload = struct.unpack(fmt, payload) 424 | 425 | remain_len = len(payload) - user_name_len 426 | 427 | user_name = None 428 | 429 | if remain_len: 430 | fmt = "!" + str(user_name_len) + "s" + str(remain_len) + "s" 431 | user_name, payload = struct.unpack(fmt, payload) 432 | remain_len = len(payload) 433 | else: 434 | fmt = "!" + str(user_name_len) + "s" 435 | user_name, = struct.unpack(fmt, payload) 436 | 437 | self._user_name = user_name 438 | 439 | # get user password 440 | remain_len -= 2 441 | if cnn_flag & 0x80 and cnn_flag &0x60 and remain_len>0: 442 | # get will topic 443 | fmt = "!H" + str(remain_len) + "s" 444 | password_len, payload = struct.unpack(fmt, payload) 445 | 446 | remain_len = len(payload) - password_len 447 | 448 | password = None 449 | 450 | if remain_len: 451 | fmt = "!" + str(password_len) + "s" + str(remain_len) + "s" 452 | pasword, payload = struct.unpack(fmt, payload) 453 | else: 454 | fmt = "!" + str(password_len) + "s" 455 | password, = struct.unpack(fmt, payload) 456 | 457 | self._password = password 458 | 459 | ack_code = self._auth.auth(self._client_id, self._user_name, self._password) 460 | self.mqtt3_send_connack(ack_code) 461 | if keep_alive: 462 | self._keep_alive = keep_alive 463 | logging.info(" connect --> device_id: %s " % ( self._client_id)) 464 | return 465 | 466 | def mqtt3_send_connack(self, connack_code=CONNACK_ACCEPTED): 467 | 468 | packet = bytearray() 469 | packet.extend(struct.pack("!BBBB", MQTT3_CONNACK, 2, 0, connack_code)) 470 | 471 | self.write(packet) 472 | if connack_code == CONNACK_ACCEPTED: 473 | self._connect_state = mq_cs_connected 474 | if self._cnn_flag & 0x02: # clean flag is set (1) 475 | # logging.info ("%s clean flag set (1) " % self._client_id) 476 | clean = True 477 | else: # clean flag is not set (0) 478 | # logging.info ("%s clean flag set (0) " % self._client_id) 479 | clean = False 480 | 481 | # if self._client_id in self._ppmtdb.cnns: 482 | # self._ppmtdb.cnns[self._client_id].context._stream.close() 483 | 484 | self._ppmtdb.work_queue.put((self._ppmtdb.add_cnn , (self, clean)) ) # put a add_cnn task into queue 485 | 486 | self._pub_acl = self._auth.pub_acl_list(self._client_id) 487 | self._sub_acl = self._auth.sub_acl_list(self._client_id) 488 | 489 | else: 490 | self.close() 491 | 492 | return 493 | 494 | def mqtt3_publish_handler(self): 495 | 496 | qos = ( self._fixed_header_byte1 & 0x06 ) >> 1 497 | retain = self._fixed_header_byte1 & 0x01 498 | dup = self._fixed_header_byte1 & 0x08 499 | 500 | if qos > 2 or self._remaining_len != len(self._buff): 501 | self.close() 502 | 503 | # get topic len & topic 504 | remain_len = self._remaining_len - 2 505 | fmt = "!H" + str(remain_len) + "s" 506 | topic_len, payload = struct.unpack(fmt, self._buff) 507 | 508 | remain_len = len(payload) - topic_len 509 | 510 | if qos: 511 | remain_len -= 2 512 | message = b"" 513 | if remain_len: 514 | fmt = "!" + str(topic_len) + "sH" + str(remain_len) + "s" 515 | topic, mid, message = struct.unpack(fmt, payload) 516 | else: 517 | fmt = "!" + str(topic_len) + "sH" 518 | topic, mid = struct.unpack(fmt, payload) 519 | else: 520 | fmt = "!" + str(topic_len) + "s" + str(remain_len) + "s" 521 | topic, message = struct.unpack(fmt, payload) 522 | mid = 0 523 | 524 | if not self.verify_pubtopic_ok(topic): 525 | self.close() 526 | return 527 | 528 | if qos == 1: 529 | self.mqtt3_send_puback(mid) 530 | elif qos == 2: 531 | self.mqtt3_send_pubrec(mid) 532 | 533 | if dup and qos==2 and self._last_mid==mid: # if qos == 1 then republish to subscribers (At least once delivery) 534 | return 535 | 536 | self._last_mid = mid 537 | 538 | if qos==2: 539 | self._ppmtdb.work_queue.put( (self._ppmtdb.inc_in_flight_i, ()) ) 540 | 541 | if message: 542 | 543 | #print " received pub --> topic: %s device_id: %s " % ( topic, self._client_id) 544 | logging.info(" received pub --> topic: %s device_id: %s " % ( topic, self._client_id)) 545 | 546 | if retain: 547 | self._ppmtdb.work_queue.put( (self._ppmtdb.add_pub, (topic, qos, message)) ) # put add pub task into queue 548 | self._ppmtdb.work_queue.put( (self._ppmtdb.pub4pub_process, (topic, qos, message)) ) # put a pub task into queue 549 | 550 | elif retain: 551 | self._ppmtdb.work_queue.put( (self._ppmtdb.unpub, (topic, qos)) ) # put unpub task into queue 552 | 553 | 554 | def mqtt3_send_puback(self, mid): 555 | packet = bytearray() 556 | packet.extend( struct.pack("!BBH", MQTT3_PUBACK, 2, mid) ) 557 | self.write(packet) 558 | # self._ppmtdb.work_queue.put( (self._ppmtdb.dec_in_flight_i, ()) ) 559 | 560 | def mqtt3_send_pubrec(self, mid): 561 | packet = bytearray() 562 | packet.extend( struct.pack("!BBH", MQTT3_PUBREC | 0x02, 2, mid) ) 563 | self.write(packet) 564 | # self._ppmtdb.work_queue.put( (self, WQ_ADD_I_MSG, (mid, yvmq_msg_state.wait_pubrel, packet)) ) 565 | # self._ppmtdb.work_queue.put( (self._ppmtdb.add_i_message, (self._client_id, mid, yvmq_msg_state.wait_pubrel, packet)) ) 566 | 567 | def mqtt3_send_pubrel(self, mid, update_state=True): 568 | # logging.info ("mqtt3_send_pubrel ---> mid:%d" % mid) 569 | packet = bytearray() 570 | packet.extend( struct.pack("!BBH", MQTT3_PUBREL | 0x02, 2, mid) ) 571 | self._curr_pub_msg.state = yvmq_msg_state.wait_pubcomp 572 | self._curr_pub_msg.packet = packet 573 | self.write(packet) 574 | return 575 | 576 | def mqtt3_send_pubcomp(self, mid): 577 | packet = bytearray() 578 | packet.extend( struct.pack("!BBH", MQTT3_PUBCOMP, 2, mid) ) 579 | self.write(packet) 580 | 581 | def mqtt3_disconnect_handler(self): 582 | self._connect_state = mq_cs_disconnecting 583 | self.close() 584 | return 585 | 586 | def mqtt3_pingreq_handler(self): 587 | logging.info(" pingreq --> device_id: %s " % ( self._client_id)) 588 | self.mqtt3_send_pingresp() 589 | return 590 | 591 | def mqtt3_send_pingresp(self): 592 | logging.info(" pingresp --> device_id: %s " % ( self._client_id)) 593 | packet = bytearray() 594 | packet.extend( struct.pack("!BB", MQTT3_PINGRESP, 0) ) 595 | self.write(packet) 596 | return 597 | 598 | def mqtt3_subscribe_handler(self): 599 | qos = ( self._fixed_header_byte1 & 0x06 ) >> 1 600 | dup = self._fixed_header_byte1 & 0x08 601 | if qos != 1 or self._remaining_len != len(self._buff): 602 | self.close() 603 | return 604 | 605 | # get mid 606 | remain_len = self._remaining_len - 2 607 | fmt = "!H" + str(remain_len) + "s" 608 | 609 | mid, payload = struct.unpack(fmt, self._buff) 610 | 611 | sub_list = [] 612 | remain_len = len(payload) - 2 613 | 614 | while remain_len: 615 | fmt = "!H" + str(remain_len) + "s" 616 | sub_topic_len, payload = struct.unpack(fmt, payload) 617 | remain_len = len(payload) - sub_topic_len - 1 618 | if remain_len: 619 | fmt = "!" + str(sub_topic_len) + "sB" + str(remain_len) + "s" 620 | sub_topic, sub_qos, payload = struct.unpack(fmt, payload) 621 | remain_len = len(payload) - 2 622 | else: 623 | fmt = "!" + str(sub_topic_len) + "sB" 624 | sub_topic, sub_qos = struct.unpack(fmt, payload) 625 | 626 | sub_list.append((sub_topic, sub_qos)) 627 | 628 | #print " received sub --> topic: %s device_id: %s " % ( sub_topic, self._client_id) 629 | logging.info(" received sub --> topic: %s device_id: %s " % ( sub_topic, self._client_id)) 630 | 631 | if sub_qos >= 0x03 or not self.verify_subtopic_ok(sub_topic): 632 | self.close() 633 | return 634 | 635 | self.mqtt3_send_suback(mid, sub_list) 636 | if dup and qos>0 and self._last_mid==mid: 637 | return 638 | 639 | self._last_mid = mid 640 | self._ppmtdb.work_queue.put( (self._ppmtdb.add_sublist, (self._client_id, sub_list)) ) # put a add sub task into queue 641 | self._ppmtdb.work_queue.put( (self._ppmtdb.pub4sub_list, (self, sub_list)) ) # put a pub4sublist task into queue 642 | return 643 | 644 | def verify_subtopic_ok(self, topic): 645 | if topic.count('#')>1: 646 | return False 647 | if '#' in topic and topic[-1] != '#': 648 | return False 649 | token_list = filter(None, topic.split('/')) 650 | for token in token_list: 651 | if token.count('+') > 1: 652 | return False 653 | #return True 654 | return self._acl_check(topic, self._sub_acl) 655 | 656 | def verify_pubtopic_ok(self, topic): 657 | if '#' in topic or '+' in topic: 658 | return False 659 | if len(topic)>0: 660 | return self._acl_check(topic, self._pub_acl) 661 | return True 662 | 663 | def mqtt3_send_suback(self, mid, sub_list): 664 | n = len(sub_list) # n bytes for n qos 665 | remain_len = n + 2 # added 2 bytes for mid in variable header 666 | packet = bytearray() 667 | packet.extend(struct.pack("!B", MQTT3_SUBACK)) 668 | self._pack_remain_len(packet, remain_len) 669 | packet.extend(struct.pack("!H", mid)) 670 | for (sub_topic, qos) in sub_list: 671 | packet.extend(struct.pack("!B", qos)) 672 | self.write(packet) 673 | return 674 | 675 | def _pack_remain_len(self, packet, len): 676 | if len > 268435455: 677 | return 678 | while True: 679 | byte = len % 128 680 | len = len // 128 681 | # If there are more digits to encode, set the top bit of this digit 682 | if len > 0: 683 | byte = byte | 0x80 684 | packet.extend(struct.pack("!B", byte)) 685 | if len == 0: 686 | return 687 | return 688 | 689 | def mqtt3_unsubscribe_handler(self): 690 | logging.info("mqtt3_unsubscribe_handler..................") 691 | 692 | qos = ( self._fixed_header_byte1 & 0x06 ) >> 1 693 | dup = self._fixed_header_byte1 & 0x08 694 | 695 | if qos != 1 or self._remaining_len != len(self._buff): 696 | self.close() 697 | return 698 | 699 | # get mid 700 | remain_len = self._remaining_len - 2 701 | fmt = "!H" + str(remain_len) + "s" 702 | 703 | mid, payload = struct.unpack(fmt, self._buff) 704 | 705 | unsub_list = [] 706 | remain_len = len(payload) - 2 707 | 708 | while remain_len: 709 | fmt = "!H" + str(remain_len) + "s" 710 | unsub_topic_len, payload = struct.unpack(fmt, payload) 711 | 712 | remain_len = len(payload) - unsub_topic_len 713 | if remain_len: 714 | fmt = "!" + str(unsub_topic_len) + "s" + str(remain_len) + "s" 715 | unsub_topic, payload = struct.unpack(fmt, payload) 716 | remain_len = len(payload) - 2 717 | else: 718 | fmt = "!" + str(unsub_topic_len) + "s" 719 | unsub_topic, = struct.unpack(fmt, payload) 720 | 721 | unsub_list.append(unsub_topic) 722 | 723 | self.mqtt3_send_unsuback(mid) 724 | 725 | if dup and qos>0 and self._last_mid==mid: 726 | return 727 | 728 | self._last_mid = mid 729 | self.unsubs_from_ppmtdb(self._client_id, unsub_list) 730 | return 731 | 732 | def unsubs_from_ppmtdb(self, client_id, unsub_list): 733 | self._ppmtdb.work_queue.put( (self._ppmtdb.unsublist, (self._client_id, unsub_list)) ) # put a add sub task into queue 734 | return 735 | 736 | def mqtt3_send_unsuback(self, mid): 737 | packet = bytearray() 738 | packet.extend( struct.pack("!BBH", MQTT3_UNSUBACK, 2, mid) ) 739 | self.write(packet) 740 | return 741 | 742 | # def read(self, to_read_bytes, read_callback): 743 | # self._stream.read_bytes(to_read_bytes, read_callback) 744 | 745 | def write(self, packet): 746 | self._ppmtdb.send_queue.put( (self,packet) ) # put a pub4sublist task into queue 747 | 748 | def close_for_reconnect(self, new_context, clean): 749 | # logging.info ("close_for_reconnect ============> clean: %s" % clean) 750 | self._connect_state = mq_cs_reconnected 751 | self.close() 752 | 753 | """ 754 | if not clean and self._curr_pub_msg: 755 | new_context._out_mid = self._out_mid 756 | self._curr_pub_msg.context = new_context 757 | new_context._curr_pub_msg = self._curr_pub_msg 758 | if new_context._curr_pub_msg: 759 | #new_context._curr_pub_msg.context = new_context 760 | new_context._curr_pub_msg.reset() # reset to publish original state 761 | new_context.wait_pub_completed_timeout() 762 | 763 | """ 764 | def close(self): 765 | 766 | if self._connect_state == mq_cs_disconnecting: 767 | if self._cnn_flag & 0x02: # clean flag is set (1) 768 | self._ppmtdb.work_queue.put( (self._ppmtdb.remove_cnn, (self,)) ) # put a disconnect task into queue 769 | 770 | elif self._connect_state == mq_cs_connected: # to handle will topic 771 | if self._cnn_flag & 0x04: # will flag is set(1) 772 | qos = (self._cnn_flag & 0x18) >> 3 773 | topic = self._will_topic 774 | message = self._will_message 775 | # print "qos:%d topic: %s message: %s" % (qos, topic, message) 776 | 777 | if self._cnn_flag & 0x20: # will retain is set (1) 778 | self._ppmtdb.work_queue.put( (self._ppmtdb.add_pub, (topic, qos, message)) ) # put add pub task into queue 779 | self._ppmtdb.work_queue.put( (self._ppmtdb.pub4pub_process, (topic, qos, message)) ) # put a pub task into queue 780 | 781 | self._stream.close() 782 | return 783 | 784 | def close_callback(self): 785 | PollIOLoop.instance().remove_timeout(self._timeout_handle) 786 | if self._connect_state == mq_cs_connected: 787 | logging.info(" close_callback --> device_id: %s " % ( self._client_id)) 788 | self._ppmtdb.work_queue.put( (self._ppmtdb.set_inactive_cnn, (self,)) ) # put a set inactive task into queue 789 | return 790 | 791 | def keep_alive_timeout(self): 792 | if not self._keep_alive: # dont set timeout if keep_alive is zero that be set by client 793 | return 794 | logging.info(" keep_alive_timeout --> device_id: %s " % ( self._client_id)) 795 | self.close() 796 | return 797 | 798 | def reset_timeout(self): 799 | if not self._keep_alive: # dont set timeout if keep_alive is zero that be set by client 800 | return 801 | if self._timeout_handle != None: 802 | PollIOLoop.instance().remove_timeout(self._timeout_handle) 803 | self._timeout_handler = None 804 | self._timeout_handle = PollIOLoop.instance().call_later(1.5*self._keep_alive, self.keep_alive_timeout) 805 | logging.info(" reset_timeout --> device_id: %s" % (self._client_id)) 806 | return 807 | 808 | def _acl_check(self, topic, acl): 809 | topic_token_list = filter(None, topic.split('/')) 810 | len0 = len(topic_token_list) 811 | for acl_topic in acl: 812 | acl_token_list = filter(None, acl_topic.split('/')) 813 | len1 = len(acl_token_list) 814 | if len0 == len1: 815 | for i in xrange(len0): 816 | if topic_token_list[i] != acl_token_list[i] and acl_token_list[i] != "+" and acl_token_list[i] != "#": 817 | break 818 | else: return True 819 | elif len0 == len1-1 and acl_token_list[len1-1] == "#": 820 | for i in xrange(len0): 821 | if topic_token_list[i] != acl_token_list[i] and acl_token_list[i] != "+": 822 | break 823 | else: return True 824 | elif len0 > len1: 825 | for i in xrange(len1): 826 | if topic_token_list[i] != acl_token_list[i] and acl_token_list[i] != "+" and acl_token_list[i] != "#": 827 | break 828 | else: return True 829 | return False 830 | 831 | def wait_pub_queue_handler_callback(self): 832 | if self._curr_pub_msg: 833 | return 834 | if self._wait_pub_queue.empty() == True: 835 | return 836 | mid, wait_state, packet, topic, qos = self._wait_pub_queue.get(False) 837 | self._curr_pub_msg = mqtt3_message(self, mid, wait_state, packet, topic, qos) 838 | self._ppmtdb.work_queue.put( (self._ppmtdb.inc_in_flight_o, ()) ) # put an inc in_flight_o into queue 839 | self.write(packet) 840 | return 841 | 842 | def wait_pub_completed_timeout(self): 843 | if self._curr_pub_msg != None: 844 | return 845 | 846 | logging.info ("wait_pub_completed_timeout: resend_times ( %d) " % self._curr_pub_msg.resend_times) 847 | if self._curr_pub_msg.resend_times == 5: 848 | self.close() 849 | else: 850 | self._curr_pub_msg.resend() 851 | self._curr_pub_msg.timeout = PollIOLoop.instance().call_later(7, self.wait_pub_completed_timeout) 852 | return 853 | 854 | 855 | class mqtt3server(TCPServer): 856 | def handle_stream(self, stream, address): 857 | logging.info("yvmqtt3 New connection : (%s) " % str(address)) 858 | mqtt3context(stream) 859 | 860 | class MQTTSrv(Application): 861 | def __init__(self, *args, **kwargs): 862 | super(MQTTSrv, self).__init__(*args, autoreload=True) 863 | return 864 | 865 | def ppmt_main(self): 866 | #print "Mqtt3 Broker start ......" 867 | 868 | logging.info("Mqtt3 Broker start ......") 869 | work_thread = worker(gMQbase.work_queue) 870 | send_thread = sender(gMQbase.send_queue) 871 | 872 | work_thread.start() 873 | send_thread.start() 874 | 875 | server = mqtt3server() 876 | server.listen(1883) 877 | 878 | PollIOLoop.instance().start() 879 | 880 | send_thread.join() 881 | work_thread.join() 882 | 883 | gMQbase.work_queue.join() 884 | gMQbase.send_queue.join() 885 | return 886 | 887 | def ppmt_main(): 888 | _m = MQTTSrv() 889 | _m.ppmt_main() 890 | 891 | def ppmt_set_authenticate(_class): 892 | global gAuth 893 | gAuth = _class() 894 | return 895 | -------------------------------------------------------------------------------- /ppmessage_mqtt/ppmtdb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical, PPMESSAGE 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # Ning Ben, ben.ning@yvertical.com 6 | # 7 | # All rights are reserved. 8 | # 9 | 10 | import threading 11 | import logging 12 | import struct 13 | import time 14 | 15 | from Queue import Queue 16 | from tornado.ioloop import PollIOLoop 17 | 18 | # Worker command 19 | MQTT3_CNNECT = 0x10 20 | MQTT3_CONNACK = 0x20 21 | MQTT3_PUBLISH = 0x30 22 | MQTT3_PUBACK = 0x40 23 | MQTT3_PUBREC = 0x50 24 | MQTT3_PUBREL = 0x60 25 | 26 | 27 | # db cmd code for db maintain 28 | DBCMD_INVALID = 0 29 | DBCMD_DEL_I_MSG = 1 30 | DBCMD_DEL_O_MSG = 2 31 | DBCMD_ADD_I_MSG = 3 32 | DBCMD_UPD_O_MSG = 4 33 | DBCMD_FINISH = 5 34 | 35 | class sys_stat: 36 | def __init__(self): 37 | self._received_bytes_per_sec = 0 38 | self._sent_bytes_per_sec = 0 39 | 40 | self._received_bytes = 0 41 | self._sent_bytes = 0 42 | self._total_received_bytes = 0 43 | self._total_sent_bytes = 0 44 | 45 | self._active_clients = 0 46 | self._inactive_clients = 0 47 | self._total_clients = 0 48 | 49 | self._received_messages_per_sec = 0 50 | self._sent_messages_per_sec = 0 51 | 52 | self._received_messages = 0 53 | self._sent_messages = 0 54 | self._total_received_messages = 0 55 | self._total_sent_messages = 0 56 | 57 | self._received_publish_messages = 0 58 | self._sent_publish_messages = 0 59 | self._subscriptions_count = 0 60 | 61 | self._retain_messages = 0 62 | self._in_flight_o = 0 63 | self._in_flight_i = 0 64 | self._timestamp = time.time() 65 | self._uptime = 0 66 | self._version = "1.00" 67 | 68 | def recalculate(self): 69 | curr_time = time.time() 70 | self._uptime = curr_time - self._timestamp 71 | self._timestamp = curr_time 72 | self._received_bytes_per_sec = long(self._received_bytes / self._uptime) 73 | self._sent_bytes_per_sec = long(self._sent_bytes / self._uptime) 74 | self._received_messages_per_sec = long(self._received_messages / self._uptime) 75 | self._sent_messages_per_sec = long(self._sent_messages / self._uptime) 76 | 77 | self._total_received_bytes += self._received_bytes 78 | self._total_sent_bytes += self._sent_bytes 79 | self._total_received_messages += self._received_messages 80 | self._total_sent_messages += self._sent_messages 81 | 82 | self._received_bytes = 0 83 | self._sent_bytes = 0 84 | self._received_messages = 0 85 | self._sent_messages = 0 86 | 87 | 88 | class yvmq_msg_state: 89 | invalid = 0 90 | wait_puback = 1 91 | wait_pubrec = 2 92 | wait_pubrel = 3 93 | wait_pubcomp = 4 94 | 95 | class mqtt3_message: 96 | def __init__(self, context, mid, state, packet, topic, qos): 97 | self.context = context 98 | self.mid = mid 99 | self.state0 = state 100 | self.state = state 101 | self.packet0 = packet 102 | self.packet = packet 103 | self.topic = topic 104 | self.qos = qos 105 | self.timeout = None 106 | self.resend_times = 0 107 | 108 | def reset(self): 109 | self.state = self.state0 110 | self.packet = self.packet0 111 | self.resend_times = 0 112 | 113 | def resend(self): 114 | self.packet[0] |= 0x08 # dup be set (1) 115 | 116 | self.context.write(self.packet) 117 | self.resend_times += 1 118 | return 119 | 120 | class mqtt3cnn: 121 | def __init__(self, context): 122 | self.client_id = context._client_id 123 | self.cnn_flag = context._cnn_flag 124 | self.context = context 125 | self.subs = {} # { subtopic: qos } 126 | self.wait_pub_queue = Queue() 127 | self.active = True 128 | 129 | def clear(self): 130 | self.subs.clear() 131 | # to empty the queue 132 | while True: 133 | if self.wait_pub_queue.empty() == True: 134 | break 135 | self.wait_pub_queue.get(False) 136 | return 137 | 138 | def resend_message(self): 139 | PollIOLoop.instance().add_callback(self.context.wait_pub_queue_handler_callback) 140 | return 141 | 142 | class mqtt3pub: 143 | def __init__(self, token, qos=0, level=0, message=None, parent=None): 144 | self.token = token 145 | self.qos = qos 146 | self.message= message 147 | self.level = level 148 | self.children = {} # {next_level_token: mqtt3pub} 149 | self.parent = parent 150 | 151 | class mqtt3sub: 152 | def __init__(self, token, qos=0, level=0, client_id=None, parent=None): 153 | self.token = token 154 | self.level = level 155 | self.clients = {} # {client_id: qos} 156 | self.children = {} # {next_level_token: mqtt3sub} 157 | self.parent = parent 158 | 159 | self.add_client(client_id, qos) 160 | 161 | if token is '#' and parent: 162 | parent.add_client(client_id, qos) 163 | 164 | def add_client(self, client_id, qos): 165 | if client_id: 166 | self.clients[client_id] = qos 167 | 168 | def remove_client(self, client_id): 169 | if self.clients.get(client_id): 170 | del self.clients[client_id] 171 | 172 | def clean_client(self): 173 | self.clients = {} 174 | 175 | class ppmtdb(object): 176 | 177 | def __init__(self): 178 | 179 | self.pub_root = mqtt3pub('/') 180 | self.sub_root = mqtt3sub('/') 181 | self.cnns = {} # { client_id: mqtt3cnn } 182 | self.level = 0 183 | self.work_queue = Queue() 184 | self.send_queue = Queue() 185 | self.sys_info = sys_stat() 186 | self.update_sysinfo_timeout = PollIOLoop.instance().add_timeout(self.sys_info._timestamp + 15, self.update_sysinfo_callback) 187 | 188 | def add_pub(self,fulltopic, qos, message): 189 | 190 | 191 | parent = self.pub_root 192 | pub = self.pub_root.children 193 | 194 | token_list = filter(None, fulltopic.split('/')) 195 | i = 0 196 | n = len(token_list) 197 | 198 | for token in token_list: 199 | i += 1 200 | if not pub.get(token): 201 | 202 | self.sys_info._retain_messages += 1 203 | 204 | if i == n: 205 | pub[token] = mqtt3pub(token, qos, i, message, parent) 206 | else: 207 | pub[token] = mqtt3pub(token, qos, i, None, parent) 208 | parent = pub[token] 209 | pub = pub[token].children 210 | else: 211 | if i == n: 212 | pub[token].qos = qos 213 | pub[token].message = message 214 | else: 215 | parent = pub[token] 216 | pub = pub[token].children 217 | 218 | def unpub(self,fulltopic, qos): 219 | 220 | parent = self.pub_root 221 | pub = self.pub_root.children 222 | curr_pub = self.pub_root 223 | 224 | token_list = filter(None, fulltopic.split('/')) 225 | i = 0 226 | n = len(token_list) 227 | matched = False 228 | 229 | for token in token_list: 230 | i += 1 231 | if token in pub: 232 | if i == n: 233 | matched = True 234 | self.sys_info._retain_messages -= 1 235 | pub[token].message = None 236 | curr_pub = pub[token] 237 | else: 238 | parent = pub[token] 239 | pub = pub[token].children 240 | else: 241 | break 242 | 243 | if matched: 244 | while curr_pub.level and not len(curr_pub.children) and not curr_pub.message: 245 | token = curr_pub.token 246 | parent = curr_pub.parent 247 | del parent.children[token] 248 | curr_pub = parent 249 | 250 | 251 | def add_sub(self,fulltopic, qos, client_id=None): 252 | 253 | parent = self.sub_root 254 | sub = self.sub_root.children 255 | 256 | token_list = filter(None, fulltopic.split('/')) 257 | 258 | i = 0 259 | n = len(token_list) 260 | 261 | for token in token_list: 262 | i += 1 263 | if not sub.get(token): 264 | if i == n: 265 | sub[token] = mqtt3sub(token, qos, i, client_id, parent) 266 | else: 267 | sub[token] = mqtt3sub(token, qos, i, None, parent) 268 | parent = sub[token] 269 | sub = sub[token].children 270 | else: 271 | if i == n: 272 | sub[token].add_client(client_id, qos) 273 | if token is '#' and parent.level: 274 | parent.add_client(client_id, qos) 275 | else: 276 | parent = sub[token] 277 | sub = sub[token].children 278 | 279 | if self.cnns.has_key(client_id): 280 | self.cnns[client_id].subs[fulltopic] = qos 281 | self.sys_info._subscriptions_count += 1 282 | 283 | def unsub(self, topic, client_id): 284 | parent = self.sub_root 285 | curr_sub = self.sub_root 286 | matched = False 287 | sub = self.sub_root.children 288 | token_list = filter(None, topic.split('/')) 289 | 290 | i = 0 291 | n = len(token_list) 292 | 293 | for token in token_list: 294 | i += 1 295 | if token in sub: 296 | if i == n: 297 | matched = True 298 | curr_sub = sub[token] 299 | sub[token].remove_client(client_id) 300 | if token is '#' and parent.level: 301 | parent.remove_client(client_id) 302 | else: 303 | parent = sub[token] 304 | sub = sub[token].children 305 | else: 306 | break 307 | 308 | if matched: 309 | while curr_sub.level and not len(curr_sub.children): 310 | token = curr_sub.token 311 | parent = curr_sub.parent 312 | del parent.children[token] 313 | curr_sub = parent 314 | 315 | if client_id in self.cnns and topic in self.cnns[client_id].subs: 316 | del self.cnns[client_id].subs[topic] 317 | self.sys_info._subscriptions_count -= 1 318 | 319 | def add_cnn(self, context, clean=True): 320 | 321 | if not context: return 322 | 323 | if clean: 324 | if context._client_id in self.cnns: 325 | old_context = self.cnns[context._client_id].context 326 | if context is not old_context: 327 | PollIOLoop.instance().add_callback(old_context.close_for_reconnect, context, clean) 328 | 329 | if not self.cnns[context._client_id].active: 330 | self.sys_info._active_clients += 1 331 | self.sys_info._inactive_clients -= 1 332 | self.cnns[context._client_id].active = True 333 | 334 | self.cnns[context._client_id].clear() 335 | self.cnns[context._client_id].context = context 336 | self.cnns[context._client_id].cnn_flag = context._cnn_flag 337 | else: 338 | self.sys_info._total_clients += 1 339 | self.sys_info._active_clients += 1 340 | self.cnns[context._client_id] = mqtt3cnn(context) 341 | 342 | context._wait_pub_queue = self.cnns[context._client_id].wait_pub_queue 343 | else: 344 | if context._client_id in self.cnns: 345 | old_context = self.cnns[context._client_id].context 346 | if context is not old_context: 347 | PollIOLoop.instance().add_callback(old_context.close_for_reconnect, context, clean) 348 | 349 | self.cnns[context._client_id].context = context 350 | self.cnns[context._client_id].cnn_flag = context._cnn_flag 351 | if not self.cnns[context._client_id].active: 352 | self.cnns[context._client_id].active = True 353 | self.sys_info._active_clients += 1 354 | self.sys_info._inactive_clients -= 1 355 | context._wait_pub_queue = self.cnns[context._client_id].wait_pub_queue 356 | else: 357 | self.cnns[context._client_id] = mqtt3cnn(context) 358 | self.sys_info._active_clients += 1 359 | self.sys_info._total_clients += 1 360 | context._wait_pub_queue = self.cnns[context._client_id].wait_pub_queue 361 | 362 | def _clean_sub(self, context): 363 | if not context: return 364 | if context._client_id in self.cnns: 365 | topic_list = self.cnns[context._client_id].subs.keys() 366 | for topic in topic_list: 367 | self.unsub(topic, context._client_id) 368 | 369 | def remove_cnn(self, context): 370 | if not context: return 371 | self._clean_sub(context) 372 | if context._client_id in self.cnns: 373 | if self.cnns[context._client_id].active: 374 | self.sys_info._active_clients -= 1 375 | else: 376 | self.sys_info._inactive_clients -= 1 377 | 378 | self.sys_info._total_clients -= 1 379 | 380 | self.cnns[context._client_id].clear() 381 | del self.cnns[context._client_id] 382 | 383 | 384 | def inc_in_flight_i(self): 385 | self.sys_info._in_flight_i += 1 386 | 387 | def dec_in_flight_i(self): 388 | self.sys_info._in_flight_i -= 1 389 | 390 | def inc_in_flight_o(self): 391 | self.sys_info._in_flight_o += 1 392 | 393 | def dec_in_flight_o(self): 394 | self.sys_info._in_flight_o -= 1 395 | 396 | def _pack_remain_len(self, packet, len): 397 | if len > 268435455: 398 | return 399 | while True: 400 | byte = len % 128 401 | len = len // 128 402 | # If there are more digits to encode, set the top bit of this digit 403 | if len > 0: 404 | byte = byte | 0x80 405 | 406 | packet.extend(struct.pack("!B", byte)) 407 | if len == 0: 408 | break 409 | return 410 | 411 | # publish message (to check already exits publish and send) message to this subscriber 412 | def pub4sub_list(self, context, sub_list): 413 | 414 | pub_dict = self.pub_root.children 415 | for (topic, qos) in sub_list: 416 | pub_token_list = [] 417 | sub_token_list = filter(None, topic.split('/'))[::-1] # reverse the list for recursive call 418 | self.pub4sub(context, sub_token_list, qos, pub_token_list, pub_dict, False) 419 | # if ret == DBCMD_FINISH: 420 | # break 421 | 422 | def pub4sub(self, context, sub_token_list, qos, pub_token_list, pub_dict, multi_level): 423 | # cmd = DBCMD_INVALID 424 | if len(sub_token_list): 425 | sub_token = sub_token_list.pop() # pop sub_list 426 | multi_level = False 427 | if sub_token is '+': 428 | for pub_token in sorted(pub_dict): 429 | pub_token_list.append(pub_token) # pubtopic push 430 | next_pub_dict = pub_dict[pub_token].children 431 | if len(sub_token_list): 432 | if sub_token_list[-1] is '#' and pub_dict[pub_token].message: 433 | pub_qos = min(qos, pub_dict[pub_token].qos) 434 | msg = pub_dict[pub_token].message 435 | topic = "/".join(pub_token_list) 436 | self.mqtt3_send_publish(context, topic, pub_qos, msg) 437 | if len(next_pub_dict): 438 | self.pub4sub(context, sub_token_list, qos, pub_token_list, next_pub_dict, multi_level) 439 | 440 | elif pub_dict[pub_token].message: 441 | pub_qos = min(qos, pub_dict[pub_token].qos) 442 | msg = pub_dict[pub_token].message 443 | topic = "/".join(pub_token_list) 444 | self.mqtt3_send_publish(context, topic, pub_qos, msg) 445 | 446 | pub_token_list.pop() # pub topic pop 447 | # if cmd == DBCMD_FINISH: 448 | # break 449 | 450 | 451 | elif sub_token is '#': 452 | multi_level = True 453 | for pub_token in sorted(pub_dict): 454 | pub_token_list.append(pub_token) # push 455 | if pub_dict[pub_token].message: 456 | pub_qos = min(qos, pub_dict[pub_token].qos) 457 | msg = pub_dict[pub_token].message 458 | topic = "/".join(pub_token_list) 459 | self.mqtt3_send_publish(context,topic, pub_qos, msg) 460 | next_pub_dict = pub_dict[pub_token].children 461 | if len(next_pub_dict): 462 | self.pub4sub(context, sub_token_list, qos, pub_token_list, next_pub_dict, multi_level) 463 | pub_token_list.pop() # pop 464 | # if cmd == DBCMD_FINISH: 465 | # break 466 | 467 | elif sub_token in pub_dict: 468 | pub_token_list.append(sub_token) # push 469 | 470 | if len(sub_token_list): 471 | 472 | if sub_token_list[-1] is '#' and pub_dict[sub_token].message: 473 | pub_qos = min(qos, pub_dict[sub_token].qos) 474 | msg = pub_dict[sub_token].message 475 | topic = "/".join(pub_token_list) 476 | self.mqtt3_send_publish(context, topic, pub_qos, msg) 477 | 478 | next_pub_dict = pub_dict[sub_token].children 479 | if len(next_pub_dict): 480 | self.pub4sub(context, sub_token_list, qos, pub_token_list, next_pub_dict, multi_level) 481 | else: 482 | if pub_dict[sub_token].message: 483 | pub_qos = min(qos, pub_dict[sub_token].qos) 484 | msg = pub_dict[sub_token].message 485 | topic = "/".join(pub_token_list) 486 | self.mqtt3_send_publish(context, topic, pub_qos, msg) 487 | 488 | pub_token_list.pop() # pop 489 | 490 | sub_token_list.append(sub_token) # push sub_list 491 | 492 | elif multi_level: 493 | for pub_token in sorted(pub_dict): 494 | pub_token_list.append(pub_token) # push 495 | 496 | if pub_dict[pub_token].message: 497 | pub_qos = min(qos, pub_dict[pub_token].qos) 498 | msg = pub_dict[pub_token].message 499 | topic = "/".join(pub_token_list) 500 | self.mqtt3_send_publish(context, topic, pub_qos, msg) 501 | 502 | next_pub_dict = pub_dict[pub_token].children 503 | if len(next_pub_dict): 504 | self.pub4sub(context, sub_token_list, qos, pub_token_list, next_pub_dict, multi_level) 505 | 506 | pub_token_list.pop() # pop 507 | # if cmd == DBCMD_FINISH: 508 | # break 509 | # return cmd 510 | 511 | # publish message (to check already exits subscribe and send) for this publish 512 | def pub4pub(self, topic, qos, message, pub_token_list, sub_dict): 513 | 514 | pub_token = pub_token_list.pop() 515 | 516 | if sub_dict.has_key(pub_token): 517 | if len(pub_token_list): 518 | next_sub_dict = sub_dict[pub_token].children 519 | self.pub4pub(topic, qos, message, pub_token_list, next_sub_dict) 520 | else: 521 | for client_id in sub_dict[pub_token].clients: 522 | if self.cnns[client_id].active: 523 | pub_qos = min( qos, sub_dict[pub_token].clients[client_id] ) 524 | context = self.cnns[client_id].context 525 | self.mqtt3_send_publish(context,topic, pub_qos, message) # send publish message to the client (with client_id) 526 | else: 527 | pub_qos = min( qos, sub_dict[pub_token].clients[client_id] ) 528 | context = self.cnns[client_id].context 529 | 530 | if sub_dict.has_key('+'): 531 | if len(pub_token_list): 532 | next_sub_dict = sub_dict['+'].children 533 | self.pub4pub(topic, qos, message, pub_token_list, next_sub_dict) 534 | else: 535 | for client_id in sub_dict['+'].clients: 536 | if self.cnns[client_id].active: 537 | pub_qos = min( qos, sub_dict['+'].clients[client_id] ) 538 | context = self.cnns[client_id].context 539 | self.mqtt3_send_publish(context, topic, pub_qos, message) # send publish message to the client (with client_id) 540 | else: 541 | pub_qos = min( qos, sub_dict['+'].clients[client_id] ) 542 | context = self.cnns[client_id].context 543 | 544 | 545 | if sub_dict.has_key('#'): 546 | for client_id in sub_dict['#'].clients: 547 | if self.cnns[client_id].active: 548 | pub_qos = min( qos, sub_dict['#'].clients[client_id] ) 549 | context = self.cnns[client_id].context 550 | self.mqtt3_send_publish(context,topic, pub_qos, message) # send publish message to the client (with client_id) 551 | else: 552 | pub_qos = min( qos, sub_dict['#'].clients[client_id] ) 553 | context = self.cnns[client_id].context 554 | logging.info("inactive pubtopic: %s device_id: %s " % (topic, context._client_id)) 555 | 556 | pub_token = pub_token_list.append(pub_token) 557 | return 558 | 559 | def mqtt3_send_publish(self, context, topic, qos, message): 560 | 561 | logging.info("sended pub --> topic: %s device_id: %s " % (topic, context._client_id)) 562 | 563 | packet = bytearray() 564 | packet.extend(struct.pack("!B", MQTT3_PUBLISH | (qos << 1) )) 565 | 566 | topic_len = len(topic) 567 | msg_len = 0 if message == None else len(message) 568 | remain_len = 2 + topic_len + msg_len 569 | 570 | if qos: 571 | remain_len += 2 # for mid 572 | 573 | self._pack_remain_len(packet, remain_len) 574 | 575 | fmt = "!H" + str(topic_len) + "s" 576 | 577 | # topic_len + topic 578 | packet.extend(struct.pack(fmt, topic_len, topic)) 579 | 580 | if qos: # for mid 581 | context._out_mid += 1 582 | if 65536==context._out_mid: 583 | context._out_mid = 1 584 | 585 | packet.extend(struct.pack("!H", context._out_mid)) 586 | 587 | if msg_len: # for payload 588 | fmt = "!" + str(msg_len) + "s" 589 | packet.extend(struct.pack(fmt, message)) 590 | 591 | mid = context._out_mid 592 | 593 | wait_state = yvmq_msg_state.invalid 594 | 595 | if qos == 1: 596 | wait_state = yvmq_msg_state.wait_puback 597 | elif qos == 2: 598 | wait_state = yvmq_msg_state.wait_pubrec 599 | 600 | if wait_state: 601 | # self.inc_in_flight_o() 602 | # context._wait_pub_queue.put( (mid, wait_state, packet) ) 603 | 604 | context._wait_pub_queue.put( (mid, wait_state, packet, topic, qos) ) # modify on June 14, 2014 for PPMESSAGE just send onece. Ben Ning 605 | 606 | # PollIOLoop.instance().add_callback(context.wait_pub_queue_handler_callback, MQTT3_PUBLISH) 607 | PollIOLoop.instance().add_callback(context.wait_pub_queue_handler_callback) 608 | else: 609 | self.send_queue.put( (context, packet) ) # put a send packet task into send_queue 610 | 611 | self.sys_info._sent_publish_messages += 1 612 | 613 | # return cmd 614 | 615 | 616 | def pub4pub_process(self, topic, qos, message): 617 | # topic, qos, message = pub_tuple 618 | self.sys_info._received_publish_messages += 1 619 | 620 | pub_token_list = filter(None, topic.split('/'))[::-1] # reverse the list for recursive call 621 | sub_dict = self.sub_root.children 622 | 623 | self.pub4pub(topic, qos, message, pub_token_list, sub_dict) 624 | 625 | def add_sublist(self, client_id, sub_list): 626 | for (sub_topic, qos) in sub_list: 627 | self.add_sub(sub_topic, qos, client_id) 628 | 629 | def unsublist(self, client_id, unsub_list): 630 | for topic in unsub_list: 631 | self.unsub(topic, client_id) 632 | 633 | def add_sent_bytes(self, sent_bytes): 634 | self.sys_info._sent_bytes += sent_bytes 635 | self.sys_info._sent_messages += 1 636 | # self.update_sys_info_topic() 637 | 638 | def add_received_bytes(self, received_bytes): 639 | self.sys_info._received_bytes += received_bytes 640 | # self.update_sys_info_topic() 641 | 642 | def add_received_message(self, received_messages): 643 | self.sys_info._received_messages += received_messages 644 | 645 | def set_inactive_cnn(self, context): 646 | if context._client_id in self.cnns: 647 | if self.cnns[context._client_id].active: 648 | self.cnns[context._client_id].active = False 649 | self.sys_info._active_clients -= 1 650 | self.sys_info._inactive_clients += 1 651 | logging.info("set_inactive_cnn: device_id: %s " % (context._client_id)) 652 | return 653 | 654 | def pub_sysinfo(self, topic, qos, message): 655 | pub_token_list = filter(None, topic.split('/'))[::-1] # reverse the list for recursive call 656 | sub_dict = self.sub_root.children 657 | self.pub4pub(topic, qos, message, pub_token_list, sub_dict) 658 | 659 | def update_sys_info_topic(self): 660 | qos = 0 661 | self.pub_sysinfo( "$SYS/stat/clients/active", qos, str(self.sys_info._active_clients) ) 662 | self.pub_sysinfo( "$SYS/stat/clients/inactive", qos, str(self.sys_info._inactive_clients) ) 663 | self.pub_sysinfo( "$SYS/stat/clients/total", qos, str(self.sys_info._total_clients) ) 664 | 665 | self.pub_sysinfo( "$SYS/stat/bytes/received", qos, str(self.sys_info._total_received_bytes) ) 666 | self.pub_sysinfo( "$SYS/stat/bytes/per_second/received", qos, str(self.sys_info._received_bytes_per_sec) ) 667 | self.pub_sysinfo( "$SYS/stat/bytes/sent", qos, str(self.sys_info._total_sent_bytes) ) 668 | self.pub_sysinfo( "$SYS/stat/bytes/per_second/sent", qos, str(self.sys_info._sent_bytes_per_sec) ) 669 | 670 | self.pub_sysinfo( "$SYS/stat/message/received", qos, str(self.sys_info._total_received_messages) ) 671 | self.pub_sysinfo( "$SYS/stat/message/per_second/received", qos, str(self.sys_info._received_messages_per_sec) ) 672 | self.pub_sysinfo( "$SYS/stat/message/sent", qos, str(self.sys_info._total_sent_messages) ) 673 | self.pub_sysinfo( "$SYS/stat/message/per_second/sent", qos, str(self.sys_info._sent_messages_per_sec) ) 674 | self.pub_sysinfo( "$SYS/stat/message/retain", qos, str(self.sys_info._retain_messages) ) 675 | self.pub_sysinfo( "$SYS/stat/message/in_flight_in", qos, str(self.sys_info._in_flight_i) ) 676 | self.pub_sysinfo( "$SYS/stat/message/in_flight_out", qos, str(self.sys_info._in_flight_o) ) 677 | 678 | self.pub_sysinfo( "$SYS/stat/publish/messages/received", qos, str(self.sys_info._received_publish_messages) ) 679 | self.pub_sysinfo( "$SYS/stat/publish/messages/sent", qos, str(self.sys_info._sent_publish_messages) ) 680 | 681 | self.pub_sysinfo( "$SYS/stat/subscriptions/count", qos, str(self.sys_info._subscriptions_count) ) 682 | self.pub_sysinfo( "$SYS/stat/timestamp", qos, str(self.sys_info._timestamp) ) 683 | 684 | 685 | def update_sysinfo_callback(self): 686 | # self.recount_cnns() 687 | self.sys_info.recalculate() 688 | 689 | self.work_queue.put( (self.update_sys_info_topic, ()) ) 690 | self.update_sysinfo_timeout = PollIOLoop.instance().add_timeout(self.sys_info._timestamp + 15, self.update_sysinfo_callback) 691 | 692 | 693 | 694 | class worker(threading.Thread): 695 | def __init__(self, work_queue): 696 | super(worker, self).__init__() 697 | self.setDaemon(True) 698 | self.queue = work_queue 699 | 700 | def run(self): 701 | while True: 702 | try: 703 | callback_fun, args = self.queue.get() 704 | callback_fun(*args) 705 | finally: 706 | self.queue.task_done() 707 | 708 | 709 | class sender(threading.Thread): 710 | def __init__(self, send_queue): 711 | super(sender, self).__init__() 712 | self.setDaemon(True) 713 | self.queue = send_queue 714 | 715 | def run(self): 716 | while True: 717 | try: 718 | context, packet = self.queue.get() 719 | buff = str(packet) 720 | PollIOLoop.instance().add_callback(self.ioloop_callback, context, buff) 721 | finally: 722 | self.queue.task_done() 723 | 724 | # The callback will be call in main thread 725 | def ioloop_callback(self, context, buff): 726 | if not context._stream.closed(): 727 | sent_bytes = len(buff) 728 | context._stream.write(buff) 729 | context._ppmtdb.work_queue.put( (context._ppmtdb.add_sent_bytes, (sent_bytes,)) ) # put add pub task into queue 730 | # else: 731 | # context.res_queue.put( (DBCMD_FINISH, None) ) 732 | 733 | 734 | 735 | -------------------------------------------------------------------------------- /ppmessage_mqtt/test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical. PPMESSAGE. 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # 6 | -------------------------------------------------------------------------------- /ppmessage_mqtt/test/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical. PPMESSAGE. 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # 6 | 7 | from mqttclient import MQTTClient 8 | 9 | import uuid 10 | import unittest 11 | import logging 12 | 13 | class TestMQTTClient(unittest.TestCase): 14 | 15 | def test_publishOne(self): 16 | #connect 17 | #publishone 18 | #disconnect 19 | #connect 20 | #publishone 21 | #... 22 | #publishone 23 | #disconnect 24 | topic = "test_topic" 25 | message = "test_message" 26 | mqtt_client = MQTTClient() 27 | 28 | mqtt_client.connect() 29 | mqtt_client.publish_one(topic, message) 30 | mqtt_client.publish_one(topic, message) 31 | #raw_input("Wait for restart push server and return.") 32 | mqtt_client.publish_one(topic, message) 33 | mqtt_client.publish_one(topic, message) 34 | mqtt_client.publish_one(topic, message) 35 | mqtt_client.start_send() 36 | mqtt_client.disconnect() 37 | 38 | 39 | 40 | if __name__ == "__main__": 41 | logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /ppmessage_mqtt/test/mqttclient.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical. PPMESSAGE. 4 | # Guijin Ding, guijin.ding@yvertical.com 5 | # 6 | 7 | from paho.mqtt import client as mqttc 8 | from Queue import Queue 9 | 10 | import json 11 | import logging 12 | import datetime 13 | 14 | MQTT_PORT = 1883 15 | MQTT_HOST = "127.0.0.1" 16 | 17 | class MQTTClient(): 18 | 19 | def __init__(self): 20 | self._mqttc = mqttc.Client("admins") 21 | self._mqttc.on_message = self.mqtt_on_message 22 | self._mqttc.on_connect = self.mqtt_on_connect 23 | self._mqttc.on_publish = self.mqtt_on_publish 24 | self._mqttc.on_subscribe = self.mqtt_on_subscribe 25 | self._mqttc.on_disconnect = self.mqtt_on_disconnect 26 | self._mqttc_connection_status = "NULL" 27 | self._active_time = datetime.datetime.now() 28 | self._push_queue = Queue() 29 | self._published = set() 30 | return 31 | 32 | def _conn_str(self, _code): 33 | _d = { 34 | 0: "Connection successful", 35 | 1: "Connection refused - incorrect protocol version", 36 | 2: "Connection refused - invalid client identifier", 37 | 3: "Connection refused - server unavailable", 38 | 4: "Connection refused - bad username or password", 39 | 5: "Connection refused - not authorised", 40 | } 41 | _str = _d.get(_code) 42 | if _str == None: 43 | _str = "unknown error" 44 | return _str 45 | 46 | def mqtt_on_connect(self, mqttc, userdata, flags, rc): 47 | logging.info("mqtt_on_connect rc: " + self._conn_str(rc)) 48 | if rc == 0: 49 | self._mqttc_connection_status = "CONNECTED" 50 | return 51 | 52 | def mqtt_on_message(self, mqttc, userdata, msg): 53 | logging.info(msg.topic+" "+str(msg.qos)+" "+str(msg.payload)) 54 | return 55 | 56 | def mqtt_on_publish(self, mqttc, userdata, mid): 57 | logging.info("published mid: " + str(mid)) 58 | if mid in self._published: 59 | self._published.remove(mid) 60 | self._active_time = datetime.datetime.now() 61 | return 62 | 63 | def mqtt_on_subscribe(self, mqttc, userdata, mid, granted_qos): 64 | logging.info("Subscribed: "+str(mid)+" "+str(granted_qos)) 65 | return 66 | 67 | def mqtt_on_log(self, mqttc, userdata, level, string): 68 | logging.info("on-log:" + string) 69 | return 70 | 71 | def mqtt_on_disconnect(self, mqttc, userdata, rc): 72 | logging.info("mqtt-on-disconnect:" + str(rc)) 73 | self._mqttc_connection_status = "NULL" 74 | return 75 | 76 | def username_pw_set(self, user, password): 77 | self._mqttc.username_pw_set(user, password) 78 | 79 | def connect(self): 80 | if self._mqttc_connection_status == "CONNECTED": 81 | return 82 | 83 | self._mqttc_connection_status = "CONNECTING" 84 | self._published = set() 85 | 86 | # server will auto disconnect after 120" 87 | # client should disconnect before server 88 | # for mqttclient can not get the server disconnect event?? 89 | _r = self._mqttc.connect(MQTT_HOST, MQTT_PORT, 120) 90 | return 91 | 92 | def publish_one(self, _token, _body): 93 | #topic, payload, qos=1, retain=True): 94 | qos = 1 95 | retain = True 96 | _body = json.dumps(_body) 97 | self._push_queue.put([_token, _body, qos, retain]) 98 | return True 99 | 100 | def disconnect(self): 101 | if self._mqttc_connection_status == "NULL": 102 | return 103 | self._mqttc_connection_status = "NULL" 104 | self._mqttc.disconnect() 105 | return 106 | 107 | def outdate(self, _delta): 108 | if self._mqttc_connection_status == "NULL": 109 | return 110 | _now = datetime.datetime.now() 111 | if _now - self._active_time > _delta: 112 | self.disconnect() 113 | return 114 | 115 | def loop(self): 116 | if self._mqttc_connection_status == "NULL": 117 | self.connect() 118 | return 119 | 120 | if self._mqttc_connection_status != "CONNECTED": 121 | logging.info("looping for: " + self._mqttc_connection_status) 122 | self._mqttc.loop() 123 | return 124 | 125 | if self._push_queue.empty() == True: 126 | if len(self._published) > 0: 127 | self._mqttc.loop() 128 | return 129 | 130 | _push = self._push_queue.get(False) 131 | if _push == None: 132 | if len(self._published) > 0: 133 | self._mqttc.loop() 134 | return 135 | self._push_queue.task_done() 136 | 137 | result, mid = self._mqttc.publish(*_push) 138 | if result == mqttc.MQTT_ERR_SUCCESS: 139 | self._published.add(mid) 140 | elif result == mqttc.MQTT_ERR_NO_CONN: 141 | self._push_queue.put(_push) 142 | self.connect() 143 | else: 144 | self._push_queue.put(_push) 145 | logging.info("WHAT HAPPEND?? %d" % result) 146 | 147 | self._mqttc.loop() 148 | return 149 | 150 | def start_send(self): 151 | while True: 152 | self.loop() 153 | 154 | if self._push_queue.empty() == True and len(self._published) == 0: 155 | logging.info("nothing to push") 156 | break 157 | 158 | if self._mqttc_connection_status == "NULL": 159 | logging.info("mqttclient connection error") 160 | break 161 | return 162 | -------------------------------------------------------------------------------- /ppmessage_mqtt/yourauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical, PPMESSAGE 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # Ning Ben, ben.ning@yvertical.com 6 | # 7 | # All rights are reserved. 8 | # 9 | 10 | from .ppauth import authenticate 11 | 12 | class your_authenticate(authenticate): 13 | def verify_client_id(self, client_id): 14 | return True 15 | def verify_user_password(self, user_id, password): 16 | return True 17 | 18 | 19 | -------------------------------------------------------------------------------- /pypi.sh: -------------------------------------------------------------------------------- 1 | sudo python setup.py register sdist upload 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2010-2016 YVertical, PPMESSAGE 4 | # Ding Guijin, guijin.ding@yvertical.com 5 | # Ning Ben, ben.ning@yvertical.com 6 | # 7 | # All rights reserved 8 | # 9 | 10 | from setuptools import setup, find_packages 11 | from distutils.core import setup, Extension 12 | import os 13 | 14 | setup( 15 | name = "ppmessage-mqtt", 16 | version = "1.0.11", 17 | author = "ppmessage.com", 18 | author_email = 'dingguijin@gmail.com', 19 | license = "http://www.apache.org/licenses/LICENSE-2.0", 20 | install_requires = ["tornado>=4.3"], 21 | packages = ["ppmessage_mqtt"], 22 | url = "http://www.ppmessage.com", 23 | keywords = "mqtt server ppmessage", 24 | description="A Python mqtt server, originally developed at PPMessage.", 25 | classifiers=[ 26 | 'License :: OSI Approved :: Apache Software License', 27 | 'Programming Language :: Python :: 2.7', 28 | ], 29 | 30 | ) 31 | --------------------------------------------------------------------------------