152 lines
4.8 KiB
Python
Executable File
152 lines
4.8 KiB
Python
Executable File
#!/usr/bin/python2
|
|
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Wrapper for an RF switch built on an Elexol EtherIO24.
|
|
|
|
The EtherIO is documented at
|
|
http://www.elexol.com/IO_Modules/Ether_IO_24_Dip_R.php
|
|
|
|
This file is both a python module and a command line utility to speak
|
|
to the module
|
|
"""
|
|
|
|
import cellular_logging
|
|
import collections
|
|
import socket
|
|
import struct
|
|
import sys
|
|
|
|
log = cellular_logging.SetupCellularLogging('ether_io_rf_switch')
|
|
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
|
|
class EtherIo24(object):
|
|
"""Encapsulates an EtherIO24 UDP-GPIO bridge."""
|
|
|
|
def __init__(self, hostname, port=2424):
|
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
self.socket.bind(('', 0))
|
|
self.destination = (hostname, port)
|
|
self.socket.settimeout(3) # In seconds
|
|
|
|
def SendPayload(self, payload):
|
|
self.socket.sendto(payload, self.destination)
|
|
|
|
def SendOperation(self, opcode, list_bytes):
|
|
"""Sends the specified opcode with [list_bytes] as an argument."""
|
|
payload = opcode + struct.pack(('=%dB' % len(list_bytes)), *list_bytes)
|
|
self.SendPayload(payload)
|
|
return payload
|
|
|
|
def SendCommandVerify(self, write_opcode, list_bytes, read_opcode=None):
|
|
"""Sends opcode and bytes,
|
|
then reads to make sure command was executed."""
|
|
if read_opcode is None:
|
|
read_opcode = write_opcode.lower()
|
|
for _ in xrange(3):
|
|
write_sent = self.SendOperation(write_opcode, list_bytes)
|
|
self.SendOperation(read_opcode, list_bytes)
|
|
try:
|
|
response = self.AwaitResponse()
|
|
if response == write_sent:
|
|
return
|
|
else:
|
|
log.warning('Unexpected reply: sent %s, got %s',
|
|
write_sent.encode('hex_codec'),
|
|
response.encode('hex_codec'))
|
|
except socket.timeout:
|
|
log.warning('Timed out awaiting reply for %s', write_opcode)
|
|
continue
|
|
raise Error('Failed to execute %s' % write_sent.encode('hex_codec'))
|
|
|
|
def AwaitResponse(self):
|
|
(response, address) = self.socket.recvfrom(65536)
|
|
if (socket.gethostbyname(address[0]) !=
|
|
socket.gethostbyname(self.destination[0])):
|
|
log.warning('Unexpected reply source: %s (expected %s)',
|
|
address, self.destination)
|
|
return response
|
|
|
|
|
|
class RfSwitch(object):
|
|
"""An RF switch hooked to an Elexol EtherIO24."""
|
|
|
|
def __init__(self, ip):
|
|
self.io = EtherIo24(ip)
|
|
# Must run on pythons without 0bxxx notation. These are 1110,
|
|
# 1101, 1011, 0111
|
|
decode = [0xe, 0xd, 0xb, 0x7]
|
|
|
|
self.port_mapping = []
|
|
for upper in xrange(3):
|
|
for lower in xrange(4):
|
|
self.port_mapping.append(decode[upper] << 4 | decode[lower])
|
|
|
|
def SelectPort(self, n):
|
|
"""Connects port n to the RF generator."""
|
|
# Set all pins to output
|
|
|
|
# !A0: all pins output
|
|
self.io.SendCommandVerify('!A', [0])
|
|
self.io.SendCommandVerify('A', [self.port_mapping[n]])
|
|
|
|
def Query(self):
|
|
"""Returns (binary port status, selected port, port direction)."""
|
|
self.io.SendOperation('!a', [])
|
|
raw_direction = self.io.AwaitResponse()
|
|
direction = ord(raw_direction[2])
|
|
|
|
self.io.SendOperation('a', [])
|
|
status = ord(self.io.AwaitResponse()[1])
|
|
try:
|
|
port = self.port_mapping.index(status)
|
|
except ValueError:
|
|
port = None
|
|
|
|
return status, port, direction
|
|
|
|
|
|
def CommandLineUtility(arguments):
|
|
"""Command line utility to control a switch."""
|
|
|
|
def Select(switch, remaining_args):
|
|
switch.SelectPort(int(remaining_args.popleft()))
|
|
|
|
def Query(switch, unused_remaining_args):
|
|
(raw_status, port, direction) = switch.Query()
|
|
if direction != 0x00:
|
|
print 'Warning: Direction register is %x, should be 0x00' % \
|
|
direction
|
|
if port is None:
|
|
port_str = 'Invalid'
|
|
else:
|
|
port_str = str(port)
|
|
print 'Port %s (0x%x)' % (port_str, raw_status)
|
|
|
|
def Usage():
|
|
print 'usage: %s hostname {query|select portnumber}' % sys.argv[0]
|
|
exit(1)
|
|
|
|
try:
|
|
hostname = arguments.popleft()
|
|
operation = arguments.popleft()
|
|
|
|
switch = RfSwitch(hostname)
|
|
|
|
if operation == 'query':
|
|
Query(switch, arguments)
|
|
elif operation == 'select':
|
|
Select(switch, arguments)
|
|
else:
|
|
Usage()
|
|
except IndexError:
|
|
Usage()
|
|
|
|
if __name__ == '__main__':
|
|
CommandLineUtility(collections.deque(sys.argv[1:]))
|