├── README.md └── blynk-mqtt.py /README.md: -------------------------------------------------------------------------------- 1 | Blynk.cc to MQTT broker bridge 2 | ============================== 3 | 4 | [Blynk.cc](http://blynk.cc/) nice project with nice [Android application](https://play.google.com/store/apps/details?id=cc.blynk), but uses own protocol and library not implemented on some hardware. 5 | 6 | This is simple bridge between Blynk.cc and MQTT. Only virtual pins allowed. 7 | 8 | 9 | Setup 10 | ----- 11 | 12 | Setup your token and broker: 13 | ``` 14 | TOKEN = "YourAppToken" 15 | MQTT_SERVER = "test.mosquitto.org" 16 | MQTT_PORT = 1883 17 | TOPIC = "/ESP009xxxxx" 18 | ``` 19 | And run ```python blynk-mqtt.py``` 20 | 21 | Requires paho-mqtt python module 22 | 23 | 24 | MQTT Topics 25 | ----------- 26 | 27 | Virtual pin 0 write request will be published as /ESP009xxxxx/vw/0 topic. 28 | Virtual pin 0 read request will be published as /ESP009xxxxx/vr/0 topic and also will be send answer to Blynk.cc server - latest pin value. 29 | Where 0 is virtual pin number. 30 | 31 | Bridge subscribes for all /ESP009xxxxx/# topics and translate them to virtual pins according translate table 32 | ``` 33 | translate_topic = ( 34 | ('sensors/bmpt', 0), 35 | ('sensors/bmpp', 1), 36 | ) 37 | ``` 38 | 39 | This mean that value from topic /ESP009xxxxx/sensors/bmpt will be translate to virtual pin 0, for example. 40 | 41 | Topics like /ESP009xxxxx/vw/0 will be translate to virtual pin 0. 42 | 43 | 44 | Copyright 45 | --------- 46 | 47 | This work based on code blynk-library/tests/pseudo-library.py from Blynk.cc project. 48 | -------------------------------------------------------------------------------- /blynk-mqtt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ''' 3 | Blynk.cc to MQTT broker bridge 4 | 5 | Example: 6 | ./blynk-mqtt.py -t b168ccc8c8734fad98323247afbc1113 --dump 7 | 8 | Author: Volodymyr Shymanskyy, Aliaksei 9 | License: The MIT license 10 | ''' 11 | import select, socket, struct 12 | import os, sys, time, getopt 13 | import paho.mqtt.client as mqtt 14 | from threading import Thread 15 | 16 | # Configuration options 17 | 18 | # Parse command line options 19 | try: 20 | opts, args = getopt.getopt(sys.argv[1:], 21 | "hs:p:t:", 22 | ["help", "server=", "port=", "token=", "sndbuf=", "rcvbuf=", "nodelay=", "dump", "mqtt_server=", "mqtt_port=", "topic="]) 23 | except getopt.GetoptError: 24 | print >>sys.stderr, __doc__ 25 | sys.exit(2) 26 | 27 | # Default options 28 | SERVER = "cloud.blynk.cc" 29 | PORT = 8442 30 | NODELAY = 1 # TCP_NODELAY 31 | SNDBUF = 0 # No SNDBUF override 32 | RCVBUF = 0 # No RCVBUF override 33 | TOKEN = "YourAppToken" 34 | DUMP = 0 35 | 36 | MQTT_SERVER = "test.mosquitto.org" 37 | MQTT_PORT = 1883 38 | # MQTT_LOGIN = "" 39 | # MQTT_PASSWORD = "" 40 | MQTT_CLIENT = "blynk.cc" 41 | TOPIC = "/ESP009xxxxx" # see http://homes-smart.ru/index.php/oborudovanie/bez-provodov-wi-fi/proekt-umnogo-modulya-na-baze-esp8266 42 | 43 | # topics to virtual pins translate table 44 | translate_topic = ( 45 | ('sensors/bmpt', 0), 46 | ('sensors/bmpp', 1), 47 | ('sensors/dhtt1', 2), 48 | ('sensors/dhth1', 3), 49 | ('sensors/freemem', 4), 50 | ('sensors/uptime', 5), 51 | ) 52 | 53 | # last pin state storage 54 | pin_storage = {} 55 | 56 | for o, v in opts: 57 | if o in ("-h", "--help"): 58 | print __doc__ 59 | sys.exit() 60 | elif o in ("-s", "--server"): 61 | SERVER = v 62 | elif o in ("-p", "--port"): 63 | PORT = int(v) 64 | elif o in ("-t", "--token"): 65 | TOKEN = v 66 | elif o in ("--sndbuf",): 67 | SNDBUF = int(v) 68 | elif o in ("--rcvbuf",): 69 | RCVBUF = int(v) 70 | elif o in ("--nodelay",): 71 | NODELAY = int(v) 72 | elif o in ("--dump",): 73 | DUMP = 1 74 | elif o in ("--mqtt_server",): 75 | MQTT_SERVER = v 76 | elif o in ("--mqtt_port",): 77 | MQTT_PORT = v 78 | elif o in ("--topic",): 79 | TOPIC = v 80 | 81 | 82 | # Blynk protocol helpers 83 | 84 | hdr = struct.Struct("!BHH") 85 | 86 | class MsgType: 87 | RSP = 0 88 | LOGIN = 2 89 | PING = 6 90 | BRIDGE = 15 91 | HW = 20 92 | 93 | class MsgStatus: 94 | OK = 200 95 | 96 | def hw(*args): 97 | # Convert params to string and join using \0 98 | data = "\0".join(map(str, args)) 99 | dump("< " + " ".join(map(str, args))) 100 | # Prepend HW command header 101 | return hdr.pack(MsgType.HW, genMsgId(), len(data)) + data 102 | 103 | def handle_hw(data, mqtt): 104 | params = data.split("\0") 105 | cmd = params.pop(0) 106 | if cmd == 'info': 107 | pass 108 | 109 | ### VIRTUAL pin operations 110 | if cmd == 'vw': # This should call user handler 111 | pin = int(params.pop(0)) 112 | val = params.pop(0) 113 | log("Virtual write pin %d, value %s" % (pin, val)) 114 | mqtt.publish(u"%s/vw/%d" % (TOPIC, pin), val) 115 | 116 | elif cmd == 'vr': # This should call user handler 117 | pin = int(params.pop(0)) 118 | log("Virtual read pin %d" % pin) 119 | mqtt.publish(u"%s/vr/%d" % (TOPIC, pin)) 120 | try: 121 | conn.sendall(hw("vw", pin, pin_storage[pin])) 122 | except: 123 | pass 124 | 125 | else: 126 | log("Unknown HW cmd: %s" % cmd) 127 | 128 | static_msg_id = 1 129 | def genMsgId(): 130 | global static_msg_id 131 | static_msg_id += 1 132 | return static_msg_id 133 | 134 | # Other utilities 135 | 136 | start_time = time.time() 137 | def log(msg): 138 | print "[{:7.3f}] {:}".format(float(time.time() - start_time), msg) 139 | 140 | def dump(msg): 141 | if DUMP: 142 | log(msg) 143 | 144 | def receive(sock, length): 145 | d = [] 146 | l = 0 147 | while l < length: 148 | r = '' 149 | try: 150 | r = sock.recv(length-l) 151 | except socket.timeout: 152 | continue 153 | if not r: 154 | return '' 155 | d.append(r) 156 | l += len(r) 157 | return ''.join(d) 158 | 159 | # Threads 160 | 161 | def readthread(conn, mqtt): 162 | while (True): 163 | data = receive(conn, hdr.size) 164 | if not data: 165 | break 166 | msg_type, msg_id, msg_len = hdr.unpack(data) 167 | dump("Got {0}, {1}, {2}".format(msg_type, msg_id, msg_len)) 168 | if msg_type == MsgType.RSP: 169 | pass 170 | elif msg_type == MsgType.PING: 171 | log("Got ping") 172 | # Send Pong 173 | conn.sendall(hdr.pack(MsgType.RSP, msg_id, MsgStatus.OK)) 174 | elif msg_type == MsgType.HW or msg_type == MsgType.BRIDGE: 175 | data = receive(conn, msg_len) 176 | # Print HW message 177 | dump("> " + " ".join(data.split("\0"))) 178 | handle_hw(data, mqtt) 179 | else: 180 | log("Unknown msg type") 181 | break 182 | 183 | 184 | def writethread(conn, mqtt): 185 | while (True): 186 | time.sleep(10) 187 | log("Sending heartbeat...") 188 | conn.sendall(hdr.pack(MsgType.PING, genMsgId(), 0)) 189 | 190 | def on_mqtt_message(client, userdata, msg): 191 | log("Topic %s, Message %s" % (msg.topic, str(msg.payload))) 192 | 193 | topic = msg.topic 194 | l = len(TOPIC) 195 | 196 | if topic[0:l] == TOPIC: 197 | path = topic[l:].split('/') 198 | if path[1] == "vw": 199 | pin = int(path[2]) 200 | pin_storage[pin] = str(msg.payload) 201 | conn.sendall(hw("vw", pin, str(msg.payload))) 202 | 203 | for old, pin in translate_topic: 204 | old_topic = u"%s/%s" % (TOPIC, old) 205 | if old_topic == msg.topic: 206 | pin_storage[pin] = str(msg.payload) 207 | log(u"Write topic %s to vw/%s, val %s" % (old_topic, pin, str(msg.payload))) 208 | conn.sendall(hw("vw", pin, str(msg.payload))) 209 | 210 | # Main code 211 | log('Connecting to MQTT broker %s:%d' % (MQTT_SERVER, MQTT_PORT)) 212 | try: 213 | mqtt = mqtt.Client(MQTT_CLIENT) 214 | mqtt.connect(MQTT_SERVER, MQTT_PORT, 60) 215 | mqtt.on_message = on_mqtt_message 216 | except: 217 | log("Can't connect") 218 | sys.exit(1) 219 | 220 | mqtt.subscribe("%s/#" % TOPIC) 221 | 222 | log('Connecting to %s:%d' % (SERVER, PORT)) 223 | try: 224 | conn = socket.create_connection((SERVER, PORT), 3) 225 | except: 226 | log("Can't connect") 227 | sys.exit(1) 228 | 229 | if NODELAY != 0: 230 | conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 231 | if SNDBUF != 0: 232 | sndbuf = conn.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) 233 | log('Default SNDBUF %s changed to %s' % (sndbuf, SNDBUF)) 234 | conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, SNDBUF) 235 | if RCVBUF != 0: 236 | rcvbuf = conn.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) 237 | log('Default RCVBUF %s changed to %s' % (rcvbuf, RCVBUF)) 238 | conn.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, RCVBUF) 239 | 240 | # Authenticate 241 | conn.sendall(hdr.pack(MsgType.LOGIN, genMsgId(), len(TOKEN))) 242 | conn.sendall(TOKEN) 243 | data = receive(conn, hdr.size) 244 | if not data: 245 | log("Auth timeout") 246 | sys.exit(1) 247 | 248 | msg_type, msg_id, status = hdr.unpack(data) 249 | dump("Got {0}, {1}, {2}".format(msg_type, msg_id, status)) 250 | 251 | if status != MsgStatus.OK: 252 | log("Auth failed: %d" % status) 253 | sys.exit(1) 254 | 255 | wt = Thread(target=readthread, args=(conn, mqtt)) 256 | rt = Thread(target=writethread, args=(conn, mqtt)) 257 | 258 | wt.start() 259 | rt.start() 260 | 261 | mqtt.loop_forever() 262 | 263 | wt.join() 264 | rt.join() 265 | 266 | 267 | conn.close() 268 | --------------------------------------------------------------------------------