Home | History | Annotate | Download | only in cellular
      1 #!/usr/bin/python
      2 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Wrapper for an RF switch built on an Elexol EtherIO24.
      7 
      8 The EtherIO is documented at
      9         http://www.elexol.com/IO_Modules/Ether_IO_24_Dip_R.php
     10 
     11 This file is both a python module and a command line utility to speak
     12 to the module
     13 """
     14 
     15 import cellular_logging
     16 import collections
     17 import socket
     18 import struct
     19 import sys
     20 
     21 log = cellular_logging.SetupCellularLogging('ether_io_rf_switch')
     22 
     23 
     24 class Error(Exception):
     25     pass
     26 
     27 
     28 class EtherIo24(object):
     29     """Encapsulates an EtherIO24 UDP-GPIO bridge."""
     30 
     31     def __init__(self, hostname, port=2424):
     32         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     33         self.socket.bind(('', 0))
     34         self.destination = (hostname, port)
     35         self.socket.settimeout(3)   # In seconds
     36 
     37     def SendPayload(self, payload):
     38         self.socket.sendto(payload, self.destination)
     39 
     40     def SendOperation(self, opcode, list_bytes):
     41         """Sends the specified opcode with [list_bytes] as an argument."""
     42         payload = opcode + struct.pack(('=%dB' % len(list_bytes)), *list_bytes)
     43         self.SendPayload(payload)
     44         return payload
     45 
     46     def SendCommandVerify(self, write_opcode, list_bytes, read_opcode=None):
     47         """Sends opcode and bytes,
     48         then reads to make sure command was executed."""
     49         if read_opcode is None:
     50             read_opcode = write_opcode.lower()
     51         for _ in xrange(3):
     52             write_sent = self.SendOperation(write_opcode, list_bytes)
     53             self.SendOperation(read_opcode, list_bytes)
     54             try:
     55                 response = self.AwaitResponse()
     56                 if response == write_sent:
     57                     return
     58                 else:
     59                     log.warning('Unexpected reply:  sent %s, got %s',
     60                                 write_sent.encode('hex_codec'),
     61                                 response.encode('hex_codec'))
     62             except socket.timeout:
     63                 log.warning('Timed out awaiting reply for %s', write_opcode)
     64                 continue
     65         raise Error('Failed to execute %s' % write_sent.encode('hex_codec'))
     66 
     67     def AwaitResponse(self):
     68         (response, address) = self.socket.recvfrom(65536)
     69         if (socket.gethostbyname(address[0]) !=
     70                 socket.gethostbyname(self.destination[0])):
     71             log.warning('Unexpected reply source: %s (expected %s)',
     72                         address, self.destination)
     73         return response
     74 
     75 
     76 class RfSwitch(object):
     77     """An RF switch hooked to an Elexol EtherIO24."""
     78 
     79     def __init__(self, ip):
     80         self.io = EtherIo24(ip)
     81         # Must run on pythons without 0bxxx notation.  These are 1110,
     82         # 1101, 1011, 0111
     83         decode = [0xe, 0xd, 0xb, 0x7]
     84 
     85         self.port_mapping = []
     86         for upper in xrange(3):
     87             for lower in xrange(4):
     88                 self.port_mapping.append(decode[upper] << 4 | decode[lower])
     89 
     90     def SelectPort(self, n):
     91         """Connects port n to the RF generator."""
     92         # Set all pins to output
     93 
     94         # !A0:  all pins output
     95         self.io.SendCommandVerify('!A', [0])
     96         self.io.SendCommandVerify('A', [self.port_mapping[n]])
     97 
     98     def Query(self):
     99         """Returns (binary port status, selected port, port direction)."""
    100         self.io.SendOperation('!a', [])
    101         raw_direction = self.io.AwaitResponse()
    102         direction = ord(raw_direction[2])
    103 
    104         self.io.SendOperation('a', [])
    105         status = ord(self.io.AwaitResponse()[1])
    106         try:
    107             port = self.port_mapping.index(status)
    108         except ValueError:
    109             port = None
    110 
    111         return status, port, direction
    112 
    113 
    114 def CommandLineUtility(arguments):
    115     """Command line utility to control a switch."""
    116 
    117     def Select(switch, remaining_args):
    118         switch.SelectPort(int(remaining_args.popleft()))
    119 
    120     def Query(switch, unused_remaining_args):
    121         (raw_status, port, direction) = switch.Query()
    122         if direction != 0x00:
    123             print 'Warning: Direction register is %x, should be 0x00' % \
    124                   direction
    125         if port is None:
    126             port_str = 'Invalid'
    127         else:
    128             port_str = str(port)
    129         print 'Port %s  (0x%x)' % (port_str, raw_status)
    130 
    131     def Usage():
    132         print 'usage:  %s hostname {query|select portnumber}' % sys.argv[0]
    133         exit(1)
    134 
    135     try:
    136         hostname = arguments.popleft()
    137         operation = arguments.popleft()
    138 
    139         switch = RfSwitch(hostname)
    140 
    141         if operation == 'query':
    142             Query(switch, arguments)
    143         elif operation == 'select':
    144             Select(switch, arguments)
    145         else:
    146             Usage()
    147     except IndexError:
    148         Usage()
    149 
    150 if __name__ == '__main__':
    151     CommandLineUtility(collections.deque(sys.argv[1:]))
    152