Facebook
From Szymon , 4 Years ago, written in Python.
Embed
Download Paste or View Raw
Hits: 305
  1. # Copyright (c) 2015-2018 Volodymyr Shymanskyy. See the file LICENSE for copying permission.
  2.  
  3. _VERSION = "0.2.0"
  4.  
  5. import struct
  6. import time
  7.  
  8. try:
  9.     import machine
  10.     gettime = lambda: time.ticks_ms()
  11. except ImportError:
  12.     const = lambda x: x
  13.     gettime = lambda: int(time.time() * 1000)
  14.  
  15. def dummy(*args):
  16.     pass
  17.  
  18. MSG_RSP = const(0)
  19. MSG_LOGIN = const(2)
  20. MSG_PING  = const(6)
  21.  
  22. MSG_TWEET = const(12)
  23. MSG_EMAIL = const(13)
  24. MSG_NOTIFY = const(14)
  25. MSG_BRIDGE = const(15)
  26. MSG_HW_SYNC = const(16)
  27. MSG_INTERNAL = const(17)
  28. MSG_PROPERTY = const(19)
  29. MSG_HW = const(20)
  30. MSG_EVENT_LOG = const(64)
  31.  
  32. MSG_REDIRECT  = const(41)  # TODO: not implemented
  33. MSG_DBG_PRINT  = const(55) # TODO: not implemented
  34.  
  35. STA_SUCCESS = const(200)
  36. STA_INVALID_TOKEN = const(9)
  37.  
  38. DISCONNECTED = const(0)
  39. CONNECTING = const(1)
  40. CONNECTED = const(2)
  41.  
  42. print("""
  43.    ___  __          __
  44.   / _ )/ /_ _____  / /__
  45.  / _  / / // / _ \\/  '_/
  46. /____/_/\\_, /_//_/_/\\_\\
  47.        /___/ for Python v""" + _VERSION + "\n")
  48.  
  49. class BlynkProtocol:
  50.     def __init__(self, auth, heartbeat=10, buffin=1024, log=None):
  51.         self.callbacks = {}
  52.         self.heartbeat = heartbeat*1000
  53.         self.buffin = buffin
  54.         self.log = log or dummy
  55.         self.auth = auth
  56.         self.state = DISCONNECTED
  57.         self.connect()
  58.  
  59.     def on(self, evt, func):
  60.         self.callbacks[evt] = func
  61.        
  62.     def ON(blynk, evt):
  63.         class Decorator:
  64.             def __init__(self, func):
  65.                 self.func = func
  66.                 blynk.callbacks[evt] = func
  67.             def __call__(self):
  68.                 return self.func()
  69.         return Decorator
  70.  
  71.     def emit(self, evt, *a, **kv):
  72.         self.log("Event:", evt, "->", *a)
  73.         if evt in self.callbacks:
  74.             self.callbacks[evt](*a, **kv)
  75.  
  76.     def virtual_write(self, pin, *val):
  77.         self.sendMsg(MSG_HW, 'vw', pin, *val)
  78.  
  79.     def set_property(self, pin, prop, *val):
  80.         self.sendMsg(MSG_PROPERTY, pin, prop, *val)
  81.  
  82.     def sync_virtual(self, *pins):
  83.         self.sendMsg(MSG_HW_SYNC, 'vr', *pins)
  84.  
  85.     def notify(self, msg):
  86.         self.sendMsg(MSG_NOTIFY, msg)
  87.  
  88.     def log_event(self, event, descr=None):
  89.         if descr==None:
  90.             self.sendMsg(MSG_EVENT_LOG, event)
  91.         else:
  92.             self.sendMsg(MSG_EVENT_LOG, event, descr)
  93.  
  94.     def sendMsg(self, cmd, *args, **kwargs):
  95.         if "id" in kwargs:
  96.             id = kwargs.id
  97.         else:
  98.             id = self.msg_id
  99.             self.msg_id += 1
  100.             if self.msg_id > 0xFFFF:
  101.                 self.msg_id = 1
  102.                
  103.         if cmd == MSG_RSP:
  104.             data = b''
  105.             dlen = args[0]
  106.         else:
  107.             data = ('\0'.join(map(str, args))).encode('ascii')
  108.             dlen = len(data)
  109.        
  110.         self.log('<', cmd, id, '|', *args)
  111.         msg = struct.pack("!BHH", cmd, id, dlen) + data
  112.         self.lastSend = gettime()
  113.         self._send(msg)
  114.  
  115.     def connect(self):
  116.         if self.state != DISCONNECTED: return
  117.         self.msg_id = 1
  118.         (self.lastRecv, self.lastSend, self.lastPing) = (gettime(), 0, 0)
  119.         self.bin = b""
  120.         self.state = CONNECTING
  121.         self.sendMsg(MSG_LOGIN, self.auth)
  122.  
  123.     def disconnect(self):
  124.         if self.state == DISCONNECTED: return
  125.         self.state = DISCONNECTED
  126.         self.emit('disconnected')
  127.  
  128.     def process(self, data=b''):
  129.         if not (self.state == CONNECTING or self.state == CONNECTED): return
  130.         now = gettime()
  131.         if now - self.lastRecv > self.heartbeat+(self.heartbeat/2):
  132.             return self.disconnect()
  133.         if (now - self.lastPing > self.heartbeat/10 and
  134.             (now - self.lastSend > self.heartbeat or
  135.              now - self.lastRecv > self.heartbeat)):
  136.             self.sendMsg(MSG_PING)
  137.             self.lastPing = now
  138.        
  139.         if data != None and len(data):
  140.             self.bin += data
  141.  
  142.         while True:
  143.             if len(self.bin) < 5: return
  144.            
  145.             cmd, i, dlen = struct.unpack("!BHH", self.bin[:5])
  146.             if i == 0: return self.disconnect()
  147.                      
  148.             self.lastRecv = now
  149.             if cmd == MSG_RSP:
  150.                 self.bin = self.bin[5:]
  151.  
  152.                 self.log('>', cmd, i, '|', dlen)
  153.                 if self.state == CONNECTING and i == 1:
  154.                     if dlen == STA_SUCCESS:
  155.                         self.state = CONNECTED
  156.                         dt = now - self.lastSend
  157.                         self.sendMsg(MSG_INTERNAL, 'ver', _VERSION, 'h-beat', self.heartbeat//1000, 'buff-in', self.buffin, 'dev', 'python')
  158.                         self.emit('connected', ping=dt)
  159.                     else:
  160.                         if dlen == STA_INVALID_TOKEN:
  161.                             print("Invalid auth token")
  162.                         return self.disconnect()
  163.             else:
  164.                 if dlen >= self.buffin:
  165.                     print("Cmd too big: ", dlen)
  166.                     return self.disconnect()
  167.            
  168.                 if len(self.bin) < 5+dlen: return
  169.                
  170.                 data = self.bin[5:5+dlen]
  171.                 self.bin = self.bin[5+dlen:]
  172.  
  173.                 args = list(map(lambda x: x.decode('ascii'), data.split(b'\0')))
  174.  
  175.                 self.log('>', cmd, i, '|', ','.join(args))
  176.                 if cmd == MSG_PING:
  177.                     self.sendMsg(MSG_RSP, STA_SUCCESS, id=i)
  178.                 elif cmd == MSG_HW or cmd == MSG_BRIDGE:
  179.                     if args[0] == 'vw':
  180.                         self.emit("V"+args[1], args[2:])
  181.                     elif args[0] == 'vr':
  182.                         self.emit("readV"+args[1])
  183.                 elif cmd == MSG_INTERNAL:
  184.                     pass
  185.                 else:
  186.                     print("Unexpected command: ", cmd)
  187.                     return self.disconnect()
  188.  
  189.     def VIRTUAL_READ(blynk, pin):
  190.         class Decorator():
  191.             def __init__(self, func):
  192.                 self.func = func
  193.                 blynk.callbacks["readV"+str(pin)] = func
  194.             def __call__(self):
  195.                 return self.func()
  196.         return Decorator
  197.  
  198.     def VIRTUAL_WRITE(blynk, pin):
  199.         class Decorator():
  200.             def __init__(self, func):
  201.                 self.func = func
  202.                 blynk.callbacks["V"+str(pin)] = func
  203.             def __call__(self):
  204.                 return self.func()
  205.         return Decorator
  206.  
  207. import socket
  208.  
  209. class Blynk(BlynkProtocol):
  210.     def __init__(self, auth, **kwargs):
  211.         BlynkProtocol.__init__(self, auth, **kwargs)
  212.  
  213.     def connect(self):
  214.         try:
  215.             self.conn = socket.socket()
  216.             self.conn.connect(socket.getaddrinfo("blynk-cloud.com", 80)[0][4])
  217.             self.conn.settimeout(0.05)
  218.             BlynkProtocol.connect(self)
  219.         except:
  220.             raise ValueError('connection with the Blynk servers failed')
  221.  
  222.     def _send(self, data):
  223.         self.conn.send(data)
  224.         # TODO: handle disconnect
  225.  
  226.     def run(self):
  227.         data = b''
  228.         try:
  229.             data = self.conn.recv(self.buffin)
  230.         except KeyboardInterrupt:
  231.             raise
  232.         except: # TODO: handle disconnect
  233.             pass
  234.         self.process(data)
  235.  
  236.