https://github.com/databus23/go-sml
pure-Go library for decoding Smart Message Language (SML) data
https://github.com/databus23/go-sml
Last synced: 14 days ago
JSON representation
pure-Go library for decoding Smart Message Language (SML) data
- Host: GitHub
- URL: https://github.com/databus23/go-sml
- Owner: databus23
- License: apache-2.0
- Created: 2026-04-10T21:56:17.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-10T22:16:43.000Z (3 months ago)
- Last Synced: 2026-06-06T14:00:04.978Z (21 days ago)
- Language: Go
- Size: 90.8 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# go-sml
A pure-Go library for decoding [Smart Message Language (SML)](https://de.wikipedia.org/wiki/Smart_Message_Language) data from German smart electricity meters. Zero external dependencies.
```go
go get github.com/databus23/go-sml
```
## Quick Start
### CLI tool
A small command-line tool is included that prints readings in the same format
as the C `sml_server`:
```bash
go install github.com/databus23/go-sml/cmd/smlreader@latest
smlreader capture.bin
# or from stdin:
smlreader -
```
Output:
```
1-0:1.8.0*255#8391648.8#Wh
1-0:15.7.0*255#163.5#W
```
### As a library
Decode a raw SML payload and print all meter readings:
```go
file, err := sml.Decode(payload)
if err != nil {
log.Fatal(err)
}
for _, entry := range file.Readings() {
if v, ok := entry.ScaledValue(); ok {
fmt.Printf("%s = %f %s\n", entry.OBISString(), v, entry.UnitString())
}
}
// Output:
// 1-0:1.8.0*255 = 8391648.800000 Wh
// 1-0:16.7.0*255 = 163.500000 W
```
## Streaming from a Serial Port
For continuous reading from a serial device (or any `io.Reader`), use `Listen`.
It handles transport framing, escape sequences, and CRC validation automatically:
```go
port, _ := os.Open("/dev/ttyUSB0")
defer port.Close()
err := sml.Listen(ctx, port, func(file *sml.File) error {
for _, entry := range file.Readings() {
if v, ok := entry.ScaledValue(); ok {
fmt.Printf("%s = %.1f %s\n", entry.OBISString(), v, entry.UnitString())
}
}
return nil
})
```
Use `ListenMessages` instead if you want per-message callbacks rather than
per-file.
## API Overview
### Root package `sml`
| Symbol | Description |
|--------|-------------|
| `Decode(data) (*File, error)` | Decode a complete SML payload (strict mode) |
| `DecodeWithOptions(data, opts)` | Decode with configurable strictness |
| `Listen(ctx, r, handler)` | Stream-decode from an `io.Reader` |
| `ListenMessages(ctx, r, handler)` | Like `Listen`, but calls handler per message |
| `File.Readings()` | Collect all `ListEntry` values from `GetListResponse` messages |
| `ListEntry.ScaledValue()` | Apply scaler, return `float64` |
| `ListEntry.OBISString()` | Format OBIS code as `"A-B:C.D.E*F"` |
| `ListEntry.UnitString()` | DLMS unit name (e.g. `"Wh"`, `"W"`, `"V"`) |
| `UnitName(code)` | Look up DLMS unit code directly |
### Sub-package `transport`
| Symbol | Description |
|--------|-------------|
| `NewReader(r) *Reader` | Wrap an `io.Reader` to extract SML transport frames |
| `Reader.Next() ([]byte, error)` | Return next validated frame payload |
The transport layer auto-detects CRC-16/X.25 (standard) and CRC-16/Kermit
(Holley DTZ541 meters).
### Value Types
SML values preserve their wire encoding width. Use a type switch on the
`Value` interface:
```go
switch v := entry.Value.(type) {
case sml.Uint32:
fmt.Println("unsigned 32-bit:", uint32(v))
case sml.Int16:
fmt.Println("signed 16-bit:", int16(v))
case sml.OctetString:
fmt.Printf("bytes: %x\n", []byte(v))
}
```
Available types: `OctetString`, `Bool`, `Int8`, `Int16`, `Int32`, `Int64`,
`Uint8`, `Uint16`, `Uint32`, `Uint64`.
### Message Body Types
`Message.Body` implements `MessageBody` and is one of:
- `*OpenResponse`
- `*CloseResponse`
- `*GetListResponse` (contains meter readings)
- `*AttentionResponse`
- `*GetProcParameterResponse`
- `*GetProfileListResponse`
- `*GetProfilePackResponse`
## Testing
### Run all tests
```bash
go test ./...
```
### Integration tests only
The integration tests decode 37 real meter captures and compare the output
against golden files generated by the C reference implementation
([libsml](https://github.com/volkszaehler/libsml)):
```bash
go test -run TestGoldenFiles -v
```
Test data comes from [devZer0/libsml-testing](https://github.com/devZer0/libsml-testing)
and covers 15+ meter models from manufacturers including EMH, DZG, Holley,
ISKRA, EasyMeter, eBZ, ITRON, and DrNeuhaus.
### Fuzz tests
```bash
go test -fuzz FuzzDecode -fuzztime 30s
go test -fuzz FuzzTransportReader -fuzztime 30s
```
## Regenerating Golden Files
The golden files in `testdata/golden/` are generated by piping each binary
capture through the C `sml_server` example program from
[volkszaehler/libsml](https://github.com/volkszaehler/libsml).
### Build sml_server from source
```bash
git clone https://github.com/volkszaehler/libsml /tmp/libsml
cd /tmp/libsml
make
# On macOS the shared library target may fail — only the static library
# is needed. Link sml_server manually:
cc -o sml_server examples/sml_server.c -Isml/include sml/lib/libsml.a -lm
```
### Generate golden files
```bash
for f in testdata/*.bin; do
name=$(basename "$f" .bin)
/tmp/libsml/sml_server - < "$f" > "testdata/golden/${name}.txt" 2>&1
done
```
The golden file format is one line per `GetListResponse` list entry:
```
OBIS#value#unit
```
where OBIS is `A-B:C.D.E*F`, value is the scaled numeric reading (or hex-formatted octet string), and unit is the DLMS unit name.
### Using Docker (no local C toolchain)
```bash
docker run --rm -v "$(pwd)":/work -w /work gcc:latest bash -c '
apt-get update -qq && apt-get install -y -qq uuid-dev > /dev/null 2>&1
git clone --quiet https://github.com/volkszaehler/libsml /tmp/libsml
cd /tmp/libsml && make > /dev/null 2>&1
for f in /work/testdata/*.bin; do
name=$(basename "$f" .bin)
./examples/sml_server - < "$f" > "/work/testdata/golden/${name}.txt" 2>&1
done
'
```
## License
Apache License 2.0 — see [LICENSE](LICENSE).
## Acknowledgements
- [volkszaehler/libsml](https://github.com/volkszaehler/libsml) — C reference implementation
- [devZer0/libsml-testing](https://github.com/devZer0/libsml-testing) — Real meter captures used for integration testing