Technical Deep Dive - griffingilreath/Punch-Card-Project GitHub Wiki
Punch Card Project: Technical Deep Dive
Introduction
This document provides a comprehensive technical overview of the Punch Card Project, focusing on the core logic, module interactions, hardware integration, and GUI control. Explore how the system manages data flow, decision-making processes, and the rationale behind key architectural decisions.
Project Overview and Architecture
The Punch Card Project simulates an IBM 80-column punch card system, integrating modern hardware (LED matrices) and a graphical user interface (GUI). The architecture is modular, designed for clarity, maintainability, and educational value.
High-Level Architecture Diagram
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Punch Card Project β
β β
β βββββββββββββ βββββββββββββββββ βββββββββββββββββββββ β
β β GUI Layer βββββ Display Logic βββββ Hardware Control β β
β βββββββββββββ βββββββββββββββββ βββββββββββββββββββββ β
β β β β β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββ βββββββββββββββββ βββββββββββββββββββββ β
β β User Data βββββ Core Logic βββββ LED State Manager β β
β βββββββββββββ βββββββββββββββββ βββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- GUI Layer: User interaction and visualization.
- Display Logic: Translates internal data into visual representations.
- Hardware Control: Interfaces with physical or simulated hardware.
- Core Logic: Central decision-making and data processing.
- LED State Manager: Manages LED states and animations.
Core Logic Modules
The core logic is encapsulated primarily within the src/core/
directory:
punch_card.py
1. Purpose:
Handles the encoding and decoding of messages into punch card patterns.
Key Logic:
- Converts text input into IBM 80 column punch card encoding.
- Manages internal representation of punch card data.
Decision Rationale:
Using IBM 80 column encoding provides historical accuracy and educational value.
Example Logic (simplified):
def encode_message(message):
encoded = []
for char in message.upper():
pattern = IBM_80_COLUMN_ENCODING.get(char, [])
encoded.append(pattern)
return encoded
message_generator.py
2. Purpose:
Generates messages for display, including integration with external APIs (e.g., OpenAI).
Key Logic:
- Fetches or generates messages based on user input or external sources.
- Ensures messages are formatted correctly for punch card encoding.
Decision Rationale:
Integration with OpenAI provides dynamic, educational content generation.
database.py
3. Purpose:
Manages persistent storage of messages, configurations, and historical data.
Key Logic:
- CRUD operations for message history and user settings.
# CRUD = Create, Read, Update, Delete # These are the four basic operations for persistent storage systems def create_message(message_text, encoding_type): # Store a new message in the database with metadata message_id = generate_unique_id() timestamp = get_current_timestamp() store_in_database(message_id, message_text, encoding_type, timestamp) return message_id def read_message(message_id): # Retrieve a stored message by its ID return query_database_by_id(message_id) def update_message(message_id, new_text=None, new_encoding=None): # Modify an existing message existing_message = read_message(message_id) if new_text: existing_message.text = new_text if new_encoding: existing_message.encoding = new_encoding existing_message.last_modified = get_current_timestamp() save_to_database(existing_message) def delete_message(message_id): # Remove a message from the database remove_from_database(message_id)
- Secure storage and retrieval of sensitive data (e.g., API keys).
Decision Rationale:
Centralized data management simplifies data handling and enhances security.
Display and GUI Control
The GUI and display logic are managed within the src/display/
directory:
gui_display.py
1. Purpose:
Provides an interactive graphical interface for users.
Key Logic:
-
Renders punch card visualizations and LED states.
# GUI rendering logic for punch card visualization class PunchCardGrid(QWidget): def __init__(self, rows=12, cols=80, parent=None): super().__init__(parent) # Define grid dimensions based on IBM 80 column standard self.rows = rows # 12 rows in standard IBM punch card self.cols = cols # 80 columns in standard IBM punch card self.led_states = [[False for _ in range(cols)] for _ in range(rows)] self.led_size = 12 # Size of each LED visualization self.padding = 2 # Spacing between LEDs self.setMinimumSize( (self.led_size + self.padding) * cols, (self.led_size + self.padding) * rows ) def paintEvent(self, event): """ This critical method handles the actual drawing of LEDs. It's called automatically by Qt when the widget needs to be redrawn. """ painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # Draw each LED in the grid for row in range(self.rows): for col in range(self.cols): x = col * (self.led_size + self.padding) y = row * (self.led_size + self.padding) # Select color based on LED state if self.led_states[row][col]: # LED is on - use highlight color painter.setBrush(QBrush(QColor(255, 100, 100))) # Light red for "on" state else: # LED is off - use background color painter.setBrush(QBrush(QColor(50, 50, 50))) # Dark gray for "off" state # Draw the LED as a rounded rectangle painter.drawRoundedRect(x, y, self.led_size, self.led_size, 2, 2) def updateLED(self, row, col, state): """ Updates a single LED state and triggers a repaint of that area. This is more efficient than repainting the entire grid. """ if 0 <= row < self.rows and 0 <= col < self.cols: self.led_states[row][col] = state # Calculate the area to update x = col * (self.led_size + self.padding) y = row * (self.led_size + self.padding) # Request repaint of just this LED self.update(QRect(x, y, self.led_size, self.led_size)) def clear(self): """Reset all LEDs to off state""" self.led_states = [[False for _ in range(self.cols)] for _ in range(self.rows)] self.update() # Repaint entire widget
-
Handles user interactions (button clicks, menu selections).
# User interaction handling with signals/slots architecture class PunchCardApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("IBM 80 Column Punch Card Simulator") # Create central widget and layout central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # Create punch card grid visualization self.punch_card_grid = PunchCardGrid() layout.addWidget(self.punch_card_grid) # Create controls controls_layout = QHBoxLayout() # Add message input self.message_input = QLineEdit() self.message_input.setPlaceholderText("Enter message to display...") controls_layout.addWidget(self.message_input) # Add display button self.display_button = QPushButton("Display Message") self.display_button.clicked.connect(self.on_display_message) controls_layout.addWidget(self.display_button) # Add clear button self.clear_button = QPushButton("Clear Display") self.clear_button.clicked.connect(self.on_clear_display) controls_layout.addWidget(self.clear_button) layout.addLayout(controls_layout) # Connect to data model for updates self.data_model = PunchCardDataModel.get_instance() self.data_model.register_observer(self) def on_display_message(self): """ This method is called when the user clicks the Display Message button. It retrieves the message from the input field and passes it to the message processor. """ message = self.message_input.text().strip() if message: # Process the message via core logic self.display_processor.process_message(message) def on_clear_display(self): """Clear the visualization""" self.punch_card_grid.clear() def update_display(self, row, col, state): """ This method is called by the observer pattern when the data model changes. It ensures the GUI stays synchronized with the underlying data. """ self.punch_card_grid.updateLED(row, col, state)
Decision Rationale:
A GUI enhances usability, making the project accessible to a broader audience.
terminal_display.py
2. Purpose:
Offers a terminal-based visualization alternative.
Key Logic:
-
Uses curses for interactive terminal UI.
# Terminal-based visualization using the curses library class CursesTerminalDisplay: def __init__(self): self.stdscr = None self.led_grid = None self.debug_window = None self.rows = 12 # Standard IBM 80 column punch card has 12 rows self.cols = 80 # 80 columns self.char_set = { 'on': 'β', # Filled block for active LED 'off': 'Β·' # Small dot for inactive LED } def initialize(self): """ Initialize the curses interface with split windows for LED display and debug messages. """ # Initialize curses self.stdscr = curses.initscr() curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) # For active LEDs curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK) # For inactive LEDs curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) # For debug messages curses.noecho() curses.cbreak() self.stdscr.keypad(True) # Get terminal dimensions max_y, max_x = self.stdscr.getmaxyx() # Create LED display window (upper half) led_height = min(self.rows + 2, max_y // 2) self.led_grid = curses.newwin(led_height, max_x, 0, 0) self.led_grid.box() self.led_grid.addstr(0, 2, " LED Display ", curses.A_BOLD) # Create debug window (lower half) debug_height = max_y - led_height self.debug_window = curses.newwin(debug_height, max_x, led_height, 0) self.debug_window.box() self.debug_window.addstr(0, 2, " Debug Messages ", curses.A_BOLD) # Initial refresh self.led_grid.refresh() self.debug_window.refresh() self.stdscr.refresh() def display_led_state(self, led_states): """ Display the current state of all LEDs in the grid window. led_states is a 2D array representing the state of each LED. """ if not self.led_grid: return # Display each LED for row in range(min(self.rows, self.led_grid.getmaxyx()[0] - 3)): for col in range(min(self.cols, self.led_grid.getmaxyx()[1] - 3)): # Determine character and color based on LED state if led_states[row][col]: char = self.char_set['on'] attr = curses.color_pair(1) # Active LED color else: char = self.char_set['off'] attr = curses.color_pair(2) # Inactive LED color # Display the LED at the correct position # Add 1 to row/col for the window border self.led_grid.addstr(row + 1, col + 1, char, attr) self.led_grid.refresh() def log_message(self, message): """ Add a debug message to the debug window. Messages are scrolled if necessary. """ if not self.debug_window: return max_y, max_x = self.debug_window.getmaxyx() # Move existing content up one line if at bottom if self.next_log_line >= max_y - 2: # Scroll contents self.debug_window.move(1, 0) self.debug_window.deleteln() self.next_log_line = max_y - 3 # Add new message self.debug_window.addstr(self.next_log_line, 1, message, curses.color_pair(3)) self.next_log_line += 1 self.debug_window.refresh() def cleanup(self): """ Safely close curses interface when program ends. This is essential to restore terminal to proper state. """ if self.stdscr: curses.nocbreak() self.stdscr.keypad(False) curses.echo() curses.endwin()
-
Provides fallback ASCII visualization when curses is unavailable.
# Fallback console mode that works in any terminal class FallbackConsoleDisplay: def __init__(self): self.rows = 12 self.cols = 80 self.char_set = { 'on': '#', # Hash for active LED 'off': '.' # Period for inactive LED } def display_led_state(self, led_states): """ Simple ASCII representation of LED states with row/column markers. This works in any terminal without requiring curses. """ # Print column headers (in chunks of 5 for readability) print(" ", end="") for i in range(0, min(self.cols, 80), 5): print(f"{i:<5}", end="") print("\n +", end="") print("-" * (min(self.cols, 80) + 2), end="") print("+") # Print each row with row number for row in range(self.rows): print(f"{row:2d} | ", end="") for col in range(min(self.cols, 80)): if led_states[row][col]: print(self.char_set['on'], end=" ") else: print(self.char_set['off'], end=" ") print("|") # Print bottom border print(" +", end="") print("-" * (min(self.cols, 80) + 2), end="") print("+") def log_message(self, message): """ Simple logging for debug messages in fallback mode. """ print(f"[DEBUG] {message}")
Decision Rationale:
Ensures compatibility across diverse environments, including headless systems.
display_adapter.py
3. Purpose:
Acts as an intermediary between core logic and display modules.
Key Logic:
-
Translates internal punch card data into visual formats.
# Display adapter connects the core punch card logic to display implementations class DisplayAdapter: def __init__(self, display_type="auto"): self.display = None self.display_type = display_type self.initialize_display() def initialize_display(self): """ Select and initialize the appropriate display based on: 1. User preference 2. System capabilities 3. Terminal size """ if self.display_type == "gui" or (self.display_type == "auto" and self._can_use_gui()): # Use the GUI display if requested or if auto-detection permits try: from gui_display import PunchCardApp self.display = PunchCardApp() return except ImportError: print("GUI libraries not available, falling back to terminal mode") # If GUI is not used, try curses for terminal UI if self.display_type == "terminal" or self.display_type == "auto": try: from terminal_display import CursesTerminalDisplay terminal_size = self._get_terminal_size() # Check if terminal is large enough for curses interface if terminal_size and terminal_size[0] >= 12 and terminal_size[1] >= 40: self.display = CursesTerminalDisplay() self.display.initialize() return except (ImportError, Exception) as e: # Curses might not be available or initialization could fail print(f"Curses terminal display not available: {e}") # Final fallback: simple console output that works everywhere from terminal_display import FallbackConsoleDisplay self.display = FallbackConsoleDisplay() print("Using fallback console display mode") def _can_use_gui(self): """Check if GUI libraries are available and display is possible""" try: # Try to import PyQt to check availability import PyQt6 # Check if we have a display (not running in headless environment) import os return os.environ.get('DISPLAY') is not None or os.environ.get('WAYLAND_DISPLAY') is not None except ImportError: return False def _get_terminal_size(self): """Get terminal dimensions in rows, columns""" try: import shutil return shutil.get_terminal_size() except (ImportError, AttributeError): try: # Fallback method using stty command import subprocess size = subprocess.check_output(['stty', 'size']).decode().split() return (int(size[0]), int(size[1])) except (subprocess.SubprocessError, ValueError, IndexError): return None
-
Manages synchronization between GUI and hardware states.
class DisplaySynchronizer: """ Ensures that all display outputs (GUI, terminal, hardware) stay synchronized with the internal data model. """ def __init__(self, data_model, displays=None): self.data_model = data_model self.displays = displays or [] self.hardware_controller = None # Register as observer of data model to receive updates self.data_model.register_observer(self) def add_display(self, display): """Add a display to be synchronized""" if display not in self.displays: self.displays.append(display) def set_hardware_controller(self, controller): """Connect a hardware controller for physical LED updates""" self.hardware_controller = controller def update(self, row, col, state): """ Called when data model changes - propagate changes to all displays and hardware controller. """ # Update software displays for display in self.displays: if hasattr(display, 'updateLED'): display.updateLED(row, col, state) elif hasattr(display, 'display_led_state'): # Terminal displays need the entire grid display.display_led_state(self.data_model.get_grid()) # Update hardware if available if self.hardware_controller: self.hardware_controller.set_led(row, col, state) def display_message(self, message, duration=5.0): """ Handle high-level message display with timing controls. This encapsulates the entire display sequence. """ # Clear any previous message self.clear_display() # Encode message to punch card format from punch_card import encode_message encoded_message = encode_message(message) # Update data model with encoded message # This will trigger the observer pattern and update all displays for col_idx, char_pattern in enumerate(encoded_message): if col_idx >= self.data_model.cols: break # Respect column limit for row_idx in char_pattern: self.data_model.set_punch(row_idx, col_idx, True) # If requested, schedule clearing after duration if duration > 0: import threading threading.Timer(duration, self.clear_display).start() def clear_display(self): """Clear all displays and hardware""" self.data_model.clear() # Hardware may need direct clearing too if self.hardware_controller: self.hardware_controller.clear_all()
Decision Rationale:
Separating display logic from core logic improves modularity and maintainability.
Hardware Integration and LED Control
Hardware integration is managed through dedicated modules:
hardware_controller.py
1. Purpose:
Abstracts hardware interactions, supporting both simulated and physical hardware.
Key Logic:
- Provides a unified interface for hardware operations.
- Supports Raspberry Pi GPIO integration for physical LED matrices.
Decision Rationale:
Abstracting hardware interactions allows easy switching between simulated and real hardware, facilitating development and testing.
led_state_manager.py
2. Purpose:
Manages the state of LEDs, including animations and patterns.
Key Logic:
- Maintains an internal representation of LED states.
- Provides methods to update, animate, and reset LED states.
Decision Rationale:
Centralized LED state management simplifies synchronization between software logic and hardware display.
Example Logic (simplified):
def set_led_state(x, y, state):
led_grid[x][y] = state
hardware_controller.update_led(x, y, state)
Data Flow and Module Interaction
Data Flow Diagram
User Input (GUI/Terminal)
β
βΌ
βββββββββββββββββββββ
β Message Generator β
βββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββ
β Punch Card β
β (Core Logic) β
βββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββ
β Display Adapter β
βββββββββββββββββββββ
β
ββββββββ΄ββββββββ
βΌ βΌ
βββββββββββ βββββββββββββ
β GUI β β Hardware β
β Display β β Controllerβ
βββββββββββ βββββββββββββ
β
βΌ
βββββββββββββββββ
β LED State β
β Manager β
βββββββββββββββββ
Explanation:
- User input triggers message generation.
- Messages are encoded into punch card patterns.
- Display adapter translates patterns for GUI and hardware.
- GUI displays visual representation; hardware controller updates physical LEDs.
- LED state manager maintains synchronization between software and hardware states.
Message Translation and LED Control
Understanding how messages are processed and displayed is central to the Punch Card Project. This section details the character-by-character processing flow and how the system translates text into visual representations across both GUI and hardware interfaces.
Character-to-LED Transformation Process
The translation of a message into LED states follows a precise sequence:
βββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
β β β β β β
β Input Message ββββΊβ Encode Each ββββΊβ Generate Punch β
β "HELLO WORLD" β β Character β β Card Pattern β
β β β β β β
βββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
β
βΌ
βββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
β β β β β β
β Update Display βββββ Map to Grid βββββ Translate to β
β (GUI & LEDs) β β Coordinates β β LED Matrix β
β β β β β β
βββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
1. Character Encoding (IBM 80 Column Punch Card Format)
Each character in the input message is individually processed through the IBM 80 column encoding system:
def process_message(message_text):
encoded_message = []
for char in message_text:
# Convert each character to its punch card representation
punch_pattern = encode_character(char)
encoded_message.append(punch_pattern)
return encoded_message
For example, the letter 'H' in IBM 80 column encoding is represented by punches in rows 12 and 8:
Column for 'H':
Row 12: β (punched)
Row 11: β‘ (not punched)
Row 0: β‘ (not punched)
Row 1: β‘ (not punched)
Row 2: β‘ (not punched)
Row 3: β‘ (not punched)
Row 4: β‘ (not punched)
Row 5: β‘ (not punched)
Row 6: β‘ (not punched)
Row 7: β‘ (not punched)
Row 8: β (punched)
Row 9: β‘ (not punched)
2. Matrix Mapping
After encoding, each character's punch pattern is mapped to LED grid coordinates:
def map_to_grid(encoded_characters):
led_grid = initialize_empty_grid()
for col_idx, character in enumerate(encoded_characters):
if col_idx >= MAX_COLS:
break # Respect the 80-column limit
for punch in character:
row = punch # The row number where the punch occurs
led_grid[row][col_idx] = True # Set LED to ON
return led_grid
3. Shared Data Model
A critical design decision was to implement a shared data model for both the GUI and physical LED hardware. This approach ensures consistency between visual representations:
class PunchCardDataModel:
def __init__(self):
self.grid = [[False for _ in range(80)] for _ in range(12)]
self.observers = [] # Both GUI and hardware controllers register here
def set_punch(self, row, col, state):
self.grid[row][col] = state
self.notify_observers(row, col, state)
def notify_observers(self, row, col, state):
for observer in self.observers:
observer.update_display(row, col, state)
Parallel Display Systems: GUI vs. Hardware LEDs
The project maintains two parallel display systems that share core data but differ in implementation:
GUI Display System
ββββββββββββββββββββββββββββββββββββββββββ
β GUI Display β
β β
β ββββββββββββββββ βββββββββββββββββ β
β β PyQt Widgets β β Punch Card β β
β β (Buttons, β β Visualization β β
β β Menus) β β (Grid of LEDs)β β
β ββββββββββββββββ βββββββββββββββββ β
β β β² β
β β β β
β βΌ β β
β ββββββββββββββββββββββββββββββββββββ β
β β Shared Data Model Observer β β
β ββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββ
The GUI system:
- Uses PyQt widgets to create a visual representation
- Renders LED states as colored circles/squares in a grid
- Updates in real-time when the shared data model changes
- Provides visual feedback and interactive controls
Hardware LED System
ββββββββββββββββββββββββββββββββββββββββββ
β Hardware Display β
β β
β ββββββββββββββββ βββββββββββββββββ β
β β Hardware β β LED Matrix β β
β β Abstraction β β (Physical or β β
β β Layer β β Simulated) β β
β ββββββββββββββββ βββββββββββββββββ β
β β β² β
β β β β
β βΌ β β
β ββββββββββββββββββββββββββββββββββββ β
β β Shared Data Model Observer β β
β ββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββ
The hardware system:
- Interfaces with physical or simulated LED hardware
- Translates logical LED states into hardware signals
- Uses hardware-specific controllers (GPIO for Raspberry Pi)
- Implements timing controls for hardware limitations
Synchronization Between Systems
The two display systems remain synchronized through the Observer pattern:
# When a message is processed in the core logic:
def display_message(message):
encoded = encode_message(message)
for col_idx, char_pattern in enumerate(encoded):
for row_idx in char_pattern:
# This updates both GUI and hardware simultaneously
punch_card_data_model.set_punch(row_idx, col_idx, True)
# Schedule clearing after display time
schedule_clear(display_duration)
This approach provides several advantages:
- Consistency: Both displays always show identical information
- Modularity: Either display can be used independently
- Testability: The system can be tested with simulated hardware
- Flexibility: New display types can be added by implementing the observer interface
Implementation Differences
While sharing the same data model, the implementations differ in key ways:
GUI Implementation:
- Uses PyQt's signal/slot mechanism for updates
- Operates in the application's main thread
- Updates occur immediately
- Rendering is handled by the GUI toolkit
Hardware Implementation:
- May use separate threads for hardware communication
- Implements timing controls to account for hardware limitations
- Includes hardware-specific error handling
- Provides fallback mechanisms when hardware is unavailable
Decision Rationale
The shared data model with separate implementations was chosen for several reasons:
- Separation of Concerns: Display logic is separated from hardware control
- Maintainability: GUI can be updated without affecting hardware control
- Flexibility: Supports both simulated and physical hardware
- Educational Value: Clearly demonstrates how different interfaces can present the same data
Letter-by-Letter Processing Flow
To better understand how the system processes text, let's examine the journey of a single message from input to display across both digital and physical interfaces.
Message Processing Timeline
The system processes messages in distinct phases that happen in sequence:
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Input Phase ββ β Encode Phaseββ β Buffer Phaseββ βDisplay Phaseββ β Clear Phase β
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
~1ms ~5ms ~2ms 500-2000ms ~10ms
- Input Phase: The message is received from user input or API
- Encode Phase: Each character is converted to its punch card pattern
- Buffer Phase: The encoded message is prepared for visualization
- Display Phase: LEDs are activated according to the pattern (longest phase)
- Clear Phase: All LEDs are deactivated after the display period
Single Letter Journey
Let's follow the letter 'A' from input through the entire system:
βββββββββββββββββββββββββββββββββββββ
β INPUT: LETTER 'A' β
βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββ
β ENCODING LOOKUP β
β β
β 'A' β IBM 80 Column Format β
β β Punches in rows 12,1 β
βββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β GUI PROCESSING BRANCH β β SHARED DATA MODEL β β HARDWARE PROCESSING BRANCH β
β β β β β β
β β’ Convert to QRect β β β’ Update internal grid β β β’ Convert to physical pins β
β β’ Calculate pixel positions βββββββ€ β’ Notify all observers βββββΊβ β’ Apply hardware timing β
β β’ Update Qt widget β β β’ Maintain grid state β β β’ Send signals to LED matrix β
β β’ Trigger repaint β β β β β’ Handle hardware errors β
ββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β DISPLAY RENDERING β β HARDWARE CONTROL β
β β β β
β β’ Draw rectangles for LEDs β β β’ GPIO pin activation β
β β’ Highlight active LEDs β β β’ Power management β
β β’ Show in GUI window β β β’ Physical LED illumination β
ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
Detailed Character-to-Interface Translation
This diagram shows the exact transformation process of a character into both GUI elements and hardware signals:
Character 'A'
β
βΌ
βββββββββββββββββββ
β Row 12: β (on) β
β Row 11: β‘ (off) β
β Row 0: β‘ (off) β
β Row 1: β (on) β
β Row 2: β‘ (off) β
β Row 3: β‘ (off) β
β Row 4: β‘ (off) β
β Row 5: β‘ (off) β
β Row 6: β‘ (off) β
β Row 7: β‘ (off) β
β Row 8: β‘ (off) β
β Row 9: β‘ (off) β
βββββββββββββββββββ
β
ββββββββββββββββββ¬βββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β GUI Display β βTerminal Viewβ βHardware Pinsβ
βββββββββββββββ€ βββββββββββββββ€ βββββββββββββββ€
β ββ β β ββ β β HIGH β Row 12
β β β β β LOW β Row 11
β β β β β LOW β Row 0
β ββ β β ββ β β HIGH β Row 1
β β β β β LOW β Row 2
β β β β β LOW β Row 3
β β β β β LOW β Row 4
β β β β β LOW β Row 5
β β β β β LOW β Row 6
β β β β β LOW β Row 7
β β β β β LOW β Row 8
β β β β β LOW β Row 9
βββββββββββββββ βββββββββββββββ βββββββββββββββ
Multi-Character Processing Flow
When processing a complete message like "HELLO", each character is processed sequentially. The following diagram shows how characters are placed in the grid:
GRID COLUMNS (0-79)
0 1 2 3 4 ...
ββββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββ
12 β β β β β β β Row 12
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
11 β β β β β β Row 11
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
0 β β β β β β β Row 0
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
1 β β β β β β β Row 1
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
2 β β β β β β Row 2
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
3 β β β β β β Row 3
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
4 β β β β β β β Row 4
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
5 β β β β β β β β β Row 5
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
6 β β β β β β Row 6
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
7 β β β β β β Row 7
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
8 β β β β β β β Row 8
ββββββββββΌβββββββββΌβββββββββΌβββββββββΌβββββββββ€
9 β β β β β β Row 9
ββββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββ
H E L L O
Each character occupies one column in the grid. The project uses the Observer pattern to ensure that whenever a cell in this grid is updated, all displays (GUI, terminal, and hardware) are notified of the change.
Data Flow Sequence Diagram
This sequence diagram illustrates the flow of data when a user enters a message:
βββββββ βββββββββββββ ββββββββββββ ββββββββββββ ββββββββββ ββββββββββ
βUser β βGUI/Consoleβ βCore Logicβ βData Modelβ βGUI Viewβ βHardwareβ
ββββ¬βββ βββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββ ββββββ¬ββββ
β β β β β β
β Input "HELLO"β β β β β
ββββββββββββββ>β β β β β
β β β β β β
β β process_messageβ β β β
β ββββββββββββββββ>β β β β
β β β β β β
β β β encode_message()β β β
β β βββββββββββββββββ>β β β
β β β β β β
β β β β set_punch(12,0,true)β β
β β β βββββββββββββββββββββ>β β
β β β β β β
β β β β set_punch(1,0,true)β β
β β β βββββββββββββββββββββ>β β
β β β β β β
β β β β notify_observers()β β
β β β βββββββββββββββββββββ>β β
β β β β β β
β β β β β update_led()β
β β β β βββββββββββββ>β
β β β β β β
β β β β β<βββββββββββββ
β β β β β β
β β β Return β β β
β β<ββββββββββββββββ β β β
β β β β β β
β Show Result β β β β β
β<ββββββββββββββ β β β β
ββββ΄βββ βββββββ΄ββββββ ββββββ΄ββββββ ββββββ΄ββββββ ββββββ΄ββββ ββββββ΄ββββ
βUser β βGUI/Consoleβ βCore Logicβ βData Modelβ βGUI Viewβ βHardwareβ
βββββββ βββββββββββββ ββββββββββββ ββββββββββββ ββββββββββ ββββββββββ
Simple Explanation: Letter-by-Letter Processing
In simple terms, here's what happens when you enter a message:
-
Split Into Characters: Your message "HELLO" is broken into individual characters: 'H', 'E', 'L', 'L', 'O'.
-
Look Up Punch Patterns: Each character is converted to its punch card pattern:
- 'H' has punches in rows 12 and 8
- 'E' has punches in rows 12 and 5
- 'L' has punches in rows 11 and 3
- 'L' has punches in rows 11 and 3
- 'O' has punches in rows 11 and 6
-
Update Central Grid: These patterns are placed into a central grid (like a spreadsheet), with each character taking one column.
-
Notify All Displays: The central grid tells all displays (GUI, terminal, and hardware) what changed.
-
Display-Specific Translation:
- GUI: Converts grid positions to colored rectangles on screen
- Terminal: Converts grid positions to ASCII characters
- Hardware: Converts grid positions to electrical signals
-
Synchronized Updates: All displays update at the same time, showing the same pattern.
-
Timing Control: After a set time, all displays clear simultaneously.
This approach ensures that whether you're looking at the screen or the physical LED matrix, you see exactly the same pattern. It's like having multiple TV screens showing the same channel - the content is the same, just displayed on different devices.
Shared Data Model Observer Pattern
The Punch Card Project utilizes the Observer design pattern as a fundamental architectural component to ensure synchronization between the internal data representation and multiple display interfaces. This pattern is critical for maintaining consistency across GUI, terminal, and hardware displays.
Observer Pattern Implementation
In the Punch Card Project, the observer pattern follows this structure:
ββββββββββββββββββββββββββ
β PunchCardDataModel β
β (Subject/Observable) β
β β
β - grid: 2D array β
β - observers: list β
β β
β + register_observer() β
β + remove_observer() β
β + notify_observers() β
β + set_punch() β
β + clear() β
βββββββββββββ¬βββββββββββββ
β
β notifies
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Observers β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββ΄ββββββ¬ββββββββββββββ¬ββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββ
β GUI β β Terminal β β Hardware β β Other β
β Display β β Display β β Control β β Observers β
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββ
Core Implementation Details
The implementation includes:
class PunchCardDataModel:
"""Central data model that manages the state of each LED in the grid."""
# Singleton instance
_instance = None
@classmethod
def get_instance(cls):
"""Get the singleton instance of the data model."""
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
"""Initialize the data model with an empty grid and no observers."""
# Prevent multiple instantiation
if PunchCardDataModel._instance is not None:
raise RuntimeError("Use get_instance() to get the singleton instance")
# 12 rows (IBM punch card standard) Γ 80 columns
self.rows = 12
self.cols = 80
# Initialize grid with all LEDs off (False)
self.grid = [[False for _ in range(self.cols)] for _ in range(self.rows)]
# List to store all observers that need to be notified of changes
self.observers = []
PunchCardDataModel._instance = self
def register_observer(self, observer):
"""
Register an observer to be notified of changes to the data model.
Args:
observer: An object implementing the update_display(row, col, state) method
"""
if observer not in self.observers:
self.observers.append(observer)
def remove_observer(self, observer):
"""Remove an observer from the notification list."""
if observer in self.observers:
self.observers.remove(observer)
def notify_observers(self, row, col, state):
"""
Notify all registered observers about a state change.
Args:
row: The row index of the changed LED
col: The column index of the changed LED
state: Boolean indicating whether the LED is on (True) or off (False)
"""
for observer in self.observers:
# Call the observer's update method
observer.update_display(row, col, state)
def set_punch(self, row, col, state):
"""
Set the state of a specific grid position and notify observers.
Args:
row: Row index (0-11)
col: Column index (0-79)
state: Boolean punch state (True = hole punched/LED on)
"""
# Validate indices
if 0 <= row < self.rows and 0 <= col < self.cols:
# Only update and notify if there's an actual change
if self.grid[row][col] != state:
self.grid[row][col] = state
self.notify_observers(row, col, state)
def get_grid(self):
"""Return a copy of the current grid state."""
return [row[:] for row in self.grid] # Return a deep copy
def clear(self):
"""Reset all positions to False (no punches/LEDs off)."""
for row in range(self.rows):
for col in range(self.cols):
self.set_punch(row, col, False)
Observer Interface
Each observer implements an update method (named consistently) that processes changes:
# In GUI observer
def update_display(self, row, col, state):
"""Update the LED state in the GUI."""
self.punch_card_grid.updateLED(row, col, state)
# In Terminal observer
def update_display(self, row, col, state):
"""Update the LED state in the terminal display."""
self.grid[row][col] = state
self._redraw_display()
# In Hardware controller
def update_display(self, row, col, state):
"""Update physical LED state."""
self.set_led(row, col, state)
# Hardware-specific timing control
if self.enforce_timing_constraints:
time.sleep(self.led_update_delay)
Detailed Data Flow Diagram
The following diagram illustrates the complete flow of data through the observer pattern:
βββββββββββββββββββββββ
β User Interface β
β (GUI or Terminal) β
ββββββββββββ¬βββββββββββ
β
β User enters message
βΌ
βββββββββββββββββββββββ
β Message Encoder β
β Converts to punch β
β card patterns β
ββββββββββββ¬βββββββββββ
β
β Encoded message
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β PunchCardDataModel β
β β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββββββββ β
β β set_punch β β notify_ β β State Grid β β
β β (row,col, βββΊβ observers β β βββββ¬ββββ¬ββββ¬ββββ β β
β β state) β β (row,col, β β β F β T β F β F β β β
β βββββββββββββββ β state) β β βββββΌββββΌββββΌββββ€ β β
β ββββββββ¬ββββββββ β β F β F β T β F β β β
β β β βββββΌββββΌββββΌββββ€ β β
β β β β T β F β F β T β β β
β β β βββββ΄ββββ΄ββββ΄ββββ β β
β β βββββββββββββββββββββ β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
β Broadcasts state changes
β
βββββββββ΄ββββββββ¬βββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββ
β GUI Observer β β Terminal β β Hardware β
β β β Observer β β Observer β
β βββββββββββββββββββ β β ββββββββββββββββ β β ββββββββββββββββββββ
β β update_display β β β βupdate_displayβ β β β update_display ββ
β β (row,col,state) β β β β(row,col,stateβ β β β (row,col,state) ββ
β ββββββββββ¬βββββββββ β β ββββββββ¬ββββββββ β β ββββββββ¬ββββββββββββ
β β β β β β β β β
β βΌ β β βΌ β β βΌ β
β βββββββββββββββββββ β β βββββββββββββββ β β ββββββββββββββββββββ
β β Update UI β β β βUpdate β β β β Send signal to ββ
β β - Change rect β β β βterminal β β β β hardware: ββ
β β color β β β β- ASCII charsβ β β β - GPIO pin ββ
β β - Repaint regionβ β β β- ASCII charsβ β β β - LED matrix ββ
β βββββββββββββββββββ β β βββββββββββββββ β β ββββββββββββββββββββ
βββββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β User sees β β User sees β β Physical LEDs β
β GUI display β β terminal β β light up β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
Advantages of the Observer Pattern
The observer pattern provides several critical benefits to the Punch Card Project:
-
Decoupling: The data model is completely decoupled from the display implementations, making it easier to add new display types without changing the core logic.
-
Consistency: All displays are guaranteed to show the same data because they all respond to the same change notifications.
-
Synchronization: Updates to multiple displays occur in a coordinated manner, with each observer being notified of the same state change.
-
Scalability: New observers (e.g., a web interface or network display) can be added by simply implementing the observer interface and registering with the data model.
-
Selective Updates: Only changed cells trigger updates, reducing processing overhead compared to refreshing entire displays.
Real-Time Example
When a user enters "HELLO" as input:
-
Initialization:
- The data model is created with all grid positions set to
False
. - GUI, terminal, and hardware displays register as observers.
- The data model is created with all grid positions set to
-
Encoding Process:
- Each character is encoded into its punch card pattern (specific rows punched).
- For 'H', rows 12 and 8 need to be punched in column 0.
-
Data Model Update:
- Core logic calls
data_model.set_punch(12, 0, True)
. - The data model updates its internal grid state.
- The data model calls
notify_observers(12, 0, True)
.
- Core logic calls
-
Observer Notifications:
- Each registered observer's
update_display(12, 0, True)
method is called. - The GUI observer updates the visual rectangle at position (12,0).
- The terminal observer updates its ASCII representation at position (12,0).
- The hardware observer sends a signal to turn on the physical LED at position (12,0).
- Each registered observer's
-
Consistent Display:
- All three displays now show an active state at the same position (12,0).
- The process repeats for each punched position in the message.
This synchronized update process ensures that all representations of the punch card - visual, terminal, and physical - remain perfectly aligned throughout the entire message display process.
Punch Card Encoding and Decoding
This section provides a detailed explanation of how the Punch Card Project implements encoding and decoding of characters using the IBM 80 column punch card format.
IBM 80 Column Punch Card Encoding
The IBM 80 column punch card system uses a specific encoding scheme where characters are represented by patterns of punches in a 12-row grid:
Rows in IBM 80 Column Punch Card
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Row 12 (Y): β β β β β β β β β β β β β β β ... β β β β β β β β β Zone
β Row 11 (X): β β β β β β β β β β β β β β β ... β β β β β β β β β Zone
β Row 0: β β β β β β β β β β β β β β β ... β β β β β β β β β Zone
β Row 1: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 2: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 3: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 4: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 5: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 6: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 7: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 8: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β Row 9: β β β β β β β β β β β β β β β ... β β β β β β β β β Digit
β 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ... 6 7 8 9 0 β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Columns (1-80)
In this system, the rows are organized in a specific order:
- Zone Rows: Rows 12, 11, and 0 are called "zone" rows
- Digit Rows: Rows 1-9 are called "digit" rows
Characters are encoded using combinations of punches in these rows, following a systematic pattern:
Character Encoding Patterns
The encoding follows these general rules:
-
Letters A-I: Zone punch in row 12 + digit punch in rows 1-9 respectively
- A = 12 + 1
- B = 12 + 2
- ...
- I = 12 + 9
-
Letters J-R: Zone punch in row 11 + digit punch in rows 1-9 respectively
- J = 11 + 1
- K = 11 + 2
- ...
- R = 11 + 9
-
Letters S-Z: Zone punch in row 0 + digit punch in rows 2-9 respectively
- S = 0 + 2
- T = 0 + 3
- ...
- Z = 0 + 9
-
Numbers 0-9: Single punch in row 0-9 respectively
- 0 = row 0 only
- 1 = row 1 only
- ...
- 9 = row 9 only
-
Special Characters: Various combinations of punches
- Space = no punches
- Period (.) = 12 + 3 + 8
- Comma (,) = 0 + 3 + 8
- Dash (-) = 11 only
- etc.
Character Mapping Implementation
In the Punch Card Project, the character mapping is implemented using a dictionary that maps characters to their punch patterns. This is the core of the encoding system:
# Hollerith/EBCDIC encoding mapping implemented as a Python dictionary
CHAR_MAPPING = {
# A-I: zone punch 12 + digit punches 1-9
'A': [1,0,0,1,0,0,0,0,0,0,0,0], # 12,1 (index 0=row12, index 3=row1)
'B': [1,0,0,0,1,0,0,0,0,0,0,0], # 12,2
'C': [1,0,0,0,0,1,0,0,0,0,0,0], # 12,3
# ...more characters...
}
Each character maps to a 12-element array representing rows 12, 11, 0-9, where:
- A
1
means a punch is present in that row - A
0
means no punch in that row
For example, the letter 'A' has punches in row 12 and row 1, so its array is [1,0,0,1,0,0,0,0,0,0,0,0]
.
Encoding Process
The encoding process involves converting a text message into a series of punch patterns, with each character occupying one column on the punch card. Here's the detailed process:
def encode_message(message: str, max_width: int = 80) -> List[List[bool]]:
"""
Encode a message into punch card patterns.
Args:
message: Text message to encode
max_width: Maximum columns to use (default 80)
Returns:
2D array of punch patterns, where each column is one character
"""
# Convert to uppercase and truncate if needed
message = message.upper()[:max_width]
# Create empty grid (12 rows Γ message length)
encoded_grid = [[False for _ in range(len(message))] for _ in range(12)]
# Process each character
for col_idx, char in enumerate(message):
# Get punch pattern for this character
pattern = CHAR_MAPPING.get(char, [0] * 12) # Default to no punches if character unknown
# Apply the pattern to the grid column
for row_idx, is_punched in enumerate(pattern):
if is_punched:
encoded_grid[row_idx][col_idx] = True
return encoded_grid
The encoding process follows these steps:
- Prepare the message: Convert to uppercase and truncate if longer than 80 characters
- Create empty grid: Initialize a 12Γn grid where n is the message length
- Process each character:
- Look up the punch pattern for the character
- Apply the pattern to the corresponding column in the grid
- Return the encoded grid: A 2D array where
True
represents a punch
Visualization of Encoding
To visualize how the letter 'H' is encoded:
Character 'H' lookup: [1,0,0,0,0,0,0,0,1,0,0,0] # Punches in rows 12 and 8
Applied to column:
Row 12: β (punched)
Row 11: β‘ (not punched)
Row 0: β‘ (not punched)
Row 1: β‘ (not punched)
Row 2: β‘ (not punched)
Row 3: β‘ (not punched)
Row 4: β‘ (not punched)
Row 5: β‘ (not punched)
Row 6: β‘ (not punched)
Row 7: β‘ (not punched)
Row 8: β (punched)
Row 9: β‘ (not punched)
Decoding Process
The decoding process is the reverse of encoding - it converts punch patterns back into characters:
def decode_message(punch_grid: List[List[bool]]) -> str:
"""
Decode a punch card grid back into a text message.
Args:
punch_grid: 2D array of punch patterns (12 rows Γ n columns)
Returns:
Decoded text message
"""
# Create a reverse mapping from punch patterns to characters
reverse_mapping = {tuple(pattern): char for char, pattern in CHAR_MAPPING.items()}
# Decode each column into a character
decoded_message = ""
for col_idx in range(len(punch_grid[0])):
# Extract this column's pattern
column_pattern = [punch_grid[row_idx][col_idx] for row_idx in range(12)]
# Convert booleans to binary values (1/0)
binary_pattern = [1 if is_punched else 0 for is_punched in column_pattern]
# Look up character in reverse mapping
char = reverse_mapping.get(tuple(binary_pattern), ' ') # Default to space if unknown
decoded_message += char
return decoded_message
The decoding process follows these steps:
- Create reverse mapping: Build a dictionary that maps punch patterns to characters
- Process each column:
- Extract the punch pattern from that column
- Convert to the binary format expected by the reverse mapping
- Look up the character for this pattern
- Append to the decoded message
- Return the decoded message: The original text is reconstructed
Translation to Display Forms
Once characters are encoded into punch patterns, they need to be translated into various forms for display:
1. GUI Visualization
For the GUI display, punch patterns are converted to colored rectangles:
def update_gui_from_encoding(encoded_grid, punch_card_grid):
"""Update GUI grid from encoded punch patterns"""
for row in range(12):
for col in range(len(encoded_grid[0])):
# Set LED to on (True) or off (False)
punch_card_grid.updateLED(row, col, encoded_grid[row][col])
2. Terminal Visualization
For terminal display, punches are represented as ASCII characters:
def format_for_terminal(encoded_grid):
"""Format punch patterns for terminal display"""
terminal_rows = []
for row in range(12):
row_chars = []
for col in range(len(encoded_grid[0])):
# 'O' for punched holes, ' ' for no hole
row_chars.append('O' if encoded_grid[row][col] else ' ')
terminal_rows.append(''.join(row_chars))
return '\n'.join(terminal_rows)
3. Hardware Control
For hardware LED control, punches are converted to electrical signals:
def send_to_hardware(encoded_grid, hardware_controller):
"""Send punch patterns to hardware controller"""
for row in range(12):
for col in range(len(encoded_grid[0])):
# Send signal to turn LED on or off
hardware_controller.set_led(row, col, encoded_grid[row][col])
Character Set Expansion and Custom Mappings
The Punch Card Project also supports expansion of the character set and custom mappings:
-
Extended Character Set: Additional special characters can be defined using various punch combinations.
-
Custom Encoding Maps: Users can define their own character mappings for special purposes.
-
Multiple Encoding Standards: Different historical punch card standards (IBM 026, IBM 029, Bull, etc.) can be implemented with different mapping dictionaries.
Complete Encoding-Decoding-Display Pipeline
The entire process of encoding a message, displaying it, and optionally decoding it involves these key steps:
βββββββββββββββββ
β User Input β
β "HELLO" β
βββββββββ¬ββββββββ
β
βΌ
βββββββββββββββββ βββββββββββββββββββ
β char_to_punch ββββββΊβ CHAR_MAPPING β
β Conversion βββββββ Dictionary β
βββββββββ¬ββββββββ βββββββββββββββββββ
β
β Encoded message
βΌ
βββββββββββββββββ
β Encoded Grid β
β 12Γ80 booleansβ
βββββββββ¬ββββββββ
β
ββββββββ΄ββββββββ
β β
βΌ βΌ
ββββββββββββββ ββββββββββββββ
β Display β β Decode β
β Adapters β β Function β
ββββββββ¬ββββββ βββββββ¬βββββββ
β β
βββββββ΄βββββββ βΌ
β β ββββββββββββββ
βΌ βΌ β Original β
GUI Hardware β Text β
Display Control β "HELLO" β
ββββββββββββββ
Practical Example: Encoding the Message "HELLO"
To illustrate the process, here's how "HELLO" is encoded:
1. Character Lookup
'H' => [1,0,0,0,0,0,0,0,1,0,0,0] # 12,8
'E' => [1,0,0,0,0,0,0,1,0,0,0,0] # 12,5
'L' => [0,1,0,0,0,1,0,0,0,0,0,0] # 11,3
'L' => [0,1,0,0,0,1,0,0,0,0,0,0] # 11,3
'O' => [0,1,0,0,0,0,0,0,1,0,0,0] # 11,6
2. Grid Construction
Row 12: [β ,β ,β‘,β‘,β‘] # 'H','E' have punches in row 12
Row 11: [β‘,β‘,β ,β ,β ] # 'L','L','O' have punches in row 11
Row 0: [β‘,β‘,β‘,β‘,β‘]
Row 1: [β‘,β‘,β‘,β‘,β‘]
Row 2: [β‘,β‘,β‘,β‘,β‘]
Row 3: [β‘,β‘,β ,β ,β‘] # 'L','L' have punches in row 3
Row 4: [β‘,β‘,β‘,β‘,β‘]
Row 5: [β‘,β ,β‘,β‘,β‘] # 'E' has punch in row 5
Row 6: [β‘,β‘,β‘,β‘,β ] # 'O' has punch in row 6
Row 7: [β‘,β‘,β‘,β‘,β‘]
Row 8: [β ,β‘,β‘,β‘,β‘] # 'H' has punch in row 8
Row 9: [β‘,β‘,β‘,β‘,β‘]
3. Display Conversion
GUI: LEDs at positions (12,0), (12,1), (11,2), (11,3), (11,4), (3,2), (3,3), (5,1), (6,4), (8,0) are ON.
Terminal:
β β β‘ β‘ β‘ # Row 12
β‘ β‘ β β β # Row 11
β‘ β‘ β‘ β‘ β‘ # Row 0
β‘ β‘ β‘ β‘ β‘ # Row 1
β‘ β‘ β‘ β‘ β‘ # Row 2
β‘ β‘ β β β‘ # Row 3
β‘ β‘ β‘ β‘ β‘ # Row 4
β‘ β β‘ β‘ β‘ # Row 5
β‘ β‘ β‘ β‘ β # Row 6
β‘ β‘ β‘ β‘ β‘ # Row 7
β β‘ β‘ β‘ β‘ # Row 8
β‘ β‘ β‘ β‘ β‘ # Row 9
Implementation Variations
The Punch Card Project implements several variations of this encoding system:
-
Binary Array Format: For internal processing, punch patterns are stored as arrays of booleans or binary values (0/1).
-
Row-index Format: For readability, punches are sometimes represented as lists of row indices where punches occur (e.g., 'A': [12, 1]).
-
ASCII Visual Format: For terminal display, punches are represented as ASCII characters ('O' or 'β ' for punched, ' ' or 'β‘' for not punched).
-
Compact Serialization: For storage in the database, punch patterns can be serialized to compact strings or binary formats.
The flexibility of these implementations allows the system to efficiently process punch card data while maintaining historical accuracy.
Decision-Making and Design Rationale
Modularity and Maintainability
The modular design allows independent development and testing of components, simplifying debugging and future enhancements.
Historical Accuracy
Adhering to IBM 80 column encoding and punch card standards provides educational value and historical authenticity.
Hardware Abstraction
Supporting both simulated and physical hardware enables flexible development and deployment scenarios.
GUI and Terminal Support
Providing both GUI and terminal interfaces ensures broad compatibility and accessibility.
Note:
This document provides a high-level yet detailed overview. For deeper dives into specific modules or code implementations, refer directly to the source code and inline documentation.