Files
pi-dro/docs/DRO.md
2026-03-21 23:51:53 +01:00

283 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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