The history had to be rewritten today: plant values and confidential document names sat in commit messages even after the working tree was cleaned (61c7d0c), and several messages ran longer than the diff they described. Writing the rule down so it doesn't happen again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CBgcWynhMWssnCEV2xM5FU
vibrato
Data collector for condition monitoring of a vibratory conveyor. It reads sensor values over IO-Link (an ifm AL1352 master, 8 ports) and Modbus RTU (an Eastron energy meter), and writes them as raw daily CSV files for a reporting tool (Power BI) to read.
vibrato does not evaluate anything — no thresholds, no alarms, no aggregation at write time. Raw rows can always be turned into aggregates later; the reverse is never true, and which aggregate will actually matter isn't known yet. So vibrato collects and stores; the analysis — reference bands, thresholds, charts — happens downstream against the same files, in whatever tool is chosen for that, deliberately not decided here.
Data sources
- Six Balluff BCM0003 vibration sensors (bearings, trough sides, drive motor, counterweight) — time-domain statistics cyclically via a configurable Custom Process Data Profile, the rest acyclically over ISDU; frequency bands acyclically; full spectra over IO-Link BLOB Transfer (transport implemented, payload decoding still pending — see "Status").
- One Balluff BCM0001 ambient-temperature sensor (contact temperature, the
reference for every other temperature channel) — a different device on
the wire despite the physical resemblance to the BCM0003; see
internal/decode's package doc. - One ifm DI6004 speed/runtime-hours sensor.
- One Eastron SDM630MCT V2 energy meter over Modbus RTU.
Storage format
One CSV file per day per data set, UTC-dated, completed days compressed to
.csv.gz. Wide format for most data sets, long format for spectra. Keys
instead of free text, with a catalog (internal/catalog) carrying the
display names and units — written out as CSV into <data_dir>/catalog/ on
every start, next to the data it describes. No database — a documented, open
format any successor can read without tooling or a running service.
<data_dir>/
catalog/ measurement-point/quantity lookup CSVs
status/status_<date>.csv port-watch snapshot, once a minute
devices/devices_<date>.csv which sensor sat where, once a day
ambient/ambient_<date>.csv
power/power_<date>.csv
speed/speed_<date>.csv
vibration/<CODE>/vibration_<CODE>_<date>.csv
vibration_acyclic/<CODE>/vibration_acyclic_<CODE>_<date>.csv
spectra/<CODE>/spectra_<CODE>_<date>.csv long format, one row per band
<CODE> is one of the six vibration locations (config/example.toml,
[[location]]); those three data sets are split per location because each
location's profile gives it its own column set, which would otherwise collide
in one file. A header change within a day gets a numeric suffix
(..._<date>_2.csv) rather than breaking the existing file's columns.
state_dir (default /var/lib/vibrato/state) holds vibrato's own operating
state — currently just the runtime-hours counter — deliberately outside
data_dir, since Power BI reads data_dir as a whole tree.
Layout
cmd/vibrato/ the collector service
cmd/vibrato-probe/ commissioning tool: dump raw process data, ISDU
parameters, or full-spectrum BLOBs from real
hardware
cmd/vibrato-sim/ dependency-free simulator (IoT Core HTTP + Modbus
TCP) for development without hardware
internal/config/ TOML configuration
internal/catalog/ measurement-point and quantity catalogs
internal/iolink/ AL1352 IoT Core client (JSON/HTTP), incl. generic
BLOB Transfer
internal/decode/ device-specific byte layouts (BCM0003, BCM0001,
DI6004)
internal/modbus/ Eastron SDM630 client (Modbus RTU)
internal/sink/ CSV writer: daily rotation, compression,
per-location partitioning
internal/scheduler/ per-channel-group polling
internal/portwatch/ read-failure tracking, feeds the "status" data set
internal/runstate/ tracks whether the machine is running
internal/runtimehours/ monotonic persistence of the runtime-hours counter
internal/clocksync/ system clock sync check (platform-specific)
config/example.toml generic configuration template, no plant reference
deploy/vibrato.service systemd unit — the Linux option, not a decision that
Linux is the target platform; that choice is still open
deploy/vibrato-windows-install.ps1
registers vibrato as a Windows service (incl.
its Event Log source) — the Windows option,
equally not a requirement
Build
go build ./...
Static binaries, no external runtime dependencies. Go 1.27; the module is
git.streifling.com/jason/vibrato.
Configure
Copy config/example.toml (a generic template — no plant reference) to a
config.local.toml or similar (anything matching *.local.toml is
git-ignored) and fill in the real addresses, port mapping, and sample
rates. Nothing plant-specific belongs in the repository.
Run
Against the bundled simulator, no hardware needed:
go build -o bin/ ./...
bin/vibrato-sim &
bin/vibrato -config config/example.toml
vibrato-sim takes -http, -modbus, -speed-port, and -ambient-port
to match a given configuration; see cmd/vibrato-sim -h.
Against real hardware, first confirm the wire format with
vibrato-probe: -isdu index[.subindex] reads any ISDU parameter raw, and
-full-spectrum (with -full-spectrum-axis) captures a full-spectrum BLOB
and dumps its raw bytes. Neither writes to the data directory — they exist
to let a decoder table be checked against a real device in minutes, not to
run the collector.
Test
gofmt -l .
go vet ./...
go test -race ./...
Status
Collecting real data end to end: all six vibration locations, ambient
temperature, speed/runtime-hours, and the energy meter read their real
sources — no placeholders anywhere in the code. Frequency bands and the
acyclic time-domain statistics that don't fit the cyclic profile are both
implemented. Full-spectrum acquisition over BLOB Transfer is implemented
as far as the transport goes (vibrato-probe -full-spectrum); decoding
the payload into frequency lines isn't — its internal byte layout isn't
documented in any specification obtained so far, and a wrong guess would
silently mislabel every value rather than fail loudly, so it stays
undecoded until a captured BLOB or further documentation settles the
format. The target operating system for the collector itself is not yet
decided; the code is kept platform-independent accordingly.
License
MIT — see LICENSE. The plant this runs against is not public: no plant
or company names, addresses, credentials, measured values, or manufacturer
document text belong in this repository.