]> vault307.fbx.one Git - Blynk.git/blob - BlynkLib.py
Blynk library and devices
[Blynk.git] / BlynkLib.py
1 # Copyright (c) 2015-2019 Volodymyr Shymanskyy. See the file LICENSE for copying permission.
2
3 __version__ = "1.0.0"
4
5 import struct
6 import time
7 import sys
8 import os
9
10 try:
11 import machine
12 gettime = lambda: time.ticks_ms()
13 SOCK_TIMEOUT = 0
14 except ImportError:
15 const = lambda x: x
16 gettime = lambda: int(time.time() * 1000)
17 SOCK_TIMEOUT = 0.05
18
19 def dummy(*args):
20 pass
21
22 MSG_RSP = const(0)
23 MSG_LOGIN = const(2)
24 MSG_PING = const(6)
25
26 MSG_TWEET = const(12)
27 MSG_NOTIFY = const(14)
28 MSG_BRIDGE = const(15)
29 MSG_HW_SYNC = const(16)
30 MSG_INTERNAL = const(17)
31 MSG_PROPERTY = const(19)
32 MSG_HW = const(20)
33 MSG_HW_LOGIN = const(29)
34 MSG_EVENT_LOG = const(64)
35
36 MSG_REDIRECT = const(41) # TODO: not implemented
37 MSG_DBG_PRINT = const(55) # TODO: not implemented
38
39 STA_SUCCESS = const(200)
40 STA_INVALID_TOKEN = const(9)
41
42 DISCONNECTED = const(0)
43 CONNECTING = const(1)
44 CONNECTED = const(2)
45
46 print("""
47 ___ __ __
48 / _ )/ /_ _____ / /__
49 / _ / / // / _ \\/ '_/
50 /____/_/\\_, /_//_/_/\\_\\
51 /___/ for Python v""" + __version__ + " (" + sys.platform + ")\n")
52
53 class EventEmitter:
54 def __init__(self):
55 self._cbks = {}
56
57 def on(self, evt, f=None):
58 if f:
59 self._cbks[evt] = f
60 else:
61 def D(f):
62 self._cbks[evt] = f
63 return f
64 return D
65
66 def emit(self, evt, *a, **kv):
67 if evt in self._cbks:
68 self._cbks[evt](*a, **kv)
69
70
71 class BlynkProtocol(EventEmitter):
72 def __init__(self, auth, tmpl_id=None, fw_ver=None, heartbeat=50, buffin=1024, log=None):
73 EventEmitter.__init__(self)
74 self.heartbeat = heartbeat*1000
75 self.buffin = buffin
76 self.log = log or dummy
77 self.auth = auth
78 self.tmpl_id = tmpl_id
79 self.fw_ver = fw_ver
80 self.state = DISCONNECTED
81 self.connect()
82
83 def virtual_write(self, pin, *val):
84 self._send(MSG_HW, 'vw', pin, *val)
85
86 def send_internal(self, pin, *val):
87 self._send(MSG_INTERNAL, pin, *val)
88
89 def set_property(self, pin, prop, *val):
90 self._send(MSG_PROPERTY, pin, prop, *val)
91
92 def sync_virtual(self, *pins):
93 self._send(MSG_HW_SYNC, 'vr', *pins)
94
95 def log_event(self, *val):
96 self._send(MSG_EVENT_LOG, *val)
97
98 def _send(self, cmd, *args, **kwargs):
99 if 'id' in kwargs:
100 id = kwargs.get('id')
101 else:
102 id = self.msg_id
103 self.msg_id += 1
104 if self.msg_id > 0xFFFF:
105 self.msg_id = 1
106
107 if cmd == MSG_RSP:
108 data = b''
109 dlen = args[0]
110 else:
111 data = ('\0'.join(map(str, args))).encode('utf8')
112 dlen = len(data)
113
114 self.log('<', cmd, id, '|', *args)
115 msg = struct.pack("!BHH", cmd, id, dlen) + data
116 self.lastSend = gettime()
117 self._write(msg)
118
119 def connect(self):
120 if self.state != DISCONNECTED: return
121 self.msg_id = 1
122 (self.lastRecv, self.lastSend, self.lastPing) = (gettime(), 0, 0)
123 self.bin = b""
124 self.state = CONNECTING
125 self._send(MSG_HW_LOGIN, self.auth)
126 self.emit('connected')
127
128 def disconnect(self):
129 if self.state == DISCONNECTED: return
130 self.bin = b""
131 self.state = DISCONNECTED
132 self.emit('disconnected')
133
134 def process(self, data=None):
135 if not (self.state == CONNECTING or self.state == CONNECTED): return
136 now = gettime()
137 if now - self.lastRecv > self.heartbeat+(self.heartbeat//2):
138 return self.disconnect()
139 if (now - self.lastPing > self.heartbeat//10 and
140 (now - self.lastSend > self.heartbeat or
141 now - self.lastRecv > self.heartbeat)):
142 self._send(MSG_PING)
143 self.lastPing = now
144
145 if data != None and len(data):
146 self.bin += data
147
148 while True:
149 if len(self.bin) < 5:
150 break
151
152 cmd, i, dlen = struct.unpack("!BHH", self.bin[:5])
153 if i == 0: return self.disconnect()
154
155 self.lastRecv = now
156 if cmd == MSG_RSP:
157 self.bin = self.bin[5:]
158
159 self.log('>', cmd, i, '|', dlen)
160 if self.state == CONNECTING and i == 1:
161 if dlen == STA_SUCCESS:
162 self.state = CONNECTED
163 dt = now - self.lastSend
164 info = ['ver', __version__, 'h-beat', self.heartbeat//1000, 'buff-in', self.buffin, 'dev', sys.platform+'-py']
165 if self.tmpl_id:
166 info.extend(['tmpl', self.tmpl_id])
167 info.extend(['fw-type', self.tmpl_id])
168 if self.fw_ver:
169 info.extend(['fw', self.fw_ver])
170 self._send(MSG_INTERNAL, *info)
171 try:
172 self.emit('connected', ping=dt)
173 except TypeError:
174 self.emit('connected')
175 else:
176 if dlen == STA_INVALID_TOKEN:
177 self.emit("invalid_auth")
178 print("Invalid auth token")
179 return self.disconnect()
180 else:
181 if dlen >= self.buffin:
182 print("Cmd too big: ", dlen)
183 return self.disconnect()
184
185 if len(self.bin) < 5+dlen:
186 break
187
188 data = self.bin[5:5+dlen]
189 self.bin = self.bin[5+dlen:]
190
191 args = list(map(lambda x: x.decode('utf8'), data.split(b'\0')))
192
193 self.log('>', cmd, i, '|', ','.join(args))
194 if cmd == MSG_PING:
195 self._send(MSG_RSP, STA_SUCCESS, id=i)
196 elif cmd == MSG_HW or cmd == MSG_BRIDGE:
197 if args[0] == 'vw':
198 self.emit("V"+args[1], args[2:])
199 self.emit("V*", args[1], args[2:])
200 elif cmd == MSG_INTERNAL:
201 self.emit("internal:"+args[0], args[1:])
202 elif cmd == MSG_REDIRECT:
203 self.emit("redirect", args[0], int(args[1]))
204 else:
205 print("Unexpected command: ", cmd)
206 return self.disconnect()
207
208 import socket
209
210 class Blynk(BlynkProtocol):
211 def __init__(self, auth, **kwargs):
212 self.insecure = kwargs.pop('insecure', False)
213 self.server = kwargs.pop('server', 'blynk.cloud')
214 self.port = kwargs.pop('port', 80 if self.insecure else 443)
215 BlynkProtocol.__init__(self, auth, **kwargs)
216 self.on('redirect', self.redirect)
217
218 def redirect(self, server, port):
219 self.server = server
220 self.port = port
221 self.disconnect()
222 self.connect()
223
224 def connect(self):
225 print('Connecting to %s:%d...' % (self.server, self.port))
226 s = socket.socket()
227 s.connect(socket.getaddrinfo(self.server, self.port)[0][-1])
228
229 try:
230 s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
231 except:
232 pass
233 if self.insecure:
234 self.conn = s
235 else:
236 try:
237 import ussl
238 ssl_context = ussl
239 except ImportError:
240 import ssl
241 ssl_context = ssl.create_default_context()
242 self.conn = ssl_context.wrap_socket(s, server_hostname=self.server)
243 try:
244 self.conn.settimeout(SOCK_TIMEOUT)
245 except:
246 s.settimeout(SOCK_TIMEOUT)
247 BlynkProtocol.connect(self)
248
249 def _write(self, data):
250 #print('<', data)
251 self.conn.write(data)
252 # TODO: handle disconnect
253
254 def run(self):
255 data = b''
256 try:
257 data = self.conn.read(self.buffin)
258 #print('>', data)
259 except KeyboardInterrupt:
260 raise
261 except socket.timeout:
262 # No data received, call process to send ping messages when needed
263 pass
264 except: # TODO: handle disconnect
265 return
266 self.process(data)
267