07 — Code
Python driver
A minimal driver class implementing the protocol decisions above. Only dependency is pyusb.
import usb.core
import usb.util
import time
class BP4610:
"""NF BP4610 bipolar DC power supply, JEMIMA USB488.
Tested on Windows 11 with firmware 1.40.
Implementation notes:
- ASCII commands carry no \\n / \\r\\n terminator; ZLP only.
- *CLS uses the binary common-command form (0x00 0x01).
- REMOTE entry via control transfer (0x20, 0x03, wValue=1).
"""
VID = 0x0D4A
PID = 0x0006
EP_OUT = 0x01
EP_IN = 0x82
MODE_CV_INT, MODE_CV_EXT, MODE_CV_ADD = 0, 1, 2
MODE_CC_INT, MODE_CC_EXT, MODE_CC_ADD = 3, 4, 5
def __init__(self, timeout_ms=2000):
self.dev = usb.core.find(idVendor=self.VID, idProduct=self.PID)
if self.dev is None:
raise RuntimeError('BP4610 not found')
try:
self.dev.set_configuration()
except usb.core.USBError:
pass
self.timeout = timeout_ms
self._drain_input()
self.set_remote(True)
self.cls()
def _drain_input(self):
try:
while True:
self.dev.read(self.EP_IN, 64, timeout=50)
except usb.core.USBError:
pass
def set_remote(self, remote=True):
ren = 0x0001 if remote else 0x0000
self.dev.ctrl_transfer(0x20, 0x03, ren, 0, 0, timeout=self.timeout)
def cls(self):
self.dev.write(self.EP_OUT, b'\x00\x01', timeout=self.timeout)
self.dev.write(self.EP_OUT, b'', timeout=self.timeout)
def write(self, command):
if isinstance(command, str):
command = command.encode('ascii')
command = command.rstrip(b'\r\n')
self.dev.write(self.EP_OUT, command, timeout=self.timeout)
self.dev.write(self.EP_OUT, b'', timeout=self.timeout) # ZLP
def read(self, max_bytes=1024):
chunks = []
try:
while True:
data = self.dev.read(self.EP_IN, 64, timeout=self.timeout)
if len(data) == 0:
break
chunks.append(bytes(data))
if len(data) < 64:
break
if sum(len(c) for c in chunks) >= max_bytes:
break
except usb.core.USBError as e:
if 'timeout' not in str(e).lower() or not chunks:
raise
full = b''.join(chunks)
if len(full) > 0 and 0x20 <= full[0] <= 0x7F:
return full.decode('ascii', errors='replace').strip().strip('"')
return full
def query(self, command):
self.write(command)
return self.read()
def close(self):
try:
self.write('CURR 0'); time.sleep(0.1)
self.write('OUTP 0'); time.sleep(0.2)
self.set_remote(False)
except: pass
usb.util.dispose_resources(self.dev)
# ----- high-level helpers -----
def set_mode(self, m): self.write(f'MODE {m}')
def output(self, on=True): self.write(f'OUTP {1 if on else 0}')
def set_voltage(self, v): self.write(f'VOLT {v}') # OFF only
def set_current(self, i): self.write(f'CURR {i}') # ON ok
def set_v_limits(self, vmax, vmin):
self.write(f'LMVP {vmax}'); self.write(f'LMVM {vmin}')
def set_i_limits(self, imax, imin):
self.write(f'LMCP {imax}'); self.write(f'LMCM {imin}')
def measure(self):
return {
'V_dc': float(self.query('MVLT?')),
'V_ac': float(self.query('MACV?')),
'I_dc': float(self.query('MCUR?')),
'I_ac': float(self.query('MACC?')),
}
Basic usage
from bp4610 import BP4610
bp = BP4610()
print(bp.query('*IDN?'))
bp.set_i_limits(5.0, -5.0)
bp.set_v_limits(30.0, -30.0)
bp.set_mode(BP4610.MODE_CC_INT)
bp.set_current(0)
bp.output(True)
bp.set_current(2.5) # changes immediately
print(bp.measure())
bp.close()