Revise circuits module, improve models, use Exceptions, factor out common code
authorTJ <git@iam.tj>
Sat, 16 Nov 2013 18:05:16 +0000 (18:05 +0000)
committerTJ <git@iam.tj>
Sat, 16 Nov 2013 18:05:16 +0000 (18:05 +0000)
circuits.py

index f7bae7c..936982e 100644 (file)
@@ -7,30 +7,31 @@
 # TODO: module in development, not ready for any use yet
 #
 
-import os.path, pygame, engine
+import os.path, pygame, engine, library
 from library import enum
 
+
 class TraceSegment:
  """ Models a straight line segment of a complete Trace """
- # C-style pseudo-enum representing the directions a segment might run
- directions = enum('N', 'NE', 'E', 'SE', 'S' , 'SW', 'W' , 'NW')
- angles     = list( 0 ,  45 ,  90,  135,  180,  225,  270,  315)
+ #          N    NE    E    SE    S     SW    W     NW
+ angles = ( 0 ,  45 ,  90,  135,  180,  225,  270,  315)
 
  # references to an attached Via and its position
  via = None
- via_pos = enum('START', 'MID', 'END')
  via_position = None
 
- def __init__(self, length=1, direction=TraceSegment.directions.E, via=None, position=TraceSegment.via_pos.START):
+ def __init__(self, length=1, direction=library.directions.E, via=None, via_position=library.positions.NONE):
   """ default segment runs for one game tile length left-to-right
-   length:    number of game 'tiles' this segment covers
-   direction:     direction
+   length:        number of game 'tiles' this segment covers
+   direction:     enum directions
+   via:           Via
+   via_position:  enum positions
   """
   # ensure all values are legal before doing any assignment
-  if not direction in TraceSegment.directions.reverse_mapping:
+  if not direction in library.directions.reverse_mapping:
    # 'direction' is an index enum, so ensure it exists in the enum before using it to select the angle
    raise IndexError('direction invalid')
-  elif not position in TraceSegment.via_pos:
+  elif not via_position in library.positions:
    # 'position' is an enum so ensure it is valid
    raise IndexError('position invalid')
   elif via and not isinstance(via, Via):
@@ -39,45 +40,70 @@ class TraceSegment:
   self.length = length
   self.angle = angles[direction]
   self.via = via
-  self.via_position = position
+  self.via_position = via_position
  
- def via_set(self, via, position):
+ def set_via(self, via, position):
   """ link to the related Via
    via:       Via object
    position:  enum via_pos
   """
-  if via and isinstance(via, Via):
-   if position in via_pos:
-    # FIXME: ensure the line above works for testing position's membership of via_pos
-    self.via = via
-    self.via_position = position
+  if not isinstance(via, Via):
+   raise TypeError('expecting a Via')
+  elif not  position in library.positions:
+   # FIXME: ensure the line above works for testing position's membership
+   raise IndexError('position invalid')
+
+  self.via = via
+  self.via_position = position
+
+ def set_direction(self, direction):
+  """ set the direction the segment is headed
+   direction: enum directions
+  """
+  if not direction in library.directions.reverse_mapping:
+   raise IndexError('direction invalid')
+  self.angle = self.angles(direction)
 
 
 class Trace:
  """ Models a single circuit trace made up of multiple segments """
  segments = list() # ordered list of one or more TraceSegments
  colours = dict()
- colours['metal'] = (192, 192, 192) # colours of the trace
- colours['shadow'] = (64, 64, 64)
- colours['reflection'] = (255, 255, 255)
colours['metal'] = (192, 192, 192) # colours of the trace
colours['shadow'] = (64, 64, 64)
colours['reflection'] = (255, 255, 255)
 
  def check_range(self, colour):
   """ Ensure a colour value is legal """
-  return colour >= 0 and colour <= 255
+  if not (type(colour) == tuple or type(colour) == int):
+   raise TypeError('colour expeted to be tuple or int')
+  elif not len(colour) == 3:
+   raise ValueError('colour tuples require exactly 3 items (RGB)')
+
+  if type(colour) == int:
+   return colour >= 0 and colour <= 255
+  else:
+   return self.check_range(colour[0]) and self.check_range(colour[1]) and self.check_range(colour[2])
 
- def __init__(self, metal=None, shadow=None, reflection=None, segments=TraceSegment()):
+ def __init__(self, metal=(192, 192, 192), shadow=(64, 64, 64), reflection=(255, 255, 255), segments=None):
   # set the colours of the trace to give a 3D raised appearance
   if metal and check_range(metal): self.colours['metal'] = metal
   if shadow and check_range(shadow): self.colours['shadow'] = shadow
   if reflection and check_range(reflection): self.colours['reflection'] = reflection
 
-  # initialise the via dictionary
+  if segments == None:
+   self.segments = TraceSegment()
+  else:
+   self.segments = segments
+
   with self.segments[0]:
    via = None
-   via_position = via_pos.START
+   via_position = library.positions.NONE
 
  def draw(self, surface):
   """ Draw the Trace onto pygame.Surface """
+  return
+
 
 class Via:
  """ Models a through-hole otherwise known as a Via """
@@ -96,30 +122,20 @@ class Bus:
 
 class IntegratedCircuit:
  """ semiconductor component """
- signals = None # key = signal name, value = pin number
-
- def __init__(self):
-  """ populate pseudo-enum with common signal names """
-  if not IntegratedCircuit.signals:
-   signals = ('NC', 'GND', 'Vcc', 'Vdd')
-   # logic gate signal names (covers multi-gate components up to quad-gate)
-   for l in ('A', 'B', 'Y'):
-    for n in range(1, 5):
-     signals.append("%s%s" % (n, l))
-
-  IntegratedCircuit.signals = enum(signals)
+ pins = list() # index = pin number, value = signal name
 
 
 class DualInLine(IntegratedCircuit):
  """ Dual In-Line Integrated Circuit """
  sides = 2
  qty = 0
- pins = list()
 
- def __init__(self, pins):
-  if pins > 0 and pins % 2 == 0:
-   for p in range(1, pins+1):
-    pins[p] = IntegratedCircuit.signals.NC
+ def __init__(self, pin_qty):
+  if not (pin_qty > 0 and pin_qty % 2 == 0):
+   raise ValueError('DIL ICs have even number of pins')
+  for p in range(1, pin_qty+1):
+   self.pins.insert(p, library.signals.NC)
 
 
 class Gate:
@@ -127,22 +143,21 @@ class Gate:
  types = None
  inputs = list()
  output = False
- type = None
gate_type = None
 
- def __init__(self, type, inputs=None):
-  # populate dictionary with common logic gate types
-  if not Gate.types:
-   Gate.types = enum('AND', 'OR', 'XOR',  'NOT', 'NAND', 'NOR')
+ def __init__(self, gate_type, inputs=None):
+  if not gate_type in library.gates:
+   raise IndexError()
 
-  if type in Gate.types:
-   self.type = type
+  self.gate_type = gate_type
 
-  if inputs and type(inputs) is list and isinstance(inputs[0], bool):
-   self.inputs = inputs
-  else:
+  if inputs == None:
    # default to 2 inputs at logic 0
    self.inputs = (False, False)
-
+  elif not (type(inputs) is list and isinstance(inputs[0], bool)):
+   raise TypeError()
+  self.inputs = inputs
   self.input_max = len(self.inputs)
   self.update()
 
@@ -151,23 +166,27 @@ class Gate:
   return
 
  def set_input(self, input, value):
-  if input >= 0 and input < self.input_max:
-   if isinstance(value, bool):
-    self.inputs[input] = value
-    self.update()
+  if not(input >= 0 and input < self.input_max):
+   raise ValueError("input expected between 0 and" % self.input_max)
+  if not isinstance(value, bool):
+   raise TypeError('Boolean expected')
+
+  self.inputs[input] = value
+  self.update()
 
  def get_output(self):
   return self.output
 
  def min_inputs(self, inputs, minimum):
-  """ enforce a minimum number of logic inputs """
+  """ enforce a minimum number of logic inputs in the list """
   return inputs if len(inputs) >= minimum else (inputs, (False for i in range(0, minimum - len(inputs))))
    
 
 # Derive each of the common logic gate types
 class GateAND(Gate):
  def __init__(self, inputs=None):
-  Gate.__init__(Gate.types.AND, self.min_inputs(inputs, 2))
+  Gate.__init__(library.gates.AND, self.min_inputs(inputs, 2))
 
  def update(self):
   self.output = self.inputs[0]
@@ -177,7 +196,7 @@ class GateAND(Gate):
 
 class GateOR(Gate):
  def __init__(self, inputs=None):
-  Gate.__init__(Gate.types.OR, self.min_inputs(inputs, 2))
+  Gate.__init__(library.gates.OR, self.min_inputs(inputs, 2))
 
  def update(self):
   self.output = self.inputs[0]
@@ -187,7 +206,7 @@ class GateOR(Gate):
 
 class GateXOR(Gate):
  def __init__(self, inputs=None):
-  Gate.__init__(Gate.types.XOR, self.min_inputs(inputs, 2))
+  Gate.__init__(library.gates.XOR, self.min_inputs(inputs, 2))
 
  def update(self):
   self.output = self.inputs[0]
@@ -197,18 +216,17 @@ class GateXOR(Gate):
 
 class GateNOT(Gate):
  def __init__(self, inputs=(False, )):
-  if len(inputs) > 1:
-   # inverters can only have 1 input
-   inputs = (inputs[0], )
-  Gate.__init__(Gate.types.NOT, inputs)
+  if len(inputs) != 1:
+   raise ValueError('NOT must have only 1 input')
+  Gate.__init__(library.gates.NOT, inputs)
 
  def update(self):
   self.output = ~self.inputs[0] # invert
 
 
 class GateNAND(Gate):
 def __init__(self, inputs=None):
-   Gate.__init__(Gate.types.NAND, self.min_inputs(inputs, 2))
+ def __init__(self, inputs=None):
+  Gate.__init__(library.gates.NAND, self.min_inputs(inputs, 2))
 
  def update(self):
   self.output = self.inputs[0]
@@ -219,7 +237,7 @@ class GateNAND(Gate):
 
 class GateNOR(Gate):
  def __init__(self, inputs=None):
-  Gate.__init__(Gate.types.NOR, self.min_inputs(inputs, 2))
+  Gate.__init__(library.gates.NOR, self.min_inputs(inputs, 2))
 
  def update(self):
   self.output = self.inputs[0]
@@ -231,17 +249,29 @@ class GateNOR(Gate):
 class ICLogic:
  """ Models an IC that contains one or more digital logic gates """
  gates = list() # list of gates
- package = None
 
- def __init__(self, gates=(), package=DualInLine(14)):
-  if package: self.package = package
-  if type(gates) is list and type gates[0] is Gate: self.gates = gates
+ def __init__(self, gates=()):
+  if not type(gates) is list:
+   raise TypeError('expected list')
+  elif not type(gates[0]) is Gate:
+   raise TypeError('expected Gate')
+
+  self.gates = gates
 
  def add(self, gate):
-  if gate and type(gate) is Gate: self.gates.append(gate)
+  if not type(gate) is Gate:
+   raise TypeError('expected Gate')
+
+  self.gates.append(gate)
 
+class ICLogicDIL(ICLogic, DualInLine):
+ """ Model of a Logic IC ina  DIL package  """
+ def __init__(self, gates=(), pins_qty=14):
+  ICLogic.__init(gates)
+  DualInLine.__init__(pins_qty)
 
-class SN7400(ICLogic, DualInLine):
+
+class SN7400(ICLogicDIL):
  """ Model of a Texas Instruments SN7400 quad 2-input NAND gate DIP14 package """
 
  def __init__(self):
@@ -249,12 +279,10 @@ class SN7400(ICLogic, DualInLine):
   gates = list()
   for i in range(0, 4):
    gates.append(GateNAND())
-  # TODO: may need to move this functionality into an intermediate super class that combines package and Logic chip
-  ICLogic.__init__(gates)
-  DualInLine.__init(14)
+  ICLogicDIL.__init__(gates, 14)
 
 
-class SN741G00(ICLogic, ...):
+class SN741G00(ICLogicDIL):
  """ Model of a Texas Instruments SN741G00 single 2-input NAND gate """
 
 
@@ -264,7 +292,7 @@ class TSSOP(IntegratedCircuit):
  pins = list()
 
  def __init__(self, pins):
-  
+  return  
 
 
 class PCB:
@@ -273,10 +301,13 @@ class PCB:
 
  def __init__(self):
   """ """
+  return
 
  def trace_add(self, trace_list):
-  """ Add a """
+  """ Add a  Trace """
+  return
  
  def component_insert(self, component):
-  """ Adds a component to  """
+  """ Adds a component to the circuit """
+  return