#1 » de DeeJay » 22 Feb 2026 09:49
I have the Zoyi zt-dq02 LCR Multimeter and it has a PC software that can be used to show measurements on the PC screen, change mode, parameters, etc. I just wanted a web page where I can see this and not use the Zoyi software, so i created this python script that makes a web page on port 5000, you can acces it with
https://raspberryipaddr:5000- Cod: Selectaţi tot
#!/usr/bin/env python3
from flask import Flask, jsonify, render_template_string, request
import threading, time, serial
from collections import deque
# --------------------------
# Configuration
# --------------------------
PORT = '/dev/ttyACM0'
BAUD = 9600
READ_INTERVAL = 0.5
HISTORY_LENGTH = 5
latest_reading = {}
history = deque(maxlen=HISTORY_LENGTH)
# --------------------------
# Helper: Format measurement
# --------------------------
def format_measurement(value_str, unit_code):
try:
value = float(value_str)
except ValueError:
return value_str
unit_code = unit_code.upper()
if unit_code in ('R','Z'):
if value >= 1e6: return f"{value/1e6:.3f} MΩ"
elif value >= 1e3: return f"{value/1e3:.3f} kΩ"
else: return f"{value:.2f} Ω"
elif unit_code == 'C':
if value >= 1e-6: return f"{value*1e6:.3f} µF"
elif value >= 1e-9: return f"{value*1e9:.3f} nF"
else: return f"{value*1e12:.3f} pF"
elif unit_code == 'L':
if value >= 1: return f"{value:.3f} H"
elif value >= 1e-3: return f"{value*1e3:.3f} mH"
else: return f"{value*1e6:.3f} µH"
elif unit_code == 'V':
return f"{value:.3f} V"
else:
return f"{value} ({unit_code})"
# --------------------------
# Serial reading thread
# --------------------------
ser = None
try:
ser = serial.Serial(PORT, BAUD, timeout=1)
except Exception as e:
print(f"Error opening {PORT}: {e}")
def read_serial():
global latest_reading, history
while True:
if ser and ser.is_open:
ser.write(b'FETCh?\n')
line = ser.readline().decode('ascii', errors='ignore').strip()
if line:
fields = line.split(',')
if len(fields) >= 5:
primary = format_measurement(fields[0], fields[3])
secondary = format_measurement(fields[1], fields[4])
reading = {
'primary': primary,
'secondary': secondary,
'mode': fields[2],
'unit': fields[3],
'extra': "|".join(fields[5:]) if len(fields) > 5 else "",
'raw': line
}
latest_reading = reading
history.appendleft(f"{primary} / {secondary}")
time.sleep(READ_INTERVAL)
if ser:
threading.Thread(target=read_serial, daemon=True).start()
# --------------------------
# Flask Web Server
# --------------------------
app = Flask(__name__)
HTML_TEMPLATE = """
<!doctype html>
<html>
<head>
<title>ZOYI Dashboard</title>
<style>
body { font-family: Arial, sans-serif; background: #2c2c2c; color: #eee; text-align:center; margin:0; padding:0;}
.container { width: 600px; margin: 20px auto; padding:20px; background:#1e1e1e; border-radius:10px; }
.main-reading { font-size: 4em; background:#ff7700; padding:20px; border-radius:10px; color:#fff; margin-bottom:10px;}
.secondary-reading { font-size: 3em; background:#00aa99; padding:15px; border-radius:10px; color:#fff; margin-bottom:10px;}
.history, .raw { background:#111; padding:10px; border-radius:5px; margin-top:10px; text-align:left; font-family: monospace; font-size: 1em; color:#ccc;}
.history div { margin: 3px 0; }
.button-panel { display:flex; flex-wrap: wrap; justify-content: center; gap:10px; margin-bottom:15px; }
.button-panel button { padding:10px 15px; border:none; border-radius:5px; font-size:1em; cursor:pointer; background:#444; color:#fff; }
.button-panel button:hover { background:#666; }
</style>
</head>
<body>
<div class="container">
<div class="button-panel">
<button onclick="sendCmd('FUNCtion:IMPedance:MAIN')">Functions</button>
<button onclick="sendCmd('FUNCtion:IMPedance:SUB')">Parameters</button>
<button onclick="sendCmd('FUNCtion:IMPedance:MODEL')">Mode</button>
<button onclick="sendCmd('FREQuency')">Freq</button>
<button onclick="sendCmd('VOLTage')">Level(V)</button>
<button onclick="sendCmd('BIAS:VOLTage')">Bias</button>
<button onclick="sendCmd('APERture')">Speed</button>
<button onclick="sendCmd('FUNCtion:IMPedance:RANGe')">Range</button>
</div>
<div class="main-reading" id="main">--</div>
<div class="secondary-reading" id="secondary_value">--</div>
<div class="history" id="history">
<h4>Last 5 readings</h4>
<div>--</div>
<div>--</div>
<div>--</div>
<div>--</div>
<div>--</div>
</div>
<div class="raw" id="raw">
Raw: --
</div>
</div>
<script>
async function update() {
const res = await fetch('/data');
const data = await res.json();
document.getElementById('main').textContent = data.primary || '--';
document.getElementById('secondary_value').textContent = data.secondary || '--';
const histDivs = document.querySelectorAll('#history div');
if(data.history){
for(let i=0; i<histDivs.length; i++){
histDivs[i].textContent = data.history[i] || '--';
}
}
document.getElementById('raw').textContent = 'Raw: ' + (data.raw || '--');
}
setInterval(update, 500);
update();
async function sendCmd(cmd){
const res = await fetch('/send', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({command:cmd})
});
const data = await res.json();
console.log('Sent:', data);
}
</script>
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/data')
def data():
return {**latest_reading, 'history': list(history)}
@app.route('/send', methods=['POST'])
def send():
cmd = request.json.get('command','')
if ser and ser.is_open:
ser.write((cmd+'\n').encode('ascii'))
return jsonify({'status':'ok','command':cmd})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
- Fişiere ataşate
-

- Zoyi ZT-DQ02 web page
- WhatsApp Image 2026-02-21 at 20.45.07.jpeg (141.27 KiB) Vizualizat de 12 ori
I spin this!
My PC: I7 3770, 16G Ram, GTX 980, SSD 180GB