146 lines
4.6 KiB
Python
146 lines
4.6 KiB
Python
import os
|
|
from os import path
|
|
from uuid import uuid4
|
|
import copy
|
|
from os import path
|
|
import sys
|
|
|
|
from bs4 import BeautifulSoup
|
|
import vcdvcd
|
|
from flask import Flask, redirect, url_for, abort, send_file, render_template, request, flash, Response
|
|
from werkzeug.utils import secure_filename
|
|
|
|
app = Flask(__name__)
|
|
app.config['MAX_CONTENT_LENGTH'] = 100 * 1000
|
|
app.config.from_envvar('VCD8SEG_SETTINGS')
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
@app.route('/show/<uuid:uuid>')
|
|
def show(uuid):
|
|
uuid = str(uuid)
|
|
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
|
|
if not path.isfile(fp):
|
|
abort(404)
|
|
|
|
return send_file(fp, mimetype='image/svg+xml')
|
|
|
|
@app.route('/upload', methods=['POST'])
|
|
def upload_vcd():
|
|
if not (file := request.files.get('file')):
|
|
flash('Request is missing file part.')
|
|
return redirect(url_for('index'))
|
|
|
|
if file.filename == '':
|
|
flash('No file selected.')
|
|
return redirect(url_for('index'))
|
|
|
|
if path.splitext(file.filename)[1].lower() != '.vcd':
|
|
flash('Please upload a .vcd waveform file (not e.g. .v or .vvp)')
|
|
return redirect(url_for('index'))
|
|
|
|
uuid = str(uuid4())
|
|
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
|
|
file.save(fp)
|
|
return redirect(url_for('vcd_setup', uuid=uuid))
|
|
|
|
|
|
@app.route('/setup/<uuid:uuid>', methods=['GET', 'POST'])
|
|
def vcd_setup(uuid):
|
|
uuid = str(uuid)
|
|
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
|
|
if not path.isfile(fp):
|
|
abort(404)
|
|
with open(fp) as f:
|
|
vcd = f.read()
|
|
|
|
if request.method == 'GET':
|
|
default_signal, signal_names = vcd_signal_names(vcd)
|
|
return render_template('setup.html', uuid=uuid, signal_names=signal_names, default_signal=default_signal)
|
|
|
|
else: # request.method == 'POST'
|
|
try:
|
|
svg = vcd_to_8seg_svg(vcd, request.form['signal'])
|
|
except IndexError:
|
|
abort(Response(400, 'Signal not found'))
|
|
|
|
os.remove(path.join(app.config['UPLOAD_PATH'], secure_filename(uuid)))
|
|
|
|
uuid = str(uuid4())
|
|
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
|
|
with open(fp, 'w') as f:
|
|
f.write(svg)
|
|
return redirect(url_for('show', uuid=uuid))
|
|
|
|
|
|
def vcd_signal_names(data):
|
|
vcd = vcdvcd.VCDVCD(vcd_string=data)
|
|
signal_names = list(vcd.references_to_ids.keys())
|
|
default_signal = [*[n for n in signal_names if 'segments' in n],
|
|
*[n for n in signal_names if 'segs' in n],
|
|
*signal_names][0]
|
|
return default_signal, signal_names
|
|
|
|
|
|
def vcd_to_8seg_svg(in_vcd, signal=None):
|
|
with open(path.join(app.static_folder, '8seg-template.svg')) as f:
|
|
soup = BeautifulSoup(f.read(), 'xml')
|
|
|
|
(vb_x, vb_y, vb_w, vb_h) = list(map(int, soup.find('svg')['viewBox'].split()))
|
|
|
|
display_template = soup.find(id='display').extract()
|
|
|
|
def generate_digit(segments):
|
|
inst = copy.copy(display_template)
|
|
for seg, val in zip('abcdefgh', segments):
|
|
if val:
|
|
tag = inst.find(id=f'seg_{seg}')
|
|
tag['style'] = tag['style'].replace('fill:#202040', 'fill:#ff0000')
|
|
return inst
|
|
|
|
def concat_digits(digits):
|
|
inst = copy.copy(soup)
|
|
svg = inst.find('svg')
|
|
|
|
x, y = vb_x, vb_y
|
|
w, h = 0, 0
|
|
for i, segments in enumerate(digits):
|
|
digit = generate_digit(segments)
|
|
digit['id'] = f'digit{i}'
|
|
digit['transform'] = f'translate({x}, {y})'
|
|
svg.append(digit)
|
|
|
|
x += vb_w
|
|
w += vb_w
|
|
h = vb_h
|
|
|
|
w_phys = (i+1) * 15 # mm
|
|
svg['viewBox'] = f'{vb_x} {vb_y} {w} {h}'
|
|
svg['width'] = f'{w_phys}mm'
|
|
svg['height'] = f'{15 * (vb_h / vb_w)}mm'
|
|
return svg
|
|
|
|
def parse_vcd(data, signal=None):
|
|
vcd = vcdvcd.VCDVCD(vcd_string=data) # vcd? vcd vcd, vcd!
|
|
|
|
signal_names = vcd.references_to_ids.keys()
|
|
if signal is None:
|
|
signal = [*[n for n in signal_names if 'segments' in n],
|
|
*[n for n in signal_names if 'segs' in n],
|
|
*signal_names][0]
|
|
|
|
try:
|
|
sig = vcd[signal]
|
|
except IndexError:
|
|
raise IndexError('Signal {signal} not found. Available signals: {", ".join(signal_names)}')
|
|
|
|
# Only use first 10k rows
|
|
for i, (time, value) in zip(range(10000), sig.tv):
|
|
value = ('0' * int(sig.size) + value)[-int(sig.size):]
|
|
yield [int(x) for x in value]
|
|
|
|
return str(concat_digits(list(parse_vcd(in_vcd, signal))))
|
|
|