# Digital Read Out (DRO) Application The `dro.py` file implements a Digital Read Out system for a lathe. It displays the current position of the lathe tool on two axes (X and Z) by reading encoder data from an Arduino via I2C communication. ## Overview The application follows the **Model-View-Controller (MVC)** architectural pattern, which separates concerns: - **Model**: Manages the state of the axes and their positions - **View**: Displays the positions in a Tkinter GUI - **Controller**: Handles user input and I2C communication ## Architecture ### Data Flow ``` Arduino (Encoder Data) ↓ (I2C) Controller (Serial Communication) → Model (Position Calculation) ↓ ↓ View (GUI Update) ← ← ← ← ← ← ← ← ← ← Observers ``` ## Classes ### Axis Represents a single axis (X or Z) with position, scale, and offset. **Attributes:** - `_position`: Current position in mm - `_scale`: Scale factor (mm per encoder step) - `_offset`: Offset applied to position (mm) - `_name`: Axis identifier ('x' or 'z') **Methods:** - `set_position(pos)`: Set the raw position - `get_position()`: Get the raw position - `set_scale(scale)`: Set the scale factor - `get_scale()`: Get the scale factor - `set_offset(offset)`: Set the offset - `get_offset()`: Get the offset ### Model Manages the state of both axes and notifies observers of changes. Implements the **Observer pattern** for MVC binding. **Enums:** - `XMode.RADIUS (0)`: X axis displays radius (default) - `XMode.DIAMETER (1)`: X axis displays diameter - `Updated.POS_X (0)`: X position changed - `Updated.POS_Z (1)`: Z position changed - `Updated.X_MODE (2)`: X mode (radius/diameter) changed - `Updated.FULLSCREEN (3)`: Fullscreen state changed **Key Methods:** - `attach(observer)`: Register an observer (typically the View) - `notify(updated)`: Notify all observers of changes - `steps_to_position(axis, steps)`: Convert encoder steps to millimeters - `set_position(axis, pos)`: Update a position (notifies if changed) - `set_offset(axis, offset)`: Set the zero offset for calibration - `get_effective_position(axis)`: Get position with offset applied; converts diameter to radius if needed - `set_toggle_x_mode()`: Toggle between radius and diameter display - `set_scale(axis, scale)`: Set the steps-to-mm conversion factor **Special Behavior:** - Position updates only trigger notifications if the change is ≥ 0.005 mm (prevents chatter) - When X axis is in DIAMETER mode, displayed position is doubled ### Controller Handles user interaction and I2C communication with the Arduino. **Hardware:** - I2C Bus: 1 (standard Raspberry Pi I2C) - Arduino Address: 0x08 **Data Format:** - Reads 4 bytes from Arduino every poll cycle - Bytes 0-1: X position (little-endian signed 16-bit integer) - Bytes 2-3: Z position (little-endian signed 16-bit integer) **Methods:** - `poll_i2c_data()`: Read encoder positions from Arduino via I2C - `handle_x_position_update(steps)`: Process X encoder data - `handle_z_position_update(steps)`: Process Z encoder data - `handle_btn_x0_press()`: Set X zero point (sets offset to -current position) - `handle_btn_z0_press()`: Set Z zero point - `handle_btn_toggle_x_mode()`: Switch between radius/diameter display - `hanlde_btn_x()`: Open dialog to manually set X position - `hanlde_btn_z()`: Open dialog to manually set Z position - `toggle_fullscreen()`: Toggle fullscreen display (F11) - `end_fullscreen()`: Exit fullscreen mode (Escape) - `handle_btn_calc()`: Launch system calculator (galculator) - `shutdown()`: Shutdown the system (produces clean shutdown) ### View Tkinter GUI displaying positions, buttons, and status. Acts as an observer of the Model. **GUI Layout:** ``` ┌─────────────────────────────────┐ │ X Position │ [X] [X_0] │ │ Z Position │ [Z] [Z_0] │ │ Mode │ │ │ │ [r/D] [Calc] [Off]│ └─────────────────────────────────┘ ``` **Display Features:** - Large font (80pt) for easy reading from distance - Green text on black background for visibility - Real-time position updates at 10 Hz - Mode indicator (R for radius, D for diameter) **Button Functions:** - **X / Z**: Open dialog to set position - **X_0 / Z_0**: Set zero point (calibrate) - **r/D**: Toggle radius/diameter mode - **Calc**: Launch calculator - **Power**: Shutdown system **Keyboard Shortcuts:** - **F11**: Toggle fullscreen - **Escape**: Exit fullscreen - **A/Z** (test mode): Rotate X encoder clockwise/counterclockwise - **S/X** (test mode): Rotate Z encoder clockwise/counterclockwise ## Usage ### Basic Startup ```bash python3 dro.py ``` ### Command Line Options ```bash # Maximize window on startup python3 dro.py --zoomed # Run in test mode (no I2C communication, use keyboard to simulate encoders) python3 dro.py --test # Both options python3 dro.py --zoomed --test ``` ### Typical Workflow 1. **Startup**: Application connects to Arduino via I2C and displays current encoder positions 2. **Zero Axes**: Press X_0 and Z_0 buttons at the tool's starting position 3. **Move Tool**: As you move the tool, positions update in real-time 4. **Set Position**: Click X or Z button to set a specific position (useful for manual adjustments) 5. **Toggle Mode**: Press r/D to switch X axis between radius and diameter display 6. **Shutdown**: Click the power button or use the system menu ### Offset/Zero Calibration The offset mechanism allows setting the zero point at any position: ``` Displayed Position = Raw Position + Offset ``` When you press X_0 or Z_0, the system sets: ``` Offset = -Current_Position ``` This makes the current display read zero. ### Radius vs Diameter Mode The X axis can display in two modes: - **Radius (r)**: Shows actual distance from spindle centerline - **Diameter (D)**: Shows diameter (actual = radius × 2) The Model automatically converts when displaying: ``` Diameter Display = Radius × 2 ``` ## Configuration ### Default Scales ```python model.set_scale('x', -2.5/200) # -12.5 mm per 1000 steps (negative = flip direction) model.set_scale('z', 90/1000) # 0.09 mm per step ``` These calibration values should be adjusted based on your encoder specifications: - Negative X scale flips the direction to match lathe conventions - Scale = (Distance Travel in mm) / (Encoder Steps) ### Update Frequency - GUI updates every 100ms (10 Hz) - I2C polling rate matches GUI updates ## Dependencies ### Python Packages - `tkinter`: GUI framework (built-in with Python) - `smbus2`: I2C communication (falls back to fake_smbus.py if not available) - Standard library: `enum`, `subprocess`, `getopt`, `sys`, `shutil`, `logging` ### Hardware - Raspberry Pi (tested on Pi 4) - Arduino Nano (with I2C firmware at address 0x08) - 2 Rotary encoders connected to Arduino ### System - `galculator`: Calculator application (optional) - `sudo` access for shutdown command ## Error Handling The application gracefully handles several error conditions: ### I2C Communication Errors ```python try: data = self._bus.read_i2c_block_data(self._address, 0, 4) except IOError as e: logger.error(f"I2C read error: {e}") except OSError as e: logger.error(f"I2C OS error: {e}") ``` Errors are logged but don't crash the application. The last known position is retained. ### Missing Dependencies If `smbus2` is not installed, the application falls back to the local `RPi/fake_smbus.py`: ```python try: import smbus2 except ImportError: import RPi.fake_smbus as smbus2 logger.warning('smbus2 not available; using fake smbus2.py') ``` ## Testing Mode The `--test` flag enables keyboard simulation of encoder rotation: ``` X Axis: A = Rotate clockwise (increase X) Z = Rotate counterclockwise (decrease X) Z Axis: S = Rotate clockwise (increase Z) X = Rotate counterclockwise (decrease Z) ``` This allows testing the GUI without physical encoders or Arduino. ## Known Issues / Notes - Method name typo: `hanlde_btn_x()` and `hanlde_btn_z()` should be `handle_btn_x()` and `handle_btn_z()` - Position updates use a 0.005 mm hysteresis threshold (prevents noise-induced updates) - Fullscreen mode requires the Tkinter window to support the platform's fullscreen API ## Performance Considerations - GUI updates are non-blocking and scheduled on the Tkinter event loop - I2C reads are synchronous and run every 100ms (10 Hz update rate) - Position filtering reduces redundant updates by ~99% in typical operation - StringVar widgets minimize GUI redraws by only updating when values change ## Future Enhancements - Add tool offset/wear compensation - Support more than 2 axes - Persistent configuration file for scales and offsets - Network interface for remote monitoring - Data logging of position history