Zoyi ZT-DQ02 Raspberry Pi web interface

Software - Discutii / Probleme
(Atentie - fara linkuri catre diverse softuri Crack/Warez! - Primiti BAN instant!)

Moderator: DeeJay

Zoyi ZT-DQ02 Raspberry Pi web interface

Post #1 » by 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

Code: Select all
#!/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)
Attachments
WhatsApp Image 2026-02-21 at 20.45.07.jpeg
Zoyi ZT-DQ02 web page
WhatsApp Image 2026-02-21 at 20.45.07.jpeg (141.27 KiB) Viewed 13 times
I spin this!
My PC: I7 3770, 16G Ram, GTX 980, SSD 180GB

User avatar




GameForest

The best online Gaming Community!
GameForest is the perfect place for all gamers!
Join our Counter-Strike Source, Counter-Strike Global Offensive and Team Fortress 2 dedicated servers and have a great fun time!

Forum started on 16 Mar 2011 19:37 and in existence for 14 Years, 11 Months and 20 Days
GameForest, WebForest are registered trademarks of IDeSys NetWorks S.R.L., Valve, the Valve logo, Half-Life, the Half-Life logo, the Lambda logo, Steam, the Steam logo, Team Fortress, the Team Fortress logo, Opposing Force, Day of Defeat, the Day of Defeat logo, Counter-Strike, the Counter-Strike logo, Source, the Source logo, and Counter-Strike: Condition Zero are trademarks and/or registered trademarks of Valve Corporation. All other trademarks are property of their respective owners. © GameForest   Powered by IDeSys NetWorks