if __name__ == '__main__': s = SPIDriver(sys.argv[1]) t1 = time.time() + float(sys.argv[2]) i = 0 random.seed(7) while time.time() < t1: expected = s.ccitt_crc s.unsel() l = 1 + rnd(100) db = [rnd(256) for j in range(l)] s.write(db) expected = crc16xmodem(db, expected) s.unsel() db = [rnd(256) for j in range(64)] r = list(array.array('B', s.writeread(db))) expected = crc16xmodem(db, expected) expected = crc16xmodem(r, expected) s.getstatus() print("expected=%04x actual=%04x" % (expected, s.ccitt_crc)) assert expected == s.ccitt_crc, "pass %d with %d bytes %s, expected=%04x actual=%04x" % ( i, len(db), list(db), expected, s.ccitt_crc) i += 1 for i in range(20): s.seta(0) s.setb(0) s.seta(1) s.setb(1)
class Frame(wx.Frame): def __init__(self): 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 label(s): return wx.StaticText(self, label = s) 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, "SPIDriver") 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_uptime = wx.StaticText(self, label = "-", style = wx.ALIGN_RIGHT) self.Bind(EVT_PING, self.refresh) self.ckCS = wx.CheckBox(self, label = "CS") self.ckA = wx.CheckBox(self, label = "A") self.ckB = wx.CheckBox(self, label = "B") self.ckCS.Bind(wx.EVT_CHECKBOX, self.check_cs) self.ckA.Bind(wx.EVT_CHECKBOX, self.check_a) self.ckB.Bind(wx.EVT_CHECKBOX, self.check_b) ps = self.GetFont().GetPointSize() fmodern = wx.Font(ps, wx.MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) def logger(): r = wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_RIGHT | wx.TE_DONTWRAP) r.SetBackgroundColour(wx.Colour(224, 224, 224)) r.SetFont(fmodern) return r self.txMISO = logger() self.txMOSI = logger() self.txVal = HexTextCtrl(self, size=wx.DefaultSize, style=0) self.txVal.SetMaxLength(2) self.txVal.SetFont(wx.Font(14 * ps // 10, wx.MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) txButton = wx.Button(self, label = "Transfer") txButton.Bind(wx.EVT_BUTTON, partial(self.transfer, self.txVal)) txButton.SetDefault() self.allw = [self.ckCS, self.ckA, self.ckB, self.txVal, txButton, self.txMISO, self.txMOSI] [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) vb = vbox([ label(""), hcenter(cb), label(""), hcenter(pair( vbox([ label("Serial"), label("Voltage"), label("Current"), label("Temp."), label("Running"), ]), vbox([ self.label_serial, self.label_voltage, self.label_current, self.label_temp, self.label_uptime, ]) )), label(""), rpair(label("MISO"), self.txMISO), rpair(label("MOSI"), self.txMOSI), label(""), hcenter(pair(self.ckCS, hbox([self.ckA, self.ckB]))), label(""), hcenter(pair(self.txVal, txButton)), label(""), ]) self.SetSizerAndFit(vb) self.SetAutoLayout(True) if len(self.devs) > 0: d1 = min(self.devs) self.connect(self.devs[d1]) cb.SetValue(d1) t = threading.Thread(target=ping_thr, args=(self, )) t.setDaemon(True) t.start() def devices(self): if sys.platform == 'darwin': devdir = "/dev/" pattern = "^tty.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 = SPIDriver(dev) [w.Enable(True) for w in self.allw] self.ckCS.SetValue(not self.sd.cs) self.ckA.SetValue(self.sd.a) self.ckB.SetValue(self.sd.b) self.refresh(None) def refresh(self, e): if self.sd: 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) 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)) def choose_device(self, e): self.connect(self.devs[e.EventObject.GetValue()]) def check_cs(self, e): if e.EventObject.GetValue(): self.sd.sel() else: self.sd.unsel() def check_a(self, e): self.sd.seta(e.EventObject.GetValue()) def check_b(self, e): self.sd.setb(e.EventObject.GetValue()) def transfer(self, htc, e): if htc.GetValue(): txb = int(htc.GetValue(), 16) rxb = struct.unpack("B", self.sd.writeread(struct.pack("B", txb)))[0] self.txMOSI.AppendText(" %02X" % txb) self.txMISO.AppendText(" %02X" % rxb) htc.ChangeValue("")
return [rnd(256) for i in range(n)] if __name__ == '__main__': if len(sys.argv) > 1: s = SPIDriver(sys.argv[1]) else: s = SPIDriver() # print(s) # t1 = time.time() + float(sys.argv[2]) while True: # time.time() < t1: for i in range(50): random.choice([ lambda: s.seta(rnd(2)), lambda: s.setb(rnd(2)), lambda: s.sel(), lambda: s.unsel(), lambda: s.writeread(pattern(1 + rnd(1))), # lambda: s.read(1 + rnd(12)), # lambda: s.getstatus() ])() os.system("outlet.py 8 on ; outlet.py 8 off") time.sleep(3) print(hex(s.debug)) while 0: s.sel() s.write(b'ABCDEF') s.unsel() time.sleep(.050)
class SPIDriverWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title="SPIDriver") self.set_border_width(10) self.sd = SPIDriver() def pair(a, b): r = Gtk.HBox(spacing=6) r.pack_start(a, False, True, 0) r.pack_end(b, False, True, 0) return r def label(s): r = Gtk.Label() r.set_text(s) return r def vbox(items): r = Gtk.VBox(spacing=6) [r.pack_start(i, True, True, 0) for i in items] return r def hbox(items): r = Gtk.HBox(spacing=6) [r.pack_start(i, False, True, 0) for i in items] return r def checkbutton(name, state, click): r = Gtk.CheckButton(name) r.set_active(state) r.connect("clicked", click) return r def button(name, click): r = Gtk.Button(name) r.connect("clicked", click) return r self.label_voltage = Gtk.Label() self.label_current = Gtk.Label() self.label_temp = Gtk.Label() self.tx = Gtk.Entry() self.tx.set_width_chars(20) self.tx.connect('changed', self.edit) self.rx = Gtk.Entry() self.rx.set_width_chars(20) self.rx.connect('button-press-event', lambda a, b: True) self.rx.set_property('editable', False) self.button_send = button("Send", self.send) self.button_send.set_sensitive(False) self.add( vbox([ pair(label("Voltage"), self.label_voltage), pair(label("Current"), self.label_current), pair(label("Temp"), self.label_temp), Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), hbox([ checkbutton("CS", 1 - self.sd.cs, self.click_cs), checkbutton("A", self.sd.a, self.click_a), checkbutton("B", self.sd.b, self.click_b), ]), pair(self.tx, self.button_send), pair(self.rx, button("Recv", self.recv)), ])) self.refresh() GLib.timeout_add(1000, self.refresh) def refresh(self): self.sd.getstatus() self.label_voltage.set_text("%.2f V" % self.sd.voltage) self.label_current.set_text("%d mA" % self.sd.current) self.label_temp.set_text("%.1f C" % self.sd.temp) return True def click_cs(self, button): [self.sd.unsel, self.sd.sel][ison(button)]() def click_a(self, button): self.sd.seta(ison(button)) def click_b(self, button): self.sd.setb(ison(button)) def edit(self, _): b = self.tx.get_buffer() valid = all([ishex(w) for w in b.get_text().split()]) self.button_send.set_sensitive(valid) def transfer(self, byte): byte = struct.unpack("B", self.sd.writeread(struct.pack("B", byte)))[0] txb = self.tx.get_buffer() txb.delete_text(0, -1) rxb = self.rx.get_buffer() rxb.set_text(rxb.get_text()[-17:] + " %02x" % byte, -1) def send(self, _): b = self.tx.get_buffer() for w in b.get_text().split(): self.transfer(int(w, 16)) def recv(self, _): self.transfer(0xff)
class SpiDriverGui: """ Setup main GUI window. Main windows is a grid/table of widgets. Data variable is attached to each tkinter widget e.g., self.ser_num.var.set('blah') on_refresh_gui() will typically update each field. """ def __init__(self): self.spi_driver = None # none yet. self.grid = tkinter.Tk() # Root window is used as a grid or table. self.grid.configure(padx=8, pady=8) # add padding around grid. self.grid.title(os.path.basename(__file__)) # Display filename in title bar. # region Compose GUI widgets. row = 0 # row counter, used to add widgets to grid. # region COM port combo. tkinter.Label(self.grid, text='Port').grid(row=row, column=0) comports = [port.device for port in serial.tools.list_ports.comports()] self.port_combo = tkinter.ttk.Combobox(self.grid, values=comports, width=10) self.port_combo.grid(row=row, column=1) self.port_prev = None if len(comports) > 0: self.port_combo.current(len(comports)-1) # Select last entry. row += 1 # endregion COM port combo. # region Diagnostic fields def create_display_parameter(str_name, row) -> tkinter.Entry: lbl = tkinter.Label(self.grid, text=str_name) lbl.grid(row=row, column=0) string_var = tkinter.StringVar() field = tkinter.Entry(self.grid, textvariable=string_var, state="readonly", width=12) field.grid(row=row, column=1) field.var = string_var # Bind the var to the Entry. return field self.ser_num = create_display_parameter('Serial', row); row += 1 self.voltage = create_display_parameter('Voltage', row); row += 1 self.current = create_display_parameter('Current', row); row += 1 self.deg_c = create_display_parameter('Temp', row); row += 1 self.uptime = create_display_parameter('Uptime', row); row += 1 self.miso = create_display_parameter('MISO', row); row += 1 self.mosi = create_display_parameter('MOSI', row); row += 1 # endregion Diagnostic fields # region 3 checkboxes # Add three checkboxes to the same row in the table. # nCS is inverted (active low) chk_frame = tkinter.Frame(self.grid) v = tkinter.IntVar(); self.chk_ncs = tkinter.Checkbutton(chk_frame, text="nCS", variable=v, command=self.on_chk_ncs ); self.chk_ncs.pack(side=tkinter.LEFT); self.chk_ncs.var = v v = tkinter.IntVar(); self.chk_a = tkinter.Checkbutton(chk_frame, text="A", variable=v, command=self.on_chk_a ); self.chk_a.pack(side=tkinter.LEFT); self.chk_a.var = v v = tkinter.IntVar(); self.chk_b = tkinter.Checkbutton(chk_frame, text="B", variable=v, command=self.on_chk_b ); self.chk_b.pack(side=tkinter.LEFT); self.chk_b.var = v chk_frame.grid(row=row, column=0, columnspan=2); row += 1 # endregion 3 checkboxes tkinter.Label(self.grid, text='Send bytes').grid(row=row, column=0) v = tkinter.StringVar() self.send_bytes = tkinter.Entry(self.grid, textvariable=v, width=12) self.send_bytes.grid(row=row, column=1) self.send_bytes.var = v v.set('12 AB') row += 1 self.btn_send = tkinter.Button(self.grid, text='Transfer', command = self.on_transfer); self.btn_send.grid(row=row, column=1) #endregion Compose GUI widgets. self.grid.after(500, self.on_refresh_gui) # Establish a refresh routine. self.grid.mainloop() # launch GUI. #region Event handlers (typically start with 'on_'). def on_chk_a(self): self.spi_driver.seta(self.chk_a.var.get()) def on_chk_b(self): self.spi_driver.setb(self.chk_b.var.get()) def on_chk_ncs(self): self.spi_driver.sel() if self.chk_ncs.var.get() else self.spi_driver.unsel() ''' Main refresh function used to update GUI widgets. Called periodically using 'tkinter.after()' ''' def on_refresh_gui(self): self.grid.after(500, self.on_refresh_gui) if self.port_prev != self.port_combo.get(): self.spi_driver = None self.port_prev = self.port_combo.get() if len(self.port_prev) > 0: try: self.spi_driver = SPIDriver(self.port_prev) except serial.serialutil.SerialException: pass if self.spi_driver is not None: self.spi_driver.getstatus() self.ser_num.var.set(self.spi_driver.serial) self.voltage.var.set("{0:.1f} V".format(self.spi_driver.voltage)) self.current.var.set("{0:.1f} mA".format(self.spi_driver.current)) self.deg_c.var.set("{0:.1f} °C".format(self.spi_driver.temp)) self.uptime.var.set(str(datetime.timedelta(seconds=self.spi_driver.uptime))) # https://stackoverflow.com/a/775095/101252 self.chk_ncs.var.set(not self.spi_driver.cs) self.chk_a.var.set(self.spi_driver.a) self.chk_b.var.set(self.spi_driver.b) return else: self.ser_num.var.set('') self.voltage.var.set('') self.current.var.set('') self.deg_c.var.set('') self.uptime.var.set('') self.chk_ncs.var.set(2) self.chk_a.var.set(2) self.chk_b.var.set(2) self.miso.var.set('') self.mosi.var.set('') """ Exchange MOSI & MISO data with SPI Driver hardware using writeread(). """ def on_transfer(self): # bytearray.fromhex will accept spaces between bytes. # hexlify will create a string with a hexadecimal representation of the byte array. tx = bytearray.fromhex(self.send_bytes.var.get()) self.mosi.var.set(binascii.hexlify(tx)) rx = self.spi_driver.writeread(tx) self.miso.var.set(binascii.hexlify(rx))