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

Mesaj #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
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) Vizualizat de 13 ori
I spin this!
My PC: I7 3770, 16G Ram, GTX 980, SSD 180GB

Avatar utilizator




GameForest

Cea mai tare Comunitate online dedicata jocurilor!
GameForest este un loc perfect pentru toti cei care se bucura de jocuri pe internet!
Va invitam pe serverele noastre de Counter-Strike Source, Counter-Strike Global Offensive si Team Fortress 2!

Aceasta comunitate exista din 16 Mar 2011 19:37 si este online de 14 Ani, 11 Luni si 20 Zile
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