adding files

This commit is contained in:
2026-03-21 23:51:53 +01:00
commit 8cb0240ca2
19 changed files with 2162 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
# Arduino I2C Protocol
This document describes the I2C communication protocol between the Raspberry Pi (DRO application) and the Arduino (encoder interface).
## Hardware Setup
### I2C Configuration
- **Bus**: I2C Bus 1 (standard Raspberry Pi I2C)
- **Arduino Slave Address**: 0x08
- **Data Rate**: Standard (100 kHz) or Fast (400 kHz)
- **Pull-up Resistors**: 4.7 kΩ (typical I2C standards)
### Connections
```
Raspberry Pi Arduino
GPIO2 (SDA) <-> SDA
GPIO3 (SCL) <-> SCL
GND <- GND
5V (optional)-> 5V (if Arduino powered separately)
```
## Data Format
The Arduino transmits encoder positions as a 4-byte block:
### Memory Layout
```
Byte 0-1: X Position (little-endian signed 16-bit integer)
Byte 2-3: Z Position (little-endian signed 16-bit integer)
```
### Example
If encoder reads:
- X encoder: +5000 steps
- Z encoder: -200 steps
The transmitted bytes would be:
```
Byte 0: 0x88 (LSB of 5000)
Byte 1: 0x13 (MSB of 5000)
Byte 2: 0x38 (LSB of -200)
Byte 3: 0xFF (MSB of -200 in two's complement)
```
### Python Parsing
```python
import struct
# Read from I2C
data = bus.read_i2c_block_data(address, 0, 4)
# Convert to signed integers
x_position = int.from_bytes(bytes(data[0:2]), 'little', signed=True)
z_position = int.from_bytes(bytes(data[2:4]), 'little', signed=True)
# Or using struct:
x_pos, z_pos = struct.unpack('<hh', bytes(data))
```
## Communication Protocol
### Single Read Operation
```
Master (RPi) Slave (Arduino)
├─ START condition ────────────────→
├─ Address + READ ─────────────────→
↓ ↓
┌─────────────────────────────────────┐
│ Arduino sends 4 bytes of data │
│ (Acknowledgement from master) │
└─────────────────────────────────────┘
├─ STOP condition ──────────────────→
```
### Timing
- **Read Operation Time**: ~1-2 ms at 100 kHz
- **Poll Interval**: 100 ms (10 Hz refresh)
- **Data Transmission**: Asynchronous (happens in parallel with GUI rendering)
## Arduino Firmware Requirements
The Arduino sketch must:
1. **Initialize I2C Slave**
```cpp
Wire.begin(0x08); // Address 0x08
Wire.onRequest(requestEvent); // Register event handler
```
2. **Maintain Encoder Position Variables**
```cpp
volatile int16_t x_position = 0;
volatile int16_t z_position = 0;
```
3. **Update Positions from Encoders**
- Attach interrupts to encoder pins
- Increment/decrement positions on encoder pulses
4. **Respond to I2C Read Requests**
```cpp
void requestEvent() {
byte buffer[4];
// Convert to bytes (little-endian)
buffer[0] = (byte)x_position;
buffer[1] = (byte)(x_position >> 8);
buffer[2] = (byte)z_position;
buffer[3] = (byte)(z_position >> 8);
Wire.write(buffer, 4);
}
```
## Encoder Interface
The Arduino handles the low-level encoder reading:
### Encoder Connection
```
Rotary Encoder (2-bit Gray code or quadrature)
├─ Signal A ──→ Arduino Interrupt Pin
├─ Signal B ──→ Arduino Pin
└─ GND ──────→ Arduino GND
```
### Common Encoder Types
1. **Quadrature Encoder**
- 2 signals 90° out of phase
- Allows detection of direction and speed
- Typical: KY-040 module
2. **Incremental Encoder**
- Pulse + Direction signals
- Requires external direction determination
3. **Absolute Encoder**
- Maintains position across power cycles
- More complex protocol (often SPI/USB)
## Error Handling
### Arduino Side
- **Buffer Overrun**: Ensure encoder ISR is fast (< 100 µs)
- **I2C Collision**: Wire library handles this automatically
- **Power Loss**: Position may reset unless using volatile storage
### Raspberry Pi Side
The DRO application handles:
```python
try:
data = self._bus.read_i2c_block_data(self._address, 0, 4)
except IOError as e:
# I2C bus error (timeout, NACK, collision)
logger.error(f"I2C read error: {e}")
except OSError as e:
# System-level I2C error
logger.error(f"I2C OS error: {e}")
```
If a read fails, the last known position is retained.
## Debugging I2C Communication
### Check I2C Bus
```bash
# List I2C devices
i2cdetect -y 1
# Expected output for Arduino at 0x08:
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: -- -- -- -- -- -- -- -- 08 -- -- -- --
```
### Manual Read Test
```bash
# Read 4 bytes from address 0x08, register 0
i2cget -y 1 0x08 0 i 4
# Expected output: four hex bytes
```
### Python Test
```python
import smbus2
bus = smbus2.SMBus(1)
data = bus.read_i2c_block_data(0x08, 0, 4)
print(f"Raw bytes: {[hex(b) for b in data]}")
x = int.from_bytes(bytes(data[0:2]), 'little', signed=True)
z = int.from_bytes(bytes(data[2:4]), 'little', signed=True)
print(f"X: {x}, Z: {z}")
bus.close()
```
## Performance Characteristics
- **Latency**: ~2-5 ms (I2C + processing)
- **Throughput**: 100 reads/second (limited by 100 ms GUI update interval)
- **Bandwidth**: 4 bytes × 10 Hz = 320 bytes/second
- **Jitter**: ±10 ms (non-deterministic on Linux)
## Troubleshooting
| Problem | Likely Cause | Solution |
|---------|--------------|----------|
| I2C not detected | Arduino not running | Upload firmware to Arduino |
| Data is zeros | Encoder not moving | Check encoder connections |
| Erratic values | Noise on I2C bus | Add pull-up resistors, shorten wires |
| Periodic dropouts | I2C collision | Use slower clock speed (100 kHz) |
| Position drifts | Encoder misconfiguration | Calibrate scale factor |
## References
- [I2C Specification](https://www.i2c-bus.org/)
- [Arduino Wire Library](https://www.arduino.cc/en/Reference/Wire)
- [Raspberry Pi I2C Setup](https://learn.adafruit.com/adafruit-16-channel-pwm-servo-driver/using-the-adafruit-library)

282
docs/DRO.md Normal file
View File

@@ -0,0 +1,282 @@
# 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

307
docs/INSTALLATION.md Normal file
View File

@@ -0,0 +1,307 @@
# DRO Installation & Setup Guide
This guide walks through setting up the Digital Read Out application on a Raspberry Pi.
## System Requirements
- **Hardware**: Raspberry Pi 4 (or later)
- **OS**: Raspbian/Raspberry Pi OS (Bullseye or later)
- **Python**: 3.7+
- **Arduino**: Arduino Nano with I2C encoder firmware
## Step 1: Install Dependencies
### Enable I2C
```bash
sudo raspi-config
# Navigate to: Interfacing Options → I2C → Yes
# Reboot
sudo reboot
```
### Install Python Libraries
```bash
# Update package manager
sudo apt-get update
sudo apt-get upgrade
# Install required packages
sudo apt-get install python3-tk python3-dev python3-pip
# Install Python dependencies
pip3 install smbus2
pip3 install adafruit-circuitpython-ads1x15 # Optional: for ADC
```
### Optional: System Calculator
```bash
sudo apt-get install galculator
```
## Step 2: Configure I2C for Shutdown
Allow the application to shutdown without a password:
```bash
sudo visudo
```
Add this line to the end of the file (replace `pi` with your username):
```
pi ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown
```
Save with Ctrl+X, then Y, then Enter.
## Step 3: Upload Arduino Firmware
The Arduino must run firmware that:
1. Reads from two rotary encoders
2. Maintains position counters
3. Responds to I2C read requests at address 0x08
See [ARDUINO_I2C_PROTOCOL.md](./ARDUINO_I2C_PROTOCOL.md) for protocol details.
Example firmware structure:
```cpp
#include <Wire.h>
volatile int16_t x_position = 0;
volatile int16_t z_position = 0;
void setup() {
Wire.begin(0x08);
Wire.onRequest(requestEvent);
// Setup encoder interrupts
pinMode(2, INPUT);
pinMode(3, INPUT);
attachInterrupt(0, x_encoder_isr, CHANGE);
attachInterrupt(1, z_encoder_isr, CHANGE);
}
void requestEvent() {
byte buffer[4];
buffer[0] = (byte)x_position;
buffer[1] = (byte)(x_position >> 8);
buffer[2] = (byte)z_position;
buffer[3] = (byte)(z_position >> 8);
Wire.write(buffer, 4);
}
// Encoder ISR routines...
void loop() {}
```
Refer to the `i2c_encoder/` directory for the complete Arduino sketch.
## Step 4: Verify I2C Connection
Test I2C communication:
```bash
# Install I2C tools
sudo apt-get install i2c-tools
# Scan for I2C devices
i2cdetect -y 1
# Should show Arduino at address 0x08:
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: -- -- -- -- -- -- -- -- 08 -- -- -- --
```
## Step 5: Auto-Start Configuration
### Option A: Desktop File (GUI Autostart)
Create `/home/pi/.config/autostart/dro.desktop`:
```ini
[Desktop Entry]
Type=Application
Name=DRO
Exec=python3 /home/pi/dro/dro.py --zoomed
StartupNotify=false
X-GNOME-Autostart-always-run=true
```
### Option B: Systemd Service
Create `/etc/systemd/system/dro.service`:
```ini
[Unit]
Description=Digital Read Out (DRO) Application
After=graphical.target
[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/dro
ExecStart=/usr/bin/python3 /home/pi/dro/dro.py --zoomed
Restart=on-failure
RestartSec=5
[Install]
WantedBy=graphical.target
```
Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable dro.service
sudo systemctl start dro.service
```
### Option C: Shell Script Autostart
Create `/home/pi/dro/autostart.sh`:
```bash
#!/bin/bash
cd /home/pi/dro
python3 dro.py --zoomed
```
Make executable:
```bash
chmod +x /home/pi/dro/autostart.sh
```
Add to crontab (for user pi):
```bash
crontab -e
# Add line:
@reboot /home/pi/dro/autostart.sh
```
## Step 6: Test the Installation
### Test Mode (without Arduino)
```bash
cd ~/dro
python3 dro.py --test
# Use keyboard to test:
# A/Z = X encoder up/down
# S/X = Z encoder up/down
```
### With Hardware
```bash
# Start the application
python3 dro.py --zoomed
# Check logs (if systemd service)
journalctl -u dro.service -f
```
## Step 7: Calibration
### Set Scale Factors
Edit `dro.py` and adjust these lines based on your encoder specifications:
```python
# X axis: negative to flip direction
model.set_scale('x', -2.5/200) # mm per step
# Z axis
model.set_scale('z', 90/1000) # mm per step
```
**How to determine:**
1. Move the tool a known distance (e.g., 10 mm)
2. Record the encoder step count
3. Scale = Distance (mm) / Steps
4. Use negative for X if the direction is backwards
### Set Zero Points
1. Position tool at starting location
2. Press **X_0** button to zero X axis
3. Press **Z_0** button to zero Z axis
## Troubleshooting
### No I2C Communication
```bash
# Check I2C is enabled
raspi-config
# Verify devices are connected
i2cdetect -y 1
# Check for USB connections (if Arduino is USB)
ls -la /dev/ttyUSB*
```
### GUI Not Appearing
```bash
# Check display settings
export DISPLAY=:0
python3 dro.py
# Verify Tkinter is installed
python3 -c "import tkinter; print('Tkinter OK')"
```
### I2C Errors
- Check pull-up resistors (4.7 kΩ is standard)
- Verify Arduino firmware is running
- Try slower I2C speed: edit smbus2 initialization
### Position Drift
- Recalibrate scale factors
- Check encoder hardware connections
- Verify encoder isn't slipping
## File Structure
```
/home/pi/dro/
├── dro.py # Main application
├── autostart.sh # Auto-start script
├── power.png # Power button icon
├── test_model.py # Unit tests
├── README.md # Project overview
├── docs/
│ ├── DRO.md # Application documentation
│ └── ARDUINO_I2C_PROTOCOL.md # I2C protocol
└── i2c_encoder/
├── i2c_encoder.ino # Arduino firmware
└── i2c_test.py # Arduino test script
```
## Performance Tips
1. **Reduce Update Interval** (for faster response):
```python
self._update_interval_ms = 50 # 20 Hz instead of 10 Hz
```
2. **Increase I2C Speed**:
```python
bus = smbus2.SMBus(1)
bus.bus.set_clock(400000) # 400 kHz fast mode
```
3. **Use GPIO Directly** (instead of I2C) for more performance:
- Requires hardware changes
- RPi.GPIO library for direct GPIO reading
## Security Notes
- The DRO needs `sudo` access to shutdown (configured via sudoers)
- Store any sensitive config files outside the repo
- Use `.env` files for sensitive data (not committed to git)
## Getting Help
- Check logs: `journalctl -u dro.service -f`
- Test I2C: `i2cget -y 1 0x08 0 i 4`
- Python console test:
```bash
python3
import smbus2
bus = smbus2.SMBus(1)
print(bus.read_i2c_block_data(0x08, 0, 4))
```

158
docs/README.md Normal file
View File

@@ -0,0 +1,158 @@
# Documentation
This directory contains comprehensive documentation for the DRO (Digital Read Out) application.
## Quick Start
1. **[INSTALLATION.md](./INSTALLATION.md)** - Setup guide for Raspberry Pi
- System requirements
- Dependency installation
- Arduino firmware setup
- Auto-start configuration
- Troubleshooting
2. **[DRO.md](./DRO.md)** - Application architecture and usage
- Architecture overview (MVC pattern)
- Class descriptions (Axis, Model, Controller, View)
- Usage instructions and workflows
- Configuration and calibration
- GUI layout and keyboard shortcuts
3. **[ARDUINO_I2C_PROTOCOL.md](./ARDUINO_I2C_PROTOCOL.md)** - Hardware protocol details
- I2C communication format
- Data layout (4-byte packets)
- Arduino firmware requirements
- Encoder interface details
- Debugging and troubleshooting
## Documentation Structure
```
DRO.md
├── Overview - What is DRO?
├── Architecture - MVC pattern and data flow
├── Classes - Detailed class documentation
│ ├── Axis - Single axis representation
│ ├── Model - State management and observers
│ ├── Controller - User input and I2C
│ └── View - Tkinter GUI
├── Usage - How to run the application
├── Configuration - Scale factors and settings
├── Error Handling - Exception handling
├── Testing - Test mode documentation
└── Performance - Design considerations
INSTALLATION.md
├── Requirements - Hardware and software
├── Step 1-7 - Complete setup walkthrough
│ ├── Dependencies
│ ├── I2C configuration
│ ├── Arduino firmware
│ ├── I2C verification
│ ├── Auto-start
│ ├── Testing
│ └── Calibration
├── Troubleshooting - Common issues and fixes
└── Tips - Performance optimization
ARDUINO_I2C_PROTOCOL.md
├── Hardware Setup - Pinout and connections
├── Data Format - 4-byte packet structure
├── Communication Protocol - I2C timing diagrams
├── Arduino Requirements - Firmware template
├── Encoder Interface - Quadrature signals
├── Error Handling - Error cases
├── Debugging - Testing tools
└── Troubleshooting - Problem solving
```
## Key Concepts
### MVC Architecture
The application separates concerns:
- **Model**: Manages encoder positions, scale factors, and offsets
- **View**: Tkinter GUI displaying positions and buttons
- **Controller**: I2C communication, button handling, input processing
### Position Calculation
```
Raw Position (steps from encoder)
× scale factor
Device Position (mm)
↓ + offset
Displayed Position (mm, possibly × 2 for diameter)
```
### I2C Communication
```
Arduino → 4 bytes (X lo, X hi, Z lo, Z hi) → DRO Application
Convert to signed 16-bit integers
Apply scale factors
Update display (10 Hz)
```
## Common Tasks
### Change Scale Factors
**File**: `dro.py` → Search for `model.set_scale()`
```python
model.set_scale('x', -2.5/200) # X axis: mm per step
model.set_scale('z', 90/1000) # Z axis: mm per step
```
### Change GUI Update Rate
**File**: `dro.py` → Class `View.__init__()`
```python
self._update_interval_ms = 100 # Change to 50 for 20 Hz
```
### Enable Auto-Start
**See**: `INSTALLATION.md` → Step 5
Three options: Desktop file, Systemd service, or Cron job
### Test Without Arduino
**Command**: `python3 dro.py --test`
Simulates encoders with keyboard (A/Z/S/X keys)
### Debug I2C Issues
**See**: `ARDUINO_I2C_PROTOCOL.md` → Debugging section
Tools: `i2cdetect`, `i2cget`, Python test script
## Troubleshooting Index
| Issue | Documentation |
|-------|----------------|
| Application won't start | INSTALLATION.md: Troubleshooting |
| I2C not detected | ARDUINO_I2C_PROTOCOL.md: Debugging |
| Positions are wrong | DRO.md: Configuration, INSTALLATION.md: Calibration |
| Arduino not communicating | ARDUINO_I2C_PROTOCOL.md: Troubleshooting |
| Missing dependencies | INSTALLATION.md: Step 1 |
| Auto-start not working | INSTALLATION.md: Step 5 |
## Contributing to Docs
When updating documentation:
1. Keep technical accuracy
2. Include code examples where relevant
3. Update the table of contents
4. Cross-reference related sections
5. Test all commands/instructions before documenting
## See Also
- **README.md** - Project overview
- **code-review-generic.instructions.md** - Code review guidelines
- **python.instructions.md** - Python coding standards

File diff suppressed because one or more lines are too long

106
docs/mock-gui.drawio Normal file
View File

@@ -0,0 +1,106 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.0.16 Chrome/132.0.6834.196 Electron/34.2.0 Safari/537.36" version="26.0.16">
<diagram name="Sida-1" id="yWZbiVy1Htm2IWZYSUBU">
<mxGraphModel dx="1087" dy="1162" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="1AcGMTNSweSiUO7pp5MJ-3" value="" style="shape=mxgraph.mockup.containers.marginRect;rectMarginTop=10;strokeColor=#666666;strokeWidth=1;dashed=0;rounded=1;arcSize=5;recursiveResize=0;html=1;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="59" y="240" width="460" height="360" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-4" value="w-frame" style="shape=rect;strokeColor=none;fillColor=#008cff;strokeWidth=1;dashed=0;rounded=1;arcSize=20;fontColor=#ffffff;fontSize=17;spacing=2;spacingTop=-2;align=left;autosize=1;spacingLeft=4;resizeWidth=0;resizeHeight=0;perimeter=none;html=1;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-3">
<mxGeometry x="5" width="90" height="30" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-27" value="" style="shape=mxgraph.mockup.containers.marginRect;rectMarginTop=10;strokeColor=#666666;strokeWidth=1;dashed=0;rounded=1;arcSize=5;recursiveResize=0;html=1;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-3">
<mxGeometry x="70" y="30" width="370" height="155" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-28" value="x-frame" style="shape=rect;strokeColor=none;fillColor=#008cff;strokeWidth=1;dashed=0;rounded=1;arcSize=20;fontColor=#ffffff;fontSize=17;spacing=2;spacingTop=-2;align=left;autosize=1;spacingLeft=4;resizeWidth=0;resizeHeight=0;perimeter=none;html=1;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-27">
<mxGeometry x="5" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-29" value="X0" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-27">
<mxGeometry x="250" y="60" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-30" value="X" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-27">
<mxGeometry x="310" y="60" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-33" value="&lt;font style=&quot;font-size: 43px;&quot;&gt;1000.000&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;align=right;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-27">
<mxGeometry x="10" y="55" width="230" height="60" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-34" value="" style="shape=mxgraph.mockup.containers.marginRect;rectMarginTop=10;strokeColor=#666666;strokeWidth=1;dashed=0;rounded=1;arcSize=5;recursiveResize=0;html=1;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-3">
<mxGeometry x="70" y="185" width="370" height="155" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-35" value="z-frame" style="shape=rect;strokeColor=none;fillColor=#008cff;strokeWidth=1;dashed=0;rounded=1;arcSize=20;fontColor=#ffffff;fontSize=17;spacing=2;spacingTop=-2;align=left;autosize=1;spacingLeft=4;resizeWidth=0;resizeHeight=0;perimeter=none;html=1;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-34">
<mxGeometry x="5" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-36" value="Z0" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-34">
<mxGeometry x="250" y="60" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-37" value="Z" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-34">
<mxGeometry x="310" y="60" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-38" value="&lt;font style=&quot;font-size: 43px;&quot;&gt;1000.000&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;align=right;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-34">
<mxGeometry x="10" y="55" width="230" height="60" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-6" value="" style="shape=mxgraph.mockup.containers.marginRect;rectMarginTop=10;strokeColor=#666666;strokeWidth=1;dashed=0;rounded=1;arcSize=5;recursiveResize=0;html=1;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="519" y="240" width="250" height="360" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-7" value="e-frame" style="shape=rect;strokeColor=none;fillColor=#008cff;strokeWidth=1;dashed=0;rounded=1;arcSize=20;fontColor=#ffffff;fontSize=17;spacing=2;spacingTop=-2;align=left;autosize=1;spacingLeft=4;resizeWidth=0;resizeHeight=0;perimeter=none;html=1;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="5" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-5" value="1" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="10" y="70" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-8" value="2" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="70" y="70" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-9" value="3" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="130" y="70" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-11" value="4" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="10" y="130" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-12" value="5" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="70" y="130" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-13" value="6" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="130" y="130" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-14" value="7" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="10" y="190" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-15" value="8" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="70" y="190" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-16" value="9" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="130" y="190" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-17" value="." style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="10" y="250" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-18" value="0" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="70" y="250" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-19" value="+/-" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="130" y="250" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-20" value="enter" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="190" y="190" width="50" height="110" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-21" value="r/D" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="190" y="130" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-40" value="" style="group" vertex="1" connectable="0" parent="1AcGMTNSweSiUO7pp5MJ-6">
<mxGeometry x="190" y="70" width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-22" value="" style="strokeWidth=1;shadow=0;dashed=0;align=center;html=1;shape=mxgraph.mockup.buttons.button;strokeColor=#666666;fontColor=#ffffff;mainText=;buttonStyle=round;fontSize=17;fontStyle=1;fillColor=#008cff;whiteSpace=wrap;" vertex="1" parent="1AcGMTNSweSiUO7pp5MJ-40">
<mxGeometry width="50" height="50" as="geometry" />
</mxCell>
<mxCell id="1AcGMTNSweSiUO7pp5MJ-39" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;endWidth=11.03448275862069;endSize=5.3517241379310345;strokeColor=#FFFFFF;strokeWidth=3;" edge="1" parent="1AcGMTNSweSiUO7pp5MJ-40">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="43" y="25" as="sourcePoint" />
<mxPoint x="7" y="24.660000000000025" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>