i2cdriver/python/samples/i2cgui.py

434 lines
14 KiB
Python

#!/usr/bin/env python
import time
import struct
import sys
import os
import re
import csv
import threading
from functools import partial
import serial.tools.list_ports as slp
import serial
try:
import wx
import wx.lib.newevent as NE
except ImportError:
print("i2cgui.py needs wxPython, but it wasn't found.")
print("See https://www.wxpython.org/pages/downloads/")
sys.exit(1)
import i2cdriver
pullup_vals = [
"disabled",
"2.2K",
"4.3K",
"1.5K",
"4.7K",
"1.5K",
"2.2K",
"1.1K",
]
PingEvent, EVT_PING = NE.NewEvent()
def ping_thr(win):
while True:
wx.PostEvent(win, PingEvent())
time.sleep(1)
StopCapture = False
def capture_thr(sd, log_csv):
global StopCapture
c = sd.capture_start(True)
with open(log_csv, 'w') as csvfile:
logcsv = csv.writer(csvfile)
for token in c():
if token:
sd.dumpcount += 1
token.dump(logcsv, "csv") # write to CSV
if StopCapture:
break
StopCapture = False
sd.capture_stop()
class HexTextCtrl(wx.TextCtrl):
def __init__(self, *args, **kwargs):
super(HexTextCtrl, self).__init__(*args, **kwargs)
self.Bind(wx.EVT_TEXT, self.on_text)
def on_text(self, event):
event.Skip()
selection = self.GetSelection()
value = self.GetValue().upper()
hex = " 0123456789ABCDEF"
value = "".join([c for c in value if c in hex])
self.ChangeValue(value)
self.SetSelection(*selection)
class MyDialog(wx.Dialog):
def __init__(self, parent, title):
super(MyDialog, self).__init__(parent, title = title, size = (250,150))
panel = wx.Panel(self)
self.btn = wx.Button(panel, wx.ID_OK, label = "ok", size = (50,20), pos = (75,50))
class Frame(wx.Frame):
def __init__(self, preferred = None):
self.sd = None
def widepair(a, b):
r = wx.BoxSizer(wx.HORIZONTAL)
r.Add(a, 1, wx.LEFT)
r.AddStretchSpacer(prop=1)
r.Add(b, 1, wx.RIGHT)
return r
def pair(a, b):
r = wx.BoxSizer(wx.HORIZONTAL)
r.Add(a, 1, wx.LEFT)
r.Add(b, 0, wx.RIGHT)
return r
def rpair(a, b):
r = wx.BoxSizer(wx.HORIZONTAL)
r.Add(a, 0, wx.LEFT)
r.Add(b, 1, wx.RIGHT)
return r
def epair(a, b):
r = wx.BoxSizer(wx.HORIZONTAL)
r.Add(a, 1, wx.LEFT)
r.Add(b, 1, wx.RIGHT)
return r
def label(s):
return wx.StaticText(self, label = s)
def button(s, f):
r = wx.Button(self, label = s)
r.Bind(wx.EVT_BUTTON, f)
return r
def hbox(items):
r = wx.BoxSizer(wx.HORIZONTAL)
[r.Add(i, 0, wx.EXPAND) for i in items]
return r
def hcenter(i):
r = wx.BoxSizer(wx.HORIZONTAL)
r.AddStretchSpacer(prop=1)
r.Add(i, 2, wx.CENTER)
r.AddStretchSpacer(prop=1)
return r
def vbox(items):
r = wx.BoxSizer(wx.VERTICAL)
[r.Add(i, 0, wx.EXPAND) for i in items]
return r
wx.Frame.__init__(self, None, -1, "I2CDriver")
self.bold = self.GetFont().Bold()
self.addrfonts = [
self.GetFont(),
self.bold
]
self.label_serial = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.label_voltage = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.label_current = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.label_temp = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.label_speed = wx.Choice(self, choices = ["100", "400"])
self.label_speed.Bind(wx.EVT_CHOICE, self.set_speed)
pupch = sorted(set(pullup_vals), reverse = True)
self.label_pullups = wx.Choice(self, choices = pupch)
self.label_pullups.Bind(wx.EVT_CHOICE, self.set_pullups)
self.label_sda = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.label_scl = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.label_uptime = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT)
self.dynamic = [
self.label_voltage,
self.label_current,
self.label_temp,
self.label_speed,
self.label_pullups,
self.label_sda,
self.label_scl,
self.label_uptime
]
[d.Enable(False) for d in self.dynamic]
self.Bind(EVT_PING, self.refresh)
def addrbutton(s):
r = wx.RadioButton(self, label = s)
r.Bind(wx.EVT_RADIOBUTTON, self.choose_addr)
return r
self.heat = {i:addrbutton("%02X" % i) for i in range(0x08, 0x78)}
# Be careful here, GridSizer changed between wx 3.x and 4.x
devgrid = wx.GridSizer(8)
devgrid.SetHGap(4)
devgrid.SetVGap(6)
for i,l in sorted(self.heat.items()):
devgrid.Add(l)
self.monitor = False
self.ckM = wx.ToggleButton(self, label = "Monitor mode")
self.ckM.Bind(wx.EVT_TOGGLEBUTTON, self.check_m)
self.capture = False
self.ckC = wx.ToggleButton(self, label = "Capture mode")
self.ckC.Bind(wx.EVT_TOGGLEBUTTON, self.check_c)
self.txVal = HexTextCtrl(self, size=wx.DefaultSize, style=0)
self.rxVal = HexTextCtrl(self, size=wx.DefaultSize, style=wx.TE_READONLY)
txButton = wx.Button(self, label = "write")
txButton.Bind(wx.EVT_BUTTON, partial(self.write, self.txVal))
self.rxCount = wx.SpinCtrl(self, min = 1)
rxButton = wx.Button(self, label = "read")
rxButton.Bind(wx.EVT_BUTTON, self.read)
self.dev_widgets = [txButton, rxButton]
self.reset_button = button("i2c reset", self.reset)
self.stop_button = button("stop", self.stop)
self.stop_button.Enable(False)
self.allw = [self.ckM, self.reset_button]
[w.Enable(False) for w in self.allw]
self.devs = self.devices()
cb = wx.ComboBox(self, choices = sorted(self.devs.keys()), style = wx.CB_READONLY)
cb.Bind(wx.EVT_COMBOBOX, self.choose_device)
self.no_addr()
self.addr = None
[self.hot(i, False) for i in self.heat]
self.started = False
info = vbox([
pair(label("Serial"), self.label_serial),
pair(label("Voltage"), self.label_voltage),
pair(label("Current"), self.label_current),
pair(label("Temp."), self.label_temp),
pair(label("SDA"), self.label_sda),
pair(label("SCL"), self.label_scl),
pair(label("Running"), self.label_uptime),
pair(label("Speed"), self.label_speed),
pair(label("Pullups"), self.label_pullups),
])
vb = vbox([
label(""),
hcenter(cb),
label(""),
hcenter(epair(self.ckM, self.ckC)),
hcenter(self.reset_button),
label(""),
hcenter(info),
label(""),
hcenter(devgrid),
label(""),
hcenter(pair(self.txVal, txButton)),
hcenter(pair(self.rxVal, hbox([self.rxCount, rxButton]))),
label(""),
hcenter(self.stop_button),
label(""),
])
self.SetSizerAndFit(vb)
self.SetAutoLayout(True)
d1 = None
while (d1 is None) and (len(self.devs) > 0):
if preferred in self.devs:
d1 = preferred
else:
d1 = min(self.devs)
try:
self.connect(self.devs[d1])
except:
del self.devs[d1]
d1 = None
cb.Set(sorted(self.devs.keys()))
if d1 is not None:
cb.SetValue(d1)
t = threading.Thread(target=ping_thr, args=(self, ))
t.setDaemon(True)
t.start()
def start(self, rw):
self.sd.start(self.addr, rw)
self.started = True
self.stop_button.Enable(True)
def stop(self, e = None):
self.sd.stop()
self.started = False
self.stop_button.Enable(False)
def reset(self, e = None):
self.sd.reset()
self.started = False
def write(self, htc, e):
if (self.addr is not None) and htc.GetValue():
vv = [int(c,16) for c in htc.GetValue().split()]
self.start(0)
self.sd.write(vv)
def read(self, e):
n = int(self.rxCount.GetValue())
if self.addr is not None:
self.start(1)
r = self.sd.read(n)
bb = struct.unpack("B"*n, r)
self.rxVal.SetValue(" ".join(["%02X" % b for b in bb]))
self.stop()
def devices(self):
if sys.platform in ('win32', 'cygwin'):
return {pi.device: pi.device for pi in slp.comports()}
elif sys.platform == 'darwin':
devdir = "/dev/"
pattern = "^cu.usbserial-(.*)"
else:
devdir = "/dev/serial/by-id/"
pattern = "^usb-FTDI_FT230X_Basic_UART_(........)-"
if not os.access(devdir, os.R_OK):
return {}
devs = os.listdir(devdir)
def filter(d):
m = re.match(pattern, d)
if m:
return (m.group(1), devdir + d)
seldev = [filter(d) for d in devs]
return dict([d for d in seldev if d])
def connect(self, dev):
self.sd = i2cdriver.I2CDriver(dev)
[w.Enable(True) for w in self.allw]
self.refresh(None)
def refresh(self, e):
if self.sd and not self.monitor and not self.capture:
lowhigh = ["LOW", "HIGH"]
self.sd.getstatus()
self.label_serial.SetLabel(self.sd.serial)
self.label_voltage.SetLabel("%.2f V" % self.sd.voltage)
self.label_current.SetLabel("%d mA" % self.sd.current)
self.label_temp.SetLabel("%.1f C" % self.sd.temp)
self.label_speed.SetSelection({100:0, 400:1}[self.sd.speed])
self.label_pullups.SetStringSelection(pullup_vals[self.sd.pullups & 7])
self.label_sda.SetLabel(lowhigh[self.sd.sda])
self.label_scl.SetLabel(lowhigh[self.sd.scl])
days = self.sd.uptime // (24 * 3600)
rem = self.sd.uptime % (24 * 3600)
hh = rem // 3600
mm = (rem // 60) % 60
ss = rem % 60;
self.label_uptime.SetLabel("%d:%02d:%02d:%02d" % (days, hh, mm, ss))
[d.Enable(True) for d in self.dynamic]
if not self.started and (self.sd.sda == 1) and (self.sd.scl == 1):
devs = self.sd.scan(True)
for i,l in self.heat.items():
self.hot(i, i in devs)
def choose_device(self, e):
self.connect(self.devs[e.EventObject.GetValue()])
def no_addr(self):
[w.Enable(False) for w in self.dev_widgets]
def choose_addr(self, e):
o = e.EventObject
v = o.GetValue()
if v:
self.addr = int(o.GetLabel(), 16)
[w.Enable(True) for w in self.dev_widgets]
def check_m(self, e):
self.monitor = e.EventObject.GetValue()
self.sd.monitor(self.monitor)
[d.Enable(not self.monitor) for d in self.dynamic]
if self.monitor:
[self.hot(i, False) for i in self.heat]
def check_c(self, e):
global StopCapture
cm = e.EventObject.GetValue()
# self.sd.monitor(self.monitor)
if cm:
openFileDialog = wx.FileDialog(self, "CSV dump to file", "", "",
"CSV files (*.csv)|*.csv",
wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
openFileDialog.ShowModal()
self.log_csv = openFileDialog.GetPath()
openFileDialog.Destroy()
if self.log_csv == u"":
e.EventObject.SetValue(False)
return
StopCapture = False
self.sd.dumpcount = 0
t = threading.Thread(target=capture_thr, args=(self.sd, self.log_csv))
t.setDaemon(True)
t.start()
else:
StopCapture = True
wx.MessageBox("Capture finished. %d events written to \"%s\"" % (self.sd.dumpcount, self.log_csv), "Message", wx.OK | wx.ICON_INFORMATION)
while StopCapture:
pass
[d.Enable(not cm) for d in self.dynamic]
if cm:
[self.hot(i, False) for i in self.heat]
self.capture = cm
def set_speed(self, e):
w = e.EventObject
s = int(w.GetString(w.GetCurrentSelection()))
self.sd.setspeed(s)
def set_pullups(self, e):
w = e.EventObject
s = w.GetString(w.GetCurrentSelection())
code = pullup_vals.index(s)
self.sd.setpullups(code | (code << 3))
def hot(self, i, s):
l = self.heat[i]
if s:
l.SetForegroundColour((0,0,0))
l.SetFont(self.addrfonts[1])
else:
l.SetForegroundColour((160,) * 3)
l.SetFont(self.addrfonts[0])
l.Enable(s)
if i == self.addr:
[w.Enable(s) for w in self.dev_widgets]
if __name__ == '__main__':
app = wx.App(0)
try:
f = Frame(*sys.argv[1:])
f.Show(True)
app.MainLoop()
except:
import sys, traceback
xc = traceback.format_exception(*sys.exc_info())
dlg = wx.MessageDialog(None, "".join(xc), "i2cgui Error Trap (%s)" % i2cdriver.__version__, wx.OK | wx.ICON_WARNING)
dlg.ShowModal()
dlg.Destroy()