283 lines
8.7 KiB
Markdown
283 lines
8.7 KiB
Markdown
# 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
|