# Write a driver module

You want to use hardware that Viam doesn’t support out of the box, whether it’s a sensor, camera, motor, or any other component. A driver module bridges that gap: it implements a standard Viam resource API so that data capture, the Test section, the SDKs, and other platform features work with your hardware automatically.

Driver modules run as separate processes alongside `viam-server`, so they carry their own dependencies and can crash without bringing `viam-server` down. You package and distribute them through the Viam registry.

This page walks through seven steps for writing a driver module, using a temperature-and-humidity sensor as the worked example. For background on choosing a resource API, module lifecycle, and dependencies, see the [overview](https://docs.viam.com/build-modules/overview/).

## Steps

When writing a module, follow the steps outlined below. To illustrate each step we’ll use a sensor module as a worked example. The same patterns apply to any resource type – substitute the appropriate API and methods for your use case.

### 1. Generate the module

Before you run the generator, [install the Viam CLI](https://docs.viam.com/cli/overview/#install) and log in with `viam login`.

The generator prompts for your organization’s public namespace. If you have not set one yet, click the organization dropdown at the upper right of the Viam app, select **Settings**, then **Set a public namespace**. You can also enter your Org ID at the prompt instead.

Run the Viam CLI generator:

```bash
viam module generate
```

The generator creates a new directory named after your module (for example, `my-sensor-module`) in your current working directory. `cd` into that directory for the rest of the steps.

The generator creates a complete project with the following files:

| File | Purpose |
| --- | --- |
| `src/main.py` | Entry point – starts the module server |
| `src/models/my_sensor.py` | Resource class skeleton – you will edit this |
| `requirements.txt` | Python dependencies |
| `meta.json` | Module metadata for the registry |
| `setup.sh` | Installs dependencies into a virtualenv |
| `build.sh` | Packages the module for upload |
| `.github/workflows/deploy.yml` | CI workflow for cloud builds |

### 2. Implement the resource API

Open the generated resource file: `src/models/my_sensor.py` (Python) or `module.go` (Go). The generator creates a class (Python) or struct (Go) with stub methods. You need to make four changes to the resource file, then review the entry point the generator created:

1. Define your config attributes.
2. Add validation logic.
3. Populate your resource from config in the constructor.
4. Implement the API methods for your resource type.

#### Define your config attributes

Config attributes are the fields a user sets when they configure your component in the Viam app. The generator creates an empty config; add a field for each attribute your module needs.

In `src/models/my_sensor.py`, declare your config attributes as type-annotated class variables:

```python
class MySensor(Sensor, EasyResource):
    MODEL: ClassVar[Model] = Model(
        ModelFamily("my-org", "my-sensor-module"), "my-sensor"
    )

source_url: str
    poll_interval: float
```

In `module.go`, find the empty `Config` struct and add fields:

```go
type Config struct {
    SourceURL    string  `json:"source_url"`
    PollInterval float64 `json:"poll_interval"`
}
```

#### Add validation logic

The generator creates an empty validation method. Add checks for required fields and return any dependencies your module needs.

In Python:
```python
@classmethod
def validate_config(
    cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
    fields = config.attributes.fields
    if "source_url" not in fields:
        raise Exception("source_url is required")
    if not fields["source_url"].string_value.startswith("http"):
        raise Exception("source_url must be an HTTP or HTTPS URL")
    return [], []  # No required or optional dependencies
```

In Go:
```go
func (cfg *Config) Validate(path string) ([]string, []string, error) {
    if cfg.SourceURL == "" {
        return nil, nil, fmt.Errorf("source_url is required")
    }
    return nil, nil, nil  // No required or optional dependencies
}
```

#### Populate your resource from config

Override `new` to read your config and set the fields on the instance:

```python
@classmethod
def new(cls, config: ComponentConfig,
        dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
    sensor = super().new(config, dependencies)
    fields = config.attributes.fields
    sensor.source_url = fields["source_url"].string_value
    sensor.poll_interval = (
        fields["poll_interval"].number_value
        if "poll_interval" in fields
        else 10.0
    )
    return sensor
```

#### Implement the API method

For a sensor, the key method is `GetReadings`, which returns a map of reading names to values:

In Python:
```python
async def get_readings(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> Mapping[str, SensorReading]:
    try:
        response = requests.get(self.source_url, timeout=5)
        response.raise_for_status()
        data = response.json()
        return {
            "temperature": data["temp"],
            "humidity": data["humidity"],
        }
    except requests.RequestException as e:
        self.logger.error(f"Failed to read from {self.source_url}: {e}")
        raise
```

In Go:
```go
func (s *mySensorModuleMySensor) Readings(ctx context.Context, extra map[string]interface{}) (map[string]interface{}, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.cfg.SourceURL, nil)
    if err != nil {
        return nil, fmt.Errorf("building request: %w", err)
    }
    resp, err := s.client.Do(req)
    if err != nil {
        s.logger.CErrorw(ctx, "failed to read from source",
            "url", s.cfg.SourceURL, "error", err)
        return nil, fmt.Errorf("failed to read from %s: %w", s.cfg.SourceURL, err)
    }
    defer resp.Body.Close()

var data sensorResponse
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return nil, fmt.Errorf("failed to decode response: %w", err)
    }

return map[string]interface{}{
        "temperature": data.Temp,
        "humidity": data.Humidity,
    }, nil
}
```

### 3. Test locally

Use the CLI to build and deploy your module to a machine, then verify it works. Two commands cover the common development loop: `viam module reload` (cloud build) for cross-architecture work, and `viam module reload-local` (local build) for same-architecture iteration.

### 4. Add logging

Both the Python and Go SDKs provide a logger that writes to `viam-server`’s log stream, visible in the **LOGS** tab.

### 5. Handle dependencies

Many modules need access to other resources on the same machine. To use another resource, you need to declare the dependency in your config validation method, resolve the dependency in your constructor, and call methods on it in your API implementation.

### 6. Use the module data directory

Every module gets a persistent data directory. Use this for caches, databases, or any state that should survive module restarts.

### 7. Add multiple models to one module (optional)

A single module can provide multiple models, even across different APIs. There is no limit on the number of models per module.
