# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**Python EMC2305** - Production-ready I2C fan controller driver for embedded Linux platforms. Provides fan speed control, RPM monitoring, and temperature sensing capabilities for the Microchip EMC2305 chip.

**Hardware:**
- **Board:** CGW-LED-FAN-CTRL-4-REV1
- **Controller:** EMC2305-1-APTR (5-channel PWM fan controller)
- **I2C Address:** 0x4D (default)
- **Power Rails:** 3.3V (EMC2305 VDD), 5V (Fan power)

**Target Platform:** Multiplatform Linux (Banana Pi, Raspberry Pi, generic Linux systems with I2C support)

## Development Commands

### Driver Testing

```bash
# Run basic I2C communication test
PYTHONPATH=. python3 tests/test_i2c_basic.py

# Test fan speed control
PYTHONPATH=. python3 examples/python/test_fan_control.py

# Test RPM monitoring
PYTHONPATH=. python3 examples/python/test_rpm_monitor.py
```

### I2C Device Verification

```bash
# Verify I2C devices are detected
i2cdetect -y [bus_number]

# Should show fan controller device(s) at configured addresses
```

### Installation

```bash
# Install in development mode
pip3 install -e .

# Install development dependencies
pip3 install -e ".[dev]"
```

## Architecture

### Layer Structure

```
┌─────────────────────────────────────────┐
│   Application Code                      │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│   Fan Controller Driver                 │  <- Hardware Abstraction
│   - Speed control                       │
│   - RPM monitoring                      │
│   - Configuration management            │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│   I2C Communication (driver/i2c.py)     │  <- Low-level I/O
│   - Direct I2C read/write               │
│   - Cross-process bus locking           │
│   - Error handling                      │
└─────────────────────────────────────────┘
```

### Critical Design Patterns

**1. I2C Bus Locking**
- **Purpose**: Prevents conflicts when multiple processes share the I2C bus
- **Implementation**: File-based advisory locks using `filelock` library
- **Lock file**: `/var/lock/i2c-[bus].lock` (configurable)
- **Timeout**: 5 seconds (default, configurable)
- **Wraps every I2C read/write operation** in `emc2305/driver/i2c.py`

**2. Thread Safety**
- Device-level locks for concurrent access
- Thread-safe configuration management
- Safe shutdown procedures

**3. Error Handling**
- Comprehensive I2C error detection
- Hardware fault monitoring
- Graceful degradation

**4. Configuration System**
- YAML/TOML-based configuration
- Auto-creation with sensible defaults
- Runtime configuration updates
- Per-device settings

## Important Files

### Core Hardware Interface
- `emc2305/driver/i2c.py` - Low-level I2C communication with cross-process bus locking
- `emc2305/driver/[chip].py` - Main fan controller driver (chip-specific)
- `emc2305/driver/constants.py` - Hardware constants (addresses, registers, timing)

### Configuration
- `emc2305/settings.py` - Configuration dataclasses and file loading
- `config/emc2305.yaml` - Default configuration template

### Documentation
- `docs/hardware/` - Datasheets, schematics, integration guides
- `docs/development/` - Implementation notes and decisions
- `README.md` - Project overview and quick start

## Device Constraints

### Hardware Limitations
- **I2C bus speed**: Typically 100 kHz or 400 kHz (check device requirements)
- **No hot-plug**: Devices must be present at initialization
- **Chip-specific limitations**: See EMC2305 datasheet for detailed constraints

### I2C Bus Sharing
- **Multiple services may use the same I2C bus** - always use I2C locking
- Follow the pattern established in the luminex project
- See `emc2305/driver/i2c.py` for implementation reference

## Critical EMC2305 Configuration Requirements

⚠️ **IMPORTANT**: The following configuration settings are MANDATORY for EMC2305 to function correctly:

### 1. GLBL_EN Bit (Register 0x20, Bit 1) - CRITICAL
**Without this bit enabled, ALL PWM outputs are disabled regardless of individual fan settings.**

This is now automatically enabled in driver initialization (`emc2305/driver/emc2305.py:231-234`).

### 2. UPDATE_TIME - Must be 200ms
**Using 500ms breaks PWM control completely.**

Default is now correctly set to 200ms (`emc2305/driver/constants.py:589`, `emc2305/driver/emc2305.py:58`).

### 3. Drive Fail Band Registers - Corrected Addresses
**Some datasheets have incorrect register addresses.**

- `REG_FAN1_DRIVE_FAIL_BAND_LOW = 0x3A` (NOT 0x3B)
- `REG_FAN1_DRIVE_FAIL_BAND_HIGH = 0x3B` (NOT 0x3C)

### 4. PWM Voltage Levels - Hardware Consideration
⚠️ **EMC2305 outputs 3.3V PWM logic (VDD = 3.3V)**

If using 5V PWM fans, a hardware level shifter circuit is required (MOSFET-based or IC-based like TXB0104).

### 5. PWM Polarity - Fan-Specific
Different fans use different PWM logic:
- **Active Low (standard)**: LOW = run, HIGH = stop → Normal polarity
- **Active High (inverted)**: HIGH = run, LOW = stop → Inverted polarity

Check fan datasheet to determine correct configuration.

### 6. Minimum Drive - Unrestricted Range
`min_drive_percent: int = 0` (changed from 20% to allow full PWM range).

### 7. Tachometer Edges Configuration - CRITICAL for RPM Accuracy
**Date Verified:** 2025-11-25

The `edges` parameter MUST match your fan's tachometer pulses per revolution:

| Fan Type | Pulses/Rev | `edges` Setting |
|----------|------------|-----------------|
| 1-pole   | 1          | `edges=3`       |
| 2-pole   | 2          | `edges=5` (default) |
| 3-pole   | 3          | `edges=7`       |
| 4-pole   | 4          | `edges=9`       |

**How to determine**: Check fan datasheet for "FG Signal" or "Tachometer" specification.
Many fans produce 2 pulses per revolution (use `edges=5`), but some high-speed fans
produce only 1 pulse per revolution (use `edges=3`).

**Example configuration:**
```python
from emc2305.driver.emc2305 import EMC2305, FanConfig
from emc2305.driver.i2c import I2CBus

bus = I2CBus(bus_number=0)
controller = EMC2305(i2c_bus=bus, device_address=0x4D)

# For 1-pulse-per-revolution fan:
config = FanConfig(edges=3)
controller.configure_fan(1, config)

rpm = controller.get_current_rpm(1)
print(f"RPM: {rpm}")
```

**Diagnostic tip**: If RPM readings are incorrect, try different `edges` values and compare
with expected fan speed. The correct setting will give readings that scale linearly with PWM.

### 8. TACH Pull-up Resistor Requirement
**EMC2305 TACH pins are open-drain inputs** and require external pull-up resistors.

**Recommended**: 10k ohm pull-up to 3.3V (same as EMC2305 VDD).

Without proper pull-up, TACH readings will be incorrect or saturated.

### 9. RPM Calculation Formula (Fixed 2025-11-25)
The driver uses the corrected EMC2305 RPM formula:

```
RPM = ((edges - 1) * TACH_FREQ * 60) / (TACH_COUNT * poles)
```

Where:
- `edges` = Tachometer edges setting (3, 5, 7, or 9)
- `TACH_FREQ` = 32000 Hz (internal) or 32768 Hz (external clock)
- `TACH_COUNT` = Raw register value right-shifted by 3 bits
- `poles` = (edges - 1) / 2

The 3-bit right shift on TACH_COUNT accounts for the register format where
the low register stores the count in bits 7:3 (3 LSBs unused).

### 10. Register Readback Quantization (Known Behavior)
**Date Verified:** 2025-11-24
**Hardware:** CGW-LED-FAN-CTRL-4-REV1, EMC2305 Rev 0x80

PWM register readback exhibits minor quantization anomaly at specific duty cycles:
- **25% (0x40) reads back as ~30% (0x4C)**
- All other tested values (0%, 50%, 75%, 100%) read back correctly
- **Physical PWM signal is CORRECT** - verified with oscilloscope
- **Fan operation is CORRECT** - no functional impact
- Anomaly appears to be internal hardware quantization, not a driver issue

**Impact:** None for production use. Register readback is 80% accurate (4/5 test points).

**Driver Enhancement:** Added `set_pwm_duty_cycle_verified()` method with configurable tolerance for applications requiring readback validation.

**Reference:** `docs/development/register-readback-findings.md` for comprehensive analysis

## Common Patterns

### Adding New Features

1. **New I2C register operations**: Add to appropriate driver file in `emc2305/driver/`
2. **New configuration options**: Add to `emc2305/settings.py` with validation
3. **New examples**: Add to `examples/python/` with clear documentation
4. **New tests**: Add to `tests/` following existing test patterns

### Working with I2C

```python
from emc2305.driver.i2c import I2CBus

# Initialize I2C bus with locking
bus = I2CBus(bus_number=0, lock_enabled=True)

# Read byte from register (automatically locked)
value = bus.read_byte(device_address, register)

# Write byte to register (automatically locked)
bus.write_byte(device_address, register, value)
```

### Error Handling

```python
try:
    fan.set_speed(75)
except I2CError as e:
    # Handle I2C communication errors
    logger.error(f"I2C error: {e}")
except FanControllerError as e:
    # Handle fan controller specific errors
    logger.error(f"Fan controller error: {e}")
```

## Testing Strategy

### Hardware-Dependent Tests
Most tests require actual hardware with the fan controller connected:
- Tests expect specific I2C addresses (configurable via environment variables)
- Use `TEST_I2C_BUS` and `TEST_DEVICE_ADDRESS` environment variables

### Test Categories
- `test_i2c_*.py` - I2C communication validation
- `test_fan_*.py` - Fan control functionality
- `test_config_*.py` - Configuration management
- Integration tests require real hardware

### Running Tests

```bash
# Run all tests
pytest tests/

# Run specific test
pytest tests/test_i2c_basic.py -v

# Run with hardware address override
TEST_I2C_BUS=1 TEST_DEVICE_ADDRESS=0x2F pytest tests/
```

## Deployment

### To Target System (e.g., Banana Pi)

```bash
# Create deployment tarball
tar czf /tmp/ventus-deploy.tar.gz ventus/ requirements.txt setup.py tests/ examples/

# Copy to target system
scp /tmp/ventus-deploy.tar.gz user@target:/tmp/

# Install on target
ssh user@target "cd /opt && tar xzf /tmp/ventus-deploy.tar.gz && pip3 install -r requirements.txt"
```

## Known Issues and Gotchas

### I2C Permissions
User must have permissions to access I2C devices:
```bash
# Add user to i2c group
sudo usermod -aG i2c username

# Or set permissions on device
sudo chmod 666 /dev/i2c-*
```

### I2C Lock File Permissions
Lock file directory must be writable:
```bash
sudo mkdir -p /var/lock
sudo chmod 1777 /var/lock
```

### Bus Speed Configuration
Some devices require specific I2C bus speeds. Configure via device tree or kernel module parameters.

## Code Style Standards

### Python Standards
- **PEP 8**: Follow standard Python style guide
- **Type hints**: Use type annotations for function signatures
- **Docstrings**: Google-style docstrings for all public functions/classes
- **Line length**: Max 100 characters
- **Imports**: Group stdlib, third-party, local with blank lines between

### Documentation
- All public APIs must have comprehensive docstrings
- Include usage examples in docstrings
- Document hardware-specific behavior
- Add inline comments for complex logic

### Error Handling
- Always wrap errors with context
- Use custom exception types for different error categories
- Log errors appropriately (debug, info, warning, error)
- Never silently catch exceptions

## Hardware Integration Notes

### I2C Communication
- Always use the I2C locking mechanism from `emc2305/driver/i2c.py`
- Handle I2C timeouts and retry logic
- Validate register addresses and values
- Check device presence before operations

### Fan Controller Operations
- Respect minimum/maximum speed limits
- Validate RPM readings for sanity
- Handle missing tachometer signals gracefully
- Monitor for hardware faults

### Temperature Sensing
- Validate temperature readings (reasonable range)
- Handle sensor errors
- Provide calibration options if needed

## Quick Reference

### Essential Commands
```bash
# Check I2C devices
i2cdetect -y 0

# Test basic communication
python3 -m emc2305.driver.test_basic

# Run examples
python3 examples/python/test_fan_control.py

# Install in dev mode
pip3 install -e .
```

### Configuration Locations
- **User config**: `~/.config/emc2305/emc2305.yaml`
- **System config**: `/etc/emc2305/emc2305.yaml`
- **Lock files**: `/var/lock/i2c-*.lock`

## Summary for AI Assistants

When working with this codebase:

1. **Follow luminex patterns** - I2C communication, locking, configuration
2. **Professional code quality** - Type hints, docstrings, error handling
3. **Hardware safety first** - Validate inputs, handle faults, safe defaults
4. **Document everything** - Especially hardware-specific behavior
5. **Test thoroughly** - Include hardware tests and edge cases
6. **Respect I2C locking** - Always use the locking mechanism
7. **Multiplatform support** - Don't assume specific hardware

**Critical Knowledge**:
- I2C bus locking is mandatory (follow luminex pattern)
- All register operations must validate addresses and values
- Fan speeds must be constrained to safe ranges
- RPM monitoring requires proper tachometer signal handling
- Temperature readings need validation and calibration
