# az-device-contract-codegen
Generate deterministic Modbus contract artifacts from typed service definitions.
- Crate: `az-device-contract-codegen`
- Local path: `creates/az-device-contract-codegen`
- Install: `cargo add az-device-contract-codegen`
The crate plans a Modbus address layout, then emits a stable artifact bundle for:
- C headers and sources for RTU/TCP handlers
- Markdown protocol documents
- Rust client code built around `tokio-modbus`
## Minimal usage
```rust
use az_device_contract_codegen::{
generate_bundle, ApiKind, ContractMethod, ContractRenderRequest, ContractService,
ContractTypedItem, ReadReturnKind, TransportKind, ValueType,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let request = ContractRenderRequest {
transports: vec![TransportKind::Rtu, TransportKind::Tcp],
services: vec![ContractService {
api_kind: ApiKind::RuntimeRead,
interface_name: "DeviceReadApi".into(),
interface_summary: Some("Runtime reads".into()),
methods: vec![ContractMethod {
summary: Some("Read signal lights".into()),
method_name: "getSignalLights".into(),
read_return_kind: Some(ReadReturnKind::Dto),
read_return_type_name: Some("SignalLights".into()),
read_area: None,
write_area: None,
read_fields: vec![
ContractTypedItem {
name: "ch1".into(),
summary: None,
value_type: ValueType::Boolean,
},
ContractTypedItem {
name: "ch2".into(),
summary: None,
value_type: ValueType::Boolean,
},
],
parameters: vec![],
}],
}],
};
let bundle = generate_bundle(&request)?;
for artifact in &bundle.artifacts {
println!("{}", artifact.relative_path.display());
}
Ok(())
}
```
Typical output paths:
- `Core/Inc/generated/modbus/*.h`
- `Core/Src/generated/modbus/**/*.c`
- `Docs/generated/modbus/protocols/*.md`
- `Docs/generated/modbus/rust/*.rs`
## Default mapping rules
- `STATIC_READ` defaults to input registers (`0x04`)
- `RUNTIME_READ`
- all-boolean DTO/scalar reads default to discrete inputs (`0x02`)
- everything else defaults to input registers (`0x04`)
- `WRITE`
- all-boolean parameters default to coils (`0x05` / `0x0F`)
- everything else defaults to holding registers (`0x06` / `0x10`)
`readArea` and `writeArea` can override those defaults explicitly.
## Notes
- The crate itself does not write files; it returns `GeneratedArtifact` values.
- Generated Rust clients assume the consumer compiles them with `tokio-modbus` and `serialport`.
- Generated filenames and helper symbols currently use the existing `okm_*` naming line used by this repository.