# Copyright (c) 2015-2018 Volodymyr Shymanskyy. See the file LICENSE for copying permission. _VERSION = "0.2.0" import struct import time try: import machine gettime = lambda: time.ticks_ms() except ImportError: const = lambda x: x gettime = lambda: int(time.time() * 1000) def dummy(*args): pass MSG_RSP = const(0) MSG_LOGIN = const(2) MSG_PING = const(6) MSG_TWEET = const(12) MSG_EMAIL = const(13) MSG_NOTIFY = const(14) MSG_BRIDGE = const(15) MSG_HW_SYNC = const(16) MSG_INTERNAL = const(17) MSG_PROPERTY = const(19) MSG_HW = const(20) MSG_EVENT_LOG = const(64) MSG_REDIRECT = const(41) # TODO: not implemented MSG_DBG_PRINT = const(55) # TODO: not implemented STA_SUCCESS = const(200) STA_INVALID_TOKEN = const(9) DISCONNECTED = const(0) CONNECTING = const(1) CONNECTED = const(2) print(""" ___ __ __ / _ )/ /_ _____ / /__ / _ / / // / _ \\/ '_/ /____/_/\\_, /_//_/_/\\_\\ /___/ for Python v""" + _VERSION + "\n") class BlynkProtocol: def __init__(self, auth, heartbeat=10, buffin=1024, log=None): self.callbacks = {} self.heartbeat = heartbeat*1000 self.buffin = buffin self.log = log or dummy self.auth = auth self.state = DISCONNECTED self.connect() def on(self, evt, func): self.callbacks[evt] = func def ON(blynk, evt): class Decorator: def __init__(self, func): self.func = func blynk.callbacks[evt] = func def __call__(self): return self.func() return Decorator def emit(self, evt, *a, **kv): self.log("Event:", evt, "->", *a) if evt in self.callbacks: self.callbacks[evt](*a, **kv) def virtual_write(self, pin, *val): self.sendMsg(MSG_HW, 'vw', pin, *val) def set_property(self, pin, prop, *val): self.sendMsg(MSG_PROPERTY, pin, prop, *val) def sync_virtual(self, *pins): self.sendMsg(MSG_HW_SYNC, 'vr', *pins) def notify(self, msg): self.sendMsg(MSG_NOTIFY, msg) def log_event(self, event, descr=None): if descr==None: self.sendMsg(MSG_EVENT_LOG, event) else: self.sendMsg(MSG_EVENT_LOG, event, descr) def sendMsg(self, cmd, *args, **kwargs): if "id" in kwargs: id = kwargs.id else: id = self.msg_id self.msg_id += 1 if self.msg_id > 0xFFFF: self.msg_id = 1 if cmd == MSG_RSP: data = b'' dlen = args[0] else: data = ('\0'.join(map(str, args))).encode('ascii') dlen = len(data) self.log('<', cmd, id, '|', *args) msg = struct.pack("!BHH", cmd, id, dlen) + data self.lastSend = gettime() self._send(msg) def connect(self): if self.state != DISCONNECTED: return self.msg_id = 1 (self.lastRecv, self.lastSend, self.lastPing) = (gettime(), 0, 0) self.bin = b"" self.state = CONNECTING self.sendMsg(MSG_LOGIN, self.auth) def disconnect(self): if self.state == DISCONNECTED: return self.state = DISCONNECTED self.emit('disconnected') def process(self, data=b''): if not (self.state == CONNECTING or self.state == CONNECTED): return now = gettime() if now - self.lastRecv > self.heartbeat+(self.heartbeat/2): return self.disconnect() if (now - self.lastPing > self.heartbeat/10 and (now - self.lastSend > self.heartbeat or now - self.lastRecv > self.heartbeat)): self.sendMsg(MSG_PING) self.lastPing = now if data != None and len(data): self.bin += data while True: if len(self.bin) < 5: return cmd, i, dlen = struct.unpack("!BHH", self.bin[:5]) if i == 0: return self.disconnect() self.lastRecv = now if cmd == MSG_RSP: self.bin = self.bin[5:] self.log('>', cmd, i, '|', dlen) if self.state == CONNECTING and i == 1: if dlen == STA_SUCCESS: self.state = CONNECTED dt = now - self.lastSend self.sendMsg(MSG_INTERNAL, 'ver', _VERSION, 'h-beat', self.heartbeat//1000, 'buff-in', self.buffin, 'dev', 'python') self.emit('connected', ping=dt) else: if dlen == STA_INVALID_TOKEN: print("Invalid auth token") return self.disconnect() else: if dlen >= self.buffin: print("Cmd too big: ", dlen) return self.disconnect() if len(self.bin) < 5+dlen: return data = self.bin[5:5+dlen] self.bin = self.bin[5+dlen:] args = list(map(lambda x: x.decode('ascii'), data.split(b'\0'))) self.log('>', cmd, i, '|', ','.join(args)) if cmd == MSG_PING: self.sendMsg(MSG_RSP, STA_SUCCESS, id=i) elif cmd == MSG_HW or cmd == MSG_BRIDGE: if args[0] == 'vw': self.emit("V"+args[1], args[2:]) elif args[0] == 'vr': self.emit("readV"+args[1]) elif cmd == MSG_INTERNAL: pass else: print("Unexpected command: ", cmd) return self.disconnect() def VIRTUAL_READ(blynk, pin): class Decorator(): def __init__(self, func): self.func = func blynk.callbacks["readV"+str(pin)] = func def __call__(self): return self.func() return Decorator def VIRTUAL_WRITE(blynk, pin): class Decorator(): def __init__(self, func): self.func = func blynk.callbacks["V"+str(pin)] = func def __call__(self): return self.func() return Decorator import socket class Blynk(BlynkProtocol): def __init__(self, auth, **kwargs): BlynkProtocol.__init__(self, auth, **kwargs) def connect(self): try: self.conn = socket.socket() self.conn.connect(socket.getaddrinfo("blynk-cloud.com", 80)[0][4]) self.conn.settimeout(0.05) BlynkProtocol.connect(self) except: raise ValueError('connection with the Blynk servers failed') def _send(self, data): self.conn.send(data) # TODO: handle disconnect def run(self): data = b'' try: data = self.conn.recv(self.buffin) except KeyboardInterrupt: raise except: # TODO: handle disconnect pass self.process(data)