diff --git a/artiq/devices/novatech409b/driver.py b/artiq/devices/novatech409b/driver.py index 411f7c55d..a8ffbdd2f 100644 --- a/artiq/devices/novatech409b/driver.py +++ b/artiq/devices/novatech409b/driver.py @@ -1,10 +1,10 @@ # Written by Joe Britton, 2015 -import time import math import logging +import asyncio -import serial +import asyncserial logger = logging.getLogger(__name__) @@ -41,22 +41,28 @@ class Novatech409B: self.simulation = True else: self.simulation = False - self.port = serial.serial_for_url( + self.port = asyncserial.AsyncSerial( serial_dev, baudrate=19200, bytesize=8, parity="N", stopbits=1, - xonxoff=0, - timeout=1.0) - self.setup() + xonxoff=0) def close(self): """Close the serial port.""" if not self.simulation: self.port.close() - def _ser_send(self, cmd, get_response=True): + async def _ser_readline(self): + c = await self.port.read(1) + r = c + while c != b"\n": + c = await self.port.read(1) + r += c + return r + + async def _ser_send(self, cmd, get_response=True): """Send a string to the serial port.""" # Low-level routine for sending serial commands to device. It sends @@ -67,48 +73,42 @@ class Novatech409B: if self.simulation: logger.info("simulation _ser_send(\"%s\")", cmd) else: - self.port.flushInput() - self.port.write((cmd + "\r\n").encode()) - result = self.port.readline().rstrip().decode() + logger.debug("_ser_send(\"%s\")", cmd) + self.port.ser.reset_input_buffer() + await self.port.write((cmd + "\r\n").encode()) if get_response: + result = (await self._ser_readline()).rstrip().decode() logger.debug("got response from device: %s", result) - if result == "OK": - pass - elif result == "": - raise UnexpectedResponse("Response from device timed out") - else: - try: - errstr = self.error_codes[result] - except KeyError: - errstr = "Unrecognized reply: '{}'".format(result) - s = "Error Code = {ec}, {ecs}".format( + if result != "OK": + errstr = self.error_codes.get(result, "Unrecognized reply") + s = "Erroneous reply from device: {ec}, {ecs}".format( ec=result, ecs=errstr) - raise UnexpectedResponse(s) + raise ValueError(s) else: pass - def reset(self): + async def reset(self): """Hardware reset of 409B.""" - self._ser_send("R", get_response=False) - time.sleep(1) - self.setup() + await self._ser_send("R", get_response=False) + await asyncio.sleep(1) + await self.setup() - def setup(self): + async def setup(self): """Initial setup of 409B.""" # Setup the Novatech 409B with the following defaults: # * command echo off ("E d") # * external clock ("") 10 MHz sinusoid -1 to +7 dBm - self._ser_send("E d", get_response=False) - self.set_phase_continuous(True) - self.set_simultaneous_update(False) + await self._ser_send("E d", get_response=False) + await self.set_phase_continuous(True) + await self.set_simultaneous_update(False) - def save_state_to_eeprom(self): + async def save_state_to_eeprom(self): """Save current state to EEPROM.""" - self._ser_send("S") + await self._ser_send("S") - def set_phase_continuous(self, is_continuous): + async def set_phase_continuous(self, is_continuous): """Toggle phase continuous mode. Sends the "M n" command. This turns off the automatic @@ -120,11 +120,11 @@ class Novatech409B: :param is_continuous: True or False """ if is_continuous: - self._ser_send("M n") + await self._ser_send("M n") else: - self._ser_send("M a") + await self._ser_send("M a") - def set_simultaneous_update(self, simultaneous): + async def set_simultaneous_update(self, simultaneous): """Set simultaneous update mode. Sends the "I m" command. In this mode an update @@ -134,29 +134,29 @@ class Novatech409B: simultaneously. """ if simultaneous: - self._ser_send("I m") + await self._ser_send("I m") else: - self._ser_send("I a") + await self._ser_send("I a") - def do_simultaneous_update(self): + async def do_simultaneous_update(self): """Apply update in simultaneous update mode.""" - self._ser_send("I p") + await self._ser_send("I p") - def set_freq(self, ch_no, freq): + async def set_freq(self, ch_no, freq): """Set frequency of one channel.""" # Novatech expects MHz - self._ser_send("F{:d} {:f}".format(ch_no, freq/1e6)) + await self._ser_send("F{:d} {:f}".format(ch_no, freq/1e6)) - def set_phase(self, ch_no, phase): + async def set_phase(self, ch_no, phase): """Set phase of one channel.""" # phase word is required by device # N is an integer from 0 to 16383. Phase is set to # N*360/16384 deg; in ARTIQ represent phase in cycles [0, 1] phase_word = round(phase*16383) cmd = "P{:d} {:d}".format(ch_no, phase_word) - self._ser_send(cmd) + await self._ser_send(cmd) - def set_gain(self, ch_no, volts): + async def set_gain(self, ch_no, volts): """Set amplitude of one channel.""" # due to error in Novatech it doesn't generate an error for @@ -167,9 +167,9 @@ class Novatech409B: raise ValueError(s) s = "V{:d} {:d}".format(ch_no, dac_value) - self._ser_send(s) + await self._ser_send(s) - def get_status(self): + async def get_status(self): if self.simulation: return ["00989680 2000 01F5 0000 00000000 00000000 000301", "00989680 2000 01F5 0000 00000000 00000000 000301", @@ -177,17 +177,19 @@ class Novatech409B: "00989680 2000 01F5 0000 00000000 00000000 000301", "80 BC0000 0000 0102 21"] else: - # status message is multi-line - self.port.flushInput() - self.port.write(("QUE" + "\r\n").encode()) - result = self.port.readlines() - result = [r.rstrip().decode() for r in result] + self.port.reset_input_buffer() + await self.port.write(("QUE" + "\r\n").encode()) + for i in range(5): + m = (await self._ser_readline()).rstrip().decode() + result.append(m) logger.debug("got device status: %s", result) return result - def ping(self): + async def ping(self): try: - stat = self.get_status() + stat = await self.get_status() + except asyncio.CancelledError: + raise except: return False # check that version number matches is "21" diff --git a/artiq/frontend/novatech409b_controller.py b/artiq/frontend/novatech409b_controller.py index f654a9849..bf4f32be4 100755 --- a/artiq/frontend/novatech409b_controller.py +++ b/artiq/frontend/novatech409b_controller.py @@ -5,6 +5,7 @@ import argparse import logging import sys +import asyncio from artiq.devices.novatech409b.driver import Novatech409B from artiq.protocols.pc_rpc import simple_server_loop @@ -38,6 +39,7 @@ def main(): sys.exit(1) dev = Novatech409B(args.device if not args.simulation else None) + asyncio.get_event_loop().run_until_complete(dev.setup()) try: simple_server_loop( {"novatech409b": dev}, bind_address_from_args(args), args.port)