#!/usr/bin/python ''' examples: Simple write operations: python blynk_ctrl.py --token=909fa1... -dw 5 1 python blynk_ctrl.py --token=909fa1... -aw 9 134 python blynk_ctrl.py --token=909fa1... -vw 1 value Simple read operations: python blynk_ctrl.py --token=909fa1... -vr 3 Using named pins (like A1, supported by some boards): python blynk_ctrl.py --token=909fa1... -dw A1 1 Multiple operations at once: python blynk_ctrl.py --token=909fa1... -aw 9 100 -dw 8 123 -vw 9 hello Sending arrays to virtual pins: python blynk_ctrl.py --token=909fa1... -vw 1 "value 1" "value 2" Do simple animations (delay commands): python blynk_ctrl.py --token=909fa1... --delayAll=0.2 -aw 9 0 -aw 9 50 -aw 9 100 -aw 9 150 -aw 9 200 -aw 9 255 python blynk_ctrl.py --token=909fa1... -aw 9 10 --delay=0.5 -aw 9 50 --delay=2.0 -aw 9 100 Author: Volodymyr Shymanskyy License: The MIT license ''' from __future__ import print_function import socket, struct import sys, time import argparse import logging parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description = 'This script uses Bridge feature to control another device from the command line.', epilog = __doc__ ) def opAction(op, expand=False, minargs=1): class _action(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): if len(values) < minargs: raise argparse.ArgumentError(self, "not enough parameters") if expand: pin = values[0] for v in values[1:]: namespace.ops.append([op, pin, v]) else: namespace.ops.append([op]+values) return _action parser.add_argument('-t', '--token', action="store", dest='token', help='auth token of the controller') parser.add_argument('-dw', '--digitalWrite', action=opAction('dw', True, 2), nargs='*', metavar=('PIN', 'VAL')) parser.add_argument('-aw', '--analogWrite', action=opAction('aw', True, 2), nargs='*', metavar=('PIN', 'VAL')) parser.add_argument('-vw', '--virtualWrite', action=opAction('vw', False, 2), nargs='*', metavar=('PIN', 'VAL')) parser.add_argument('-dr', '--digitalRead', action=opAction('dr'), nargs=1, metavar='PIN') parser.add_argument('-ar', '--analogRead', action=opAction('ar'), nargs=1, metavar='PIN') parser.add_argument('-vr', '--virtualRead', action=opAction('vr'), nargs=1, metavar='PIN') parser.add_argument('--delay', action=opAction('delay'), nargs=1, type=float, metavar='SECs') parser.add_argument('--delayAll', action="store", dest='delayAll', type=float, metavar='SECs', help='delay between all operations') parser.add_argument('-s', '--server', action='store', dest='server', help='server address or domain name') parser.add_argument('-p', '--port', action="store", dest='port', type=int, help='server port') parser.add_argument('--target', action="store", dest='target', metavar="TOKEN", help='auth token of the target device') parser.add_argument('--dump', action="store_true", dest='dump', help='dump communication') parser.set_defaults( server='blynk.cloud', port=80, dump=False, nodelay=True, bridge=64, ops = [] ) args = parser.parse_args() #import pprint #pprint.pprint(args) #sys.exit() logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') log = logging.getLogger("blynk_ctrl") if not args.target and args.token: args.target = args.token if not args.token: parser.error("token not specified!") if args.dump: log.setLevel(logging.DEBUG) # Helpers hdr = struct.Struct("!BHH") class MsgType: RSP = 0 LOGIN = 2 PING = 6 BRIDGE = 15 HW_SYNC = 16 HW_INFO = 17 HW = 20 class MsgStatus: OK = 200 def compose(msg_type, *args): # Convert params to string and join using \0 data = "\0".join(map(str, args)) msg_id = genMsgId() msg_len = len(data) log.debug(" < %2d,%2d,%2d : %s", msg_type, msg_id, msg_len, "=".join(map(str, args))) return hdr.pack(msg_type, msg_id, msg_len) + data.encode('utf-8') static_msg_id = 0 def genMsgId(): global static_msg_id static_msg_id += 1 return static_msg_id def receive(sock, length): d = [] l = 0 while l < length: r = '' try: r = sock.recv(length-l) except socket.timeout: continue if not r: return '' d.append(r) l += len(r) return b''.join(d) # Main code try: conn = socket.create_connection((args.server, args.port), 3) except: log.error("Can't connect to %s:%d", args.server, args.port) sys.exit(1) if args.nodelay: conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # Authenticate conn.sendall(compose(MsgType.LOGIN, args.token)) data = receive(conn, hdr.size) if not data: log.error("Login timeout") sys.exit(1) msg_type, msg_id, msg_status = hdr.unpack(data) if msg_type != MsgType.RSP or msg_status != MsgStatus.OK: log.error("Login failed: %d,%d,%d", msg_type, msg_id, msg_status) sys.exit(1) def do_read(cmd, pin): conn.sendall(compose(MsgType.HW_SYNC, cmd, pin)) while True: data = receive(conn, hdr.size) if not data: log.warning("Data read timeout") sys.exit(1) msg_type, msg_id, msg_len = hdr.unpack(data) if msg_type == MsgType.RSP: log.debug(" > %2d,%2d : status %2d", msg_type, msg_id, msg_len) elif msg_type == MsgType.HW or msg_type == MsgType.BRIDGE: data = receive(conn, msg_len).decode('utf-8').split("\0") log.debug(" > %2d,%2d,%2d : %s", msg_type, msg_id, msg_len, "=".join(data)) if data[0] == cmd[0]+'w' and data[1] == pin: data = data[2:] if len(data) > 1: print(data) else: print(data[0]) break for op in args.ops: cmd = op[0] op = op[1:] if cmd == 'dw': conn.sendall(compose(MsgType.BRIDGE, args.bridge, "i", args.target)) conn.sendall(compose(MsgType.BRIDGE, args.bridge, "dw", op[0], op[1])) elif cmd == 'aw': conn.sendall(compose(MsgType.BRIDGE, args.bridge, "i", args.target)) conn.sendall(compose(MsgType.BRIDGE, args.bridge, "aw", op[0], op[1])) elif cmd == 'vw': conn.sendall(compose(MsgType.BRIDGE, args.bridge, "i", args.target)) conn.sendall(compose(MsgType.BRIDGE, args.bridge, "vw", op[0], *op[1:])) elif cmd == 'dr' or cmd == 'ar' or cmd == 'vr': do_read(cmd, op[0]) elif cmd == 'delay': time.sleep(op[0]) else: log.warning("Wrong command: {}".format(cmd)) if args.delayAll: time.sleep(args.delayAll) # Finished conn.close()