asic-rs 0.6.2

Simple ASIC management in Rust
Documentation
# asic-rs ![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue) [![asic-rs on crates.io](https://img.shields.io/crates/v/asic-rs)](https://crates.io/crates/asic-rs) [![asic-rs on docs.rs](https://docs.rs/asic-rs/badge.svg)](https://docs.rs/asic-rs) [![Source Code Repository](https://img.shields.io/badge/Code-On%20GitHub-blue?logo=GitHub)](https://github.com/256foundation/asic-rs) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)

asic-rs is an async miner management and control library for ASIC miners.
It provides one set of concepts across Rust and Python: a factory discovers
miners, a miner object gathers data and performs supported control operations,
and shared data/config models describe the result.

The Rust crate is published as `asic-rs`. The Python bindings are published as
`pyasic_rs` and expose the same high-level API through PyO3 classes and
Pydantic-compatible data models.

### API Map

|Concept|Rust|Python|
|-------|----|------|
|Discovery and miner construction|[`MinerFactory`][__link0]|`pyasic_rs.MinerFactory`|
|Miner handle|`Box<dyn Miner>`|`pyasic_rs.Miner`|
|Full telemetry snapshot|`MinerData`|`pyasic_rs.data.MinerData`|
|Hashrate values|`HashRate`, `HashRateUnit`|`HashRate`, `HashRateUnit`|
|Pool configuration|`PoolGroupConfig`, `PoolConfig`|`PoolGroup`, `Pool`|
|Fan configuration|`FanConfig`|`FanConfig`|
|Tuning configuration|`TuningConfig`|`TuningConfig`|
|Optional controls/configs|`supports_*` methods|`supports_*` properties|

All network operations are asynchronous. Rust methods generally return
`Result<T>` and use `Option<T>` when a miner does not expose a value. Python
methods are awaitable and return the Python equivalent, using `None` for missing
or unsupported values.

### Examples

The paired examples below use stable markers so documentation tools can render
Rust and Python snippets as language tabs while GitHub, PyPI, and docs.rs still
show both examples plainly.

#### Get One Miner

If the miner IP is known, ask `MinerFactory` to identify the firmware and build
the correct miner implementation.

<!-- asic-rs-example:get-miner rust -->

```rust
use asic_rs::MinerFactory;
use std::{net::IpAddr, str::FromStr};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let factory = MinerFactory::new();
    let ip = IpAddr::from_str("192.168.1.10")?;

    if let Some(miner) = factory.get_miner(ip).await? {
        println!("Found {} {} at {}", miner.get_device_info().make, miner.get_device_info().model, ip);
    }

    Ok(())
}
```

<!-- asic-rs-example:get-miner python -->

```python
import asyncio

from pyasic_rs import MinerFactory


async def main() -> None:
    factory = MinerFactory()
    miner = await factory.get_miner("192.168.1.10")

    if miner is not None:
        print(f"Found {miner.make} {miner.model} at {miner.ip}")


if __name__ == "__main__":
    asyncio.run(main())
```

#### Scan A Network

When the exact IP is not known, add a subnet, octet range, or range string to
the factory and scan it. Large scans automatically use bounded concurrency.

<!-- asic-rs-example:scan rust -->

```rust
use asic_rs::MinerFactory;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let miners = MinerFactory::from_subnet("192.168.1.0/24")?
        .with_concurrent_limit(2500)
        .scan()
        .await?;

    println!("Found {} miner(s)", miners.len());
    Ok(())
}
```

<!-- asic-rs-example:scan python -->

```python
import asyncio

from pyasic_rs import MinerFactory


async def main() -> None:
    miners = await (
        MinerFactory.from_subnet("192.168.1.0/24")
        .with_concurrent_limit(2500)
        .scan()
    )

    print(f"Found {len(miners)} miner(s)")


if __name__ == "__main__":
    asyncio.run(main())
```

Other range constructors are available in both languages:

<!-- asic-rs-example:ranges rust -->

```rust
let by_octets = MinerFactory::from_octets("192", "168", "1", "1-255")?;
let by_range = MinerFactory::from_range("192.168.1.1-255")?;
```

<!-- asic-rs-example:ranges python -->

```python
from pyasic_rs import MinerFactory

by_octets = MinerFactory.from_octets("192", "168", "1", "1-255")
by_range = MinerFactory.from_range("192.168.1.1-255")
```

#### Stream Scan Results

Use streaming scans when you want to act on miners as soon as they are found
instead of waiting for the whole scan to finish.

<!-- asic-rs-example:scan-stream rust -->

```rust
use asic_rs::MinerFactory;
use futures::StreamExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut stream = MinerFactory::from_subnet("192.168.1.0/24")?.scan_stream();

    while let Some(miner) = stream.next().await {
        println!("{} {}", miner.get_device_info().make, miner.get_device_info().model);
    }

    Ok(())
}
```

<!-- asic-rs-example:scan-stream python -->

```python
import asyncio

from pyasic_rs import MinerFactory


async def main() -> None:
    factory = MinerFactory.from_subnet("192.168.1.0/24")

    async for miner in factory.scan_stream():
        print(f"{miner.make} {miner.model}")


if __name__ == "__main__":
    asyncio.run(main())
```

#### Gather Data

`get_data` returns a full `MinerData` snapshot. Individual `get_*` calls are
available when only one field is needed.

<!-- asic-rs-example:data rust -->

```rust
use asic_rs::MinerFactory;
use std::{net::IpAddr, str::FromStr};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let factory = MinerFactory::new();
    let ip = IpAddr::from_str("192.168.1.10")?;

    if let Some(miner) = factory.get_miner(ip).await? {
        let data = miner.get_data().await;
        let mac = miner.get_mac().await;

        println!("{} is mining: {}", data.ip, data.is_mining);
        println!("MAC: {mac:?}");
    }

    Ok(())
}
```

<!-- asic-rs-example:data python -->

```python
import asyncio

from pyasic_rs import MinerFactory


async def main() -> None:
    miner = await MinerFactory().get_miner("192.168.1.10")
    if miner is None:
        return

    data = await miner.get_data()
    mac = await miner.get_mac()

    print(f"{data.ip} is mining: {data.is_mining}")
    print(f"MAC: {mac}")


if __name__ == "__main__":
    asyncio.run(main())
```

To reduce collection work, exclude fields from a full data snapshot.

<!-- asic-rs-example:data-exclude rust -->

```rust
use asic_rs::core::data::collector::DataField;
let data = miner
    .get_data_filtered(vec![DataField::Hashboards, DataField::Chips])
    .await;
```

<!-- asic-rs-example:data-exclude python -->

```python
from pyasic_rs.data import DataField

data = await miner.get_data(exclude=[DataField.Hashboards, DataField.Chips])
```

#### Authentication

Backends use their built-in default credentials unless you override them.
Set credentials before starting other operations on that miner.

<!-- asic-rs-example:auth rust -->

```rust
use asic_rs::MinerFactory;
use asic_rs::core::traits::auth::MinerAuth;
use std::{net::IpAddr, str::FromStr};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let factory = MinerFactory::new();
    let ip = IpAddr::from_str("192.168.1.10")?;

    if let Some(mut miner) = factory.get_miner(ip).await? {
        miner.set_auth(MinerAuth::new("admin", "secret"));
        let data = miner.get_data().await;
        println!("{:?}", data.hashrate);
    }

    Ok(())
}
```

<!-- asic-rs-example:auth python -->

```python
miner = await MinerFactory().get_miner("192.168.1.10")
if miner is not None:
    miner.set_auth("admin", "secret")
    data = await miner.get_data()
```

#### Control A Miner

Control support depends on the miner and firmware. Check the matching
`supports_*` value before issuing a control command in user-facing tools.

<!-- asic-rs-example:control rust -->

```rust
if miner.supports_restart() {
    let restarted = miner.restart().await?;
    println!("Restart accepted: {restarted}");
}
```

<!-- asic-rs-example:control python -->

```python
if miner.supports_restart:
    restarted = await miner.restart()
    print(f"Restart accepted: {restarted}")
```

#### Configure Pools, Fans, And Tuning

Configuration methods follow the same support pattern as controls. The Python
models are Pydantic-compatible, so they can be validated, dumped, and embedded
in your own Pydantic models.

<!-- asic-rs-example:config rust -->

```rust
use asic_rs::core::config::{
    fan::FanConfig,
    pools::{PoolConfig, PoolGroupConfig},
    tuning::TuningConfig,
};
use asic_rs::core::data::{miner::TuningTarget, pool::PoolURL};
if miner.supports_pools_config() {
    let group = PoolGroupConfig {
        name: "default".to_string(),
        quota: 1,
        pools: vec![PoolConfig {
            url: PoolURL::from("stratum+tcp://pool.example.com:3333".to_string()),
            username: "worker.1".to_string(),
            password: "x".to_string(),
        }],
    };
    miner.set_pools_config(vec![group]).await?;
}

if miner.supports_fan_config() {
    miner.set_fan_config(FanConfig::manual(80)).await?;
}

if miner.supports_tuning_config() {
    let config = TuningConfig::new(TuningTarget::from_watts(3200.0));
    miner.set_tuning_config(config, None).await?;
}
```

<!-- asic-rs-example:config python -->

```python
from pyasic_rs.config import FanConfig, Pool, PoolGroup, TuningConfig

if miner.supports_pools_config:
    group = PoolGroup(
        name="default",
        quota=1,
        pools=[
            Pool(
                url="stratum+tcp://pool.example.com:3333",
                username="worker.1",
                password="x",
            )
        ],
    )
    await miner.set_pools_config([group])

if miner.supports_fan_config:
    await miner.set_fan_config(FanConfig.manual(80))

if miner.supports_tuning_config:
    await miner.set_tuning_config(TuningConfig.power(3200.0))
```

### Python Data Models

Python data/config classes are backed by Rust structs and implement a
Pydantic-style surface:

```python
from pydantic import BaseModel

from pyasic_rs.data import HashRate


class Snapshot(BaseModel):
    hashrate: HashRate


snapshot = Snapshot.model_validate(
    {"hashrate": {"value": 100.0, "unit": "TH/s", "algo": "SHA256"}}
)
print(snapshot.model_dump())
```

Use `model_validate`, `model_dump`, and `model_json_schema` on supported model
classes when integrating with Python validation or API layers.


 [__link0]: https://docs.rs/asic-rs/latest/asic_rs/struct.MinerFactory.html