#!/usr/bin/env python """ serial-plotter.py - for Tasmota Copyright (C) 2020 Christian Baars This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Requirements: - Python - pip3 matplotlib - a Tasmotadriver that plots Instructions: expects serial data in the format: 'PLOT: graphnumber value' graph (1-4) integer value Code snippet example: (last value will be ignored) AddLog_P2(LOG_LEVEL_INFO, PSTR("PLOT: %u, %u, %u,"),button_index+1, _value, Button.touch_hits[button_index]); Usage: set serial config in code ./serial-plotter.py set output in tasmota, e.g.; TouchCal 1..4 (via Textbox) """ import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib.widgets import TextBox import time import serial import argparse #default values port = '/dev/cu.SLAB_USBtoUART' baud = 115200 #command line input parser = argparse.ArgumentParser() parser.add_argument("--port", "-p", help="change serial port, default: " + port) parser.add_argument("--baud", "-b", help="change baud rate, default: " + str(baud)) args = parser.parse_args() if args.port: print("change serial port to %s" % args.port) port = args.port if args.baud: print("change baud rate to %s" % args.baud) baud = args.baud #time range dt = 0.01 t = np.arange(0.0, 100, dt) #lists for the data xs = [0] #counting up x ys = [[0],[0],[0],[0]] #4 fixed graphs for now max_y = 1 # min_y = 0 fig = plt.figure('Tasmota Serial Plotter') ax = fig.add_subplot(111, autoscale_on=True, xlim=(0, 200), ylim=(0, 20)) #fixed x scale for now, y will adapt ax.grid() line1, = ax.plot([], [], color = "r", label='G 1') line2, = ax.plot([], [], color = "g", label='G 2') line3, = ax.plot([], [], color = "b", label='G 3') line4, = ax.plot([], [], color = "y", label='G 4') time_template = 'time = %.1fs' time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes) ser = serial.Serial() ser.port = port ser.baudrate = baud ser.timeout = 0 #return immediately try: ser.open() except: print("Could not connect to serial with settings: " + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') print("port available?") exit() if ser.is_open==True: print("Serial Plotter started ...:") plt.title('connected to ' + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') else: print("Could not connect to serial: " + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') plt.title('NOT connected to ' + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') def init(): line1.set_data([], []) line2.set_data([], []) line3.set_data([], []) line4.set_data([], []) time_text.set_text('') return [line1,line2,line3,line4,time_text ] #was line def parse_line(data_line): pos = data_line.find("PLOT:", 10) if pos<0: # print("wrong format") return 0,0 raw_data = data_line[pos+6:] val_list = raw_data.split(',') try: g = int(val_list[0]) v = int(val_list[1]) return g, v except: return 0,0 def update(num, line1, line2): global xs, ys, max_y time_text.set_text(time_template % (num*dt) ) receive_data = str(ser.readline()) #string g, v = parse_line(receive_data) if (g in range(1,5)): # print(v,g) if v>max_y: max_y = v print(max_y) ax.set_ylim([0, max_y * 1.2]) idx = 0 for y in ys: y.append(y[-1]) if idx == g-1: y[-1] = v idx = idx +1 xs.append(xs[-1]+1) if len(ys[0])>200: xs.pop() for y in ys: y.pop(0) line1.set_data(xs, ys[0]) line2.set_data(xs, ys[1]) line3.set_data(xs, ys[2]) line4.set_data(xs, ys[3]) return [line1,line2,line3,line4, time_text] def handle_close(evt): print('Closing serial connection') ser.close() print('Closed serial plotter') ani = animation.FuncAnimation(fig, update, None, fargs=[line1, line2], interval=10, blit=True, init_func=init) ax.set_xlabel('Last 200 Samples') ax.set_ylabel('Values') fig.canvas.mpl_connect('close_event', handle_close) def submit(text): print (text) ser.write(text.encode() + "\n".encode()) plt.subplots_adjust(bottom=0.25) axbox = plt.axes([0.15, 0.05, 0.7, 0.075]) text_box = TextBox(axbox, 'Send:', initial='') text_box.on_submit(submit) ax.legend(loc='lower right', ncol=2) if ser.is_open==True: plt.show()