#!/usr/bin/python2.7 # -*- coding: utf-8 -*- """package eddsctrl_server author Benoit Dubois copyright FEMTO ENGINEERING license GPL v3.0+ brief DDS controller, server part. details This package provides interface to handle DDS package. This package implements server part of a client/server architecture. Code inspired from Python Module of the Week website (BSD licence): http://pymotw.com/2/select/ for the logic and from: http://eli.thegreenplace.net/2011/08/02/length-prefix-framing-for-protocol-buffers/ for the message using the length prefix technique. """ import logging CONSOLE_LOG_LEVEL = logging.DEBUG FILE_LOG_LEVEL = logging.DEBUG import os try: import configparser except ImportError: import ConfigParser as configparser try: from queue import Queue, Empty except ImportError: from Queue import Queue, Empty from struct import pack, unpack from select import select from socket import socket, AF_INET, SOCK_STREAM from dds.ad9912dev import Ad9912Dev as DdsDevice # for actual use #from dds.dds_emul import TestUsbDds as DdsDevice # for test without DDS from eddsctrl_server.constants import APP_NAME, SERVER_PORT, SERVERHOST, \ DEFAULT_IFREQ, DEFAULT_OFREQ, DEFAULT_AMP, DEFAULT_PHY, \ CFG_SERVER_FILE, LOG_SERVER_FILE #============================================================================== class EDdsCtrlServer(object): """Class dedicated to interface socket client with DDS device. Provide a basic interface to handle client queries: Message is built using the length prefix technique: length is sent as a packed 4-byte little-endian integer. Allow only 4 queries, changement of output frequency, input frequency, phase or amplitude value. Message structure is simple: - Length of message, - Exclamation mark - Character identifier, - Exclamation mark - Value - Exclamation mark There are 8 valid identifiers: - 'o[?]' for set/get output frequency - 'i[?]' for set/get input frequency - 'p[?]' for set/get phase - 'a[?]' for set/get amplitude """ def __init__(self, settings_file=None): """Constructor: initialize the server. :param settings_file: file containing setting data value (str) :returns: None """ # Retrieve parameter values from 'ini' file if settings_file is None: raise ValueError("Parameter settings_file missing.") self._settingsf = settings_file config = configparser.ConfigParser() config.read(settings_file) try: self._port = config.getint('dds_ctrl', 'server_port') ifreq = config.getfloat('dds_ctrl', 'ifreq') ofreq = config.getfloat('dds_ctrl', 'ofreq') phase = config.getfloat('dds_ctrl', 'phase') amp = config.getint('dds_ctrl', 'amp') except KeyError as ex: logging.critical("Correct or delete the configuration file.") raise KeyError("Key %s not found in configuration file:"% str(ex)) # Init devices self._init_dds(ifreq, ofreq, phase, amp) self._init_server() # Start threaded server self._server_loop() def _init_dds(self, ifreq, ofreq, phase, amp): """Create and configure a DDS object. :param ifreq: dds input frequency (float) :param ofreq: dds output frequency (float) :param phase: dds output phase (float) :param amp: dds output amplitude (int) :returns: None """ self._dds = DdsDevice() try: self._dds.set_ifreq(ifreq) self._dds.set_ofreq(ofreq) self._dds.set_phy(phase) self._dds.set_amp(amp) self._dds.set_hstl_output_state(False) self._dds.set_cmos_output_state(False) except Exception as ex: raise Exception("Unexpected error during DDS initialisation: %s" \ % str(ex)) logging.debug("DDS inititialization done") def _init_server(self): """Create and configure a basic server object. :returns: None """ try: self.server = socket(AF_INET, SOCK_STREAM) self.server.setblocking(0) host = SERVERHOST # Get local machine name self.server.bind((host, self._port)) # Bind to the port self.server.listen(3) # Now wait for client connection except IOError as ex: raise IOError("EDDSCTRLSERVER: %s, have you closed properly the " \ "server? " % str(ex)) except Exception as ex: raise Exception("EDDSCTRLSERVER: unexpected error during server " \ "initialisation: %s" % str(ex)) # Sockets from which we expect to read self.inputs = [self.server] # Sockets to which we expect to write self.outputs = [] # Outgoing message queues (socket:Queue) self.message_queues = {} logging.debug("Server initialization done") def _server_loop(self): """Main server loop: Handle connection, read and write on server socket. :returns: None """ while self.inputs: # Await an event on a readable socket descriptor readable, writable, exceptional = select(self.inputs, \ self.outputs, \ self.inputs) # Handle inputs self._handle_readable(readable) # Handle outputs self._handle_writable(writable) # Handle exceptional condition self._handle_exceptional(exceptional) def _handle_readable(self, socket_list): """Read message from client. Message is built using the length prefix technique. See: http://eli.thegreenplace.net/2011/08/02/length-prefix-framing-for-protocol-buffers/ :param socket_list: List of socket usable to receive data :returns: None """ for sock in socket_list: # Received a connect to the server (listening) socket if sock is self.server: self._accept_connect() # Established connection else: header_data = self._recv_n_bytes(sock, 4) if header_data == None: logging.error("Message seems received but no data to read") return if len(header_data) == 4: msg_len = unpack('