You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
7.0 KiB
234 lines
7.0 KiB
"""Ezviz cloud MQTT client for push messages.""" |
|
import os |
|
import logging |
|
logging.basicConfig( |
|
format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s - %(message)s', |
|
level=logging.DEBUG if os.environ.get('DEBUG') else logging.INFO) |
|
|
|
import base64 |
|
import json |
|
import threading |
|
import time |
|
|
|
import paho.mqtt.client as mqtt |
|
import requests |
|
from pyezviz.constants import DEFAULT_TIMEOUT, FEATURE_CODE |
|
from pyezviz.exceptions import HTTPError, InvalidURL, PyEzvizError |
|
|
|
API_ENDPOINT_SERVER_INFO = "/v3/configurations/system/info" |
|
API_ENDPOINT_REGISTER_MQTT = "/v1/getClientId" |
|
API_ENDPOINT_START_MQTT = "/api/push/start" |
|
API_ENDPOINT_STOP_MQTT = "/api/push/stop" |
|
|
|
|
|
MQTT_APP_KEY = "4c6b3cc2-b5eb-4813-a592-612c1374c1fe" |
|
APP_SECRET = "17454517-cc1c-42b3-a845-99b4a15dd3e6" |
|
|
|
|
|
def on_subscribe(client, userdata, mid, granted_qos): |
|
"""On MQTT message subscribe.""" |
|
# pylint: disable=unused-argument |
|
logging.info("Subscribed: " + str(mid) + " " + str(granted_qos)) |
|
|
|
|
|
def on_connect(client, userdata, flags, return_code): |
|
"""On MQTT connect.""" |
|
# pylint: disable=unused-argument |
|
if return_code == 0: |
|
logging.debug("connected OK Returned code=%s", return_code) |
|
else: |
|
logging.info("Bad connection Returned code=%s", return_code) |
|
client.reconnect() |
|
|
|
|
|
#def on_message(client, userdata, msg): |
|
# """On MQTT message receive.""" |
|
# # pylint: disable=unused-argument |
|
# mqtt_message = json.loads(msg.payload) |
|
# mqtt_message["ext"] = mqtt_message["ext"].split(",") |
|
# |
|
# # Print payload message |
|
# decoded_message = {mqtt_message['ext'][2]:{'id':mqtt_message['id'], 'alert':mqtt_message['alert'], 'time':mqtt_message['ext'][1], 'alert type':mqtt_message['ext'][4], 'image':mqtt_message['ext'][16]}} |
|
# print(decoded_message) |
|
|
|
|
|
class MQTTClient(threading.Thread): |
|
"""Open MQTT connection to ezviz cloud.""" |
|
|
|
def __init__( |
|
self, |
|
token, |
|
callback, |
|
timeout=DEFAULT_TIMEOUT, |
|
): |
|
"""Initialize the client object.""" |
|
threading.Thread.__init__(self) |
|
self._session = None |
|
self._token = token or { |
|
"session_id": None, |
|
"rf_session_id": None, |
|
"username": None, |
|
"api_url": "apiieu.ezvizlife.com", |
|
} |
|
self._callback = callback |
|
self._timeout = timeout |
|
self._stop_event = threading.Event() |
|
self._mqtt_data = { |
|
"mqtt_clientid": None, |
|
"ticket": None, |
|
"push_url": token["service_urls"]["pushAddr"], |
|
} |
|
|
|
def _mqtt(self): |
|
"""Receive MQTT messages from ezviz server""" |
|
|
|
ezviz_mqtt_client = mqtt.Client( |
|
client_id=self._mqtt_data["mqtt_clientid"], protocol=4, transport="tcp" |
|
) |
|
ezviz_mqtt_client.on_connect = on_connect |
|
ezviz_mqtt_client.on_subscribe = on_subscribe |
|
ezviz_mqtt_client.on_message = self._callback |
|
ezviz_mqtt_client.username_pw_set(MQTT_APP_KEY, APP_SECRET) |
|
|
|
ezviz_mqtt_client.connect(self._mqtt_data["push_url"], 1882, 60) |
|
ezviz_mqtt_client.subscribe( |
|
f"{MQTT_APP_KEY}/ticket/{self._mqtt_data['ticket']}", qos=2 |
|
) |
|
|
|
ezviz_mqtt_client.loop_start() |
|
return ezviz_mqtt_client |
|
|
|
def _register_ezviz_push(self): |
|
"""Register for push messages.""" |
|
|
|
auth_seq = base64.b64encode(f"{MQTT_APP_KEY}:{APP_SECRET}".encode("ascii")) |
|
auth_seq = "Basic " + auth_seq.decode() |
|
|
|
payload = { |
|
"appKey": MQTT_APP_KEY, |
|
"clientType": "5", |
|
"mac": FEATURE_CODE, |
|
"token": "123456", |
|
"version": "v1.3.0", |
|
} |
|
|
|
try: |
|
req = self._session.post( |
|
f"https://{self._mqtt_data['push_url']}{API_ENDPOINT_REGISTER_MQTT}", |
|
allow_redirects=False, |
|
headers={"Authorization": auth_seq}, |
|
data=payload, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.ConnectionError as err: |
|
raise InvalidURL("A Invalid URL or Proxy error occured") from err |
|
|
|
except requests.HTTPError as err: |
|
raise HTTPError from err |
|
|
|
try: |
|
json_result = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
self._mqtt_data["mqtt_clientid"] = json_result["data"]["clientId"] |
|
|
|
def run(self): |
|
"""Method representing the thread's activity which should not be used directly.""" |
|
|
|
if self._session is None: |
|
self._session = requests.session() |
|
self._session.headers.update( |
|
{"User-Agent": "okhttp/3.12.1"} |
|
) # Android generic user agent. |
|
|
|
self._register_ezviz_push() |
|
self._start_ezviz_push() |
|
self._mqtt() |
|
|
|
while not self._stop_event.is_set(): |
|
time.sleep(1) |
|
|
|
def start(self): |
|
"""Start mqtt. |
|
Start mqtt thread |
|
""" |
|
super().start() |
|
|
|
def stop(self): |
|
"""Stop push notifications.""" |
|
|
|
payload = { |
|
"appKey": MQTT_APP_KEY, |
|
"clientId": self._mqtt_data["mqtt_clientid"], |
|
"clientType": 5, |
|
"sessionId": self._token["session_id"], |
|
"username": self._token["username"], |
|
} |
|
|
|
try: |
|
req = self._session.post( |
|
f"https://{self._mqtt_data['push_url']}{API_ENDPOINT_STOP_MQTT}", |
|
data=payload, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.ConnectionError as err: |
|
raise InvalidURL("A Invalid URL or Proxy error occured") from err |
|
|
|
except requests.HTTPError as err: |
|
raise HTTPError from err |
|
|
|
self._stop_event.set() |
|
|
|
def _start_ezviz_push(self): |
|
"""Send start for push messages to ezviz api.""" |
|
|
|
payload = { |
|
"appKey": MQTT_APP_KEY, |
|
"clientId": self._mqtt_data["mqtt_clientid"], |
|
"clientType": 5, |
|
"sessionId": self._token["session_id"], |
|
"username": self._token["username"], |
|
"token": "123456", |
|
} |
|
|
|
try: |
|
req = self._session.post( |
|
f"https://{self._mqtt_data['push_url']}{API_ENDPOINT_START_MQTT}", |
|
allow_redirects=False, |
|
data=payload, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.ConnectionError as err: |
|
raise InvalidURL("A Invalid URL or Proxy error occured") from err |
|
|
|
except requests.HTTPError as err: |
|
raise HTTPError from err |
|
|
|
try: |
|
json_result = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
self._mqtt_data["ticket"] = json_result["ticket"]
|
|
|