Helper functions for downloading and processing precision dairy monitoring data — feed/water intake, weather, and heat stress:
download_data()— authenticates with the C-Lock Inc. portal API and downloads weight (SmartScale) or intake (SmartFeed) data, as daily summaries or individual visits.download_weather()— pulls daily weather data (temperature, humidity, wind, solar radiation, precipitation, soil moisture/temperature, elevation) for a given latitude/longitude from NASA POWER, and computes the Temperature-Humidity Index (THI, NRC 1971).summarise_intakes()— aggregates intake visits to one row per animal per day: visit count, total/mean/min/max intake, and the behavioral traits from Dressler et al. (2023) (time per session, session interval, intake rate), joined with matching dailyT2M/THIfromdownload_weather(). Also separates out negative-intake visits for review.summarise_bw()— aggregates weight visits to one row per animal per day, applying the company's front-feet-to-full-body correction factor (1.76) and flagging implausible visit weights as outliers for review.process_VRfiles()/mixers()— re-exported from feedeffir; process feed bin data from the Hokofarm Group Insentec (RIC) system.process_VRfiles()processes a single day'sVR######.DATfile;mixers()batch-processes a folder of them against a treatment-group roster for daily/weekly/trial diet-mixing summaries. See?feedeffir::process_VRfiles/?feedeffir::mixersfor full parameter documentation.
# install.packages("remotes")
remotes::install_github("GMBog/smarttools")This also installs feedeffir
(declared as a Remotes: dependency) for process_VRfiles()/mixers().
Requires a C-Lock portal account. Set credentials as environment variables
(e.g. in ~/.Renviron) so they never need to be hardcoded in scripts:
CLOCK_USER=your_portal_username
CLOCK_PASS=your_portal_password
library(smarttools)
unit <- "11228,11229,11230,11231,11232,11233"
start_date <- "2026-07-13"
end_date <- as.character(Sys.Date())
# Individual visits
visits_df <- download_data(t = "weight", d = "visits", unit = unit,
start_date = start_date, end_date = end_date)
# Daily summaries
daily_df <- download_data(t = "weight", d = "daily", unit = unit,
start_date = start_date, end_date = end_date)
intake_visits_df <- download_data(t = "intake", d = "visits", unit = unit,
start_date = start_date, end_date = end_date)
intake_daily_df <- download_data(t = "intake", d = "daily", unit = unit,
start_date = start_date, end_date = end_date)No authentication required.
library(smarttools)
weather_df <- download_weather(
lat = 38.53706526753634,
lon = -121.75995771946077,
start_date = "2026-07-21",
end_date = as.character(Sys.Date())
)Returns one row per day with the requested weather parameters, site
elevation, and THI/THI_max computed automatically when temperature and
humidity are among the requested parameters. Default parameters:
| Code | Description |
|---|---|
T2M / T2M_MAX / T2M_MIN |
Air temperature at 2m (mean/max/min, °C) |
RH2M |
Relative humidity at 2m (%) |
WS2M |
Wind speed at 2m (m/s) |
ALLSKY_SFC_SW_DWN |
Surface solar radiation (MJ/m²/day) |
PRECTOTCORR |
Precipitation (mm/day) |
GWETTOP / GWETROOT |
Soil wetness, surface / root zone (fraction) |
TS |
Earth skin (soil surface) temperature (°C) |
library(smarttools)
vdf_WI <- download_data(
t = "intake", d = "visits",
unit = "11228,11229,11230,11231,11232,11233",
start_date = "2026-07-21",
end_date = as.character(Sys.Date())
)
result <- summarise_intakes(
vdf_WI,
lat = 38.53706526753634,
lon = -121.75995771946077
)
daily_WI <- result$daily
negative_intakes <- result$negative_intakesDrops visits with a blank RFID, and visits that aren't a valid session
per Dressler et al. (2023) (Duration > 5s and < 3600s). Visits with a
negative IntakeKG are excluded from the daily summary and returned
separately in negative_intakes for review. daily has one row per
RFID/Date:
| Column | Description |
|---|---|
T2M / THI |
From download_weather() for that date; omitted if the weather download fails |
DailySession |
Number of valid visits |
DailyWaterIntakeKG |
Total daily intake |
SessionSizeKG / MinSessionSizeKG / MaxSessionSizeKG |
Mean/min/max intake per visit |
TimePerSessionSec |
Mean visit duration |
SessionIntervalMin |
Mean time between consecutive same-day visits (NA if only one visit that day) |
IntakeRateGperSec |
Each visit's own rate (IntakeKG*1000/Duration), averaged across the day |
library(smarttools)
vdf_BW <- download_data(
t = "weight", d = "visits",
unit = "11228,11229,11230,11231,11232,11233",
start_date = "2026-07-21",
end_date = as.character(Sys.Date())
)
result <- summarise_bw(vdf_BW)
daily_bw <- result$daily
outliers <- result$outliersDrops visits with a blank RFID. Each WeightKG reading only captures the
animal's front two feet on the scale, so BW_kg is the mean of that day's
visit weights (RawWeightKG) multiplied by 1.76 to estimate full body
weight. Visits with an implausible WeightKG are flagged as outliers using
Tukey's boxplot rule, with quartiles computed per RFID across its full
set of visits in df (not per day, since a single day often has too few
visits to estimate quartiles reliably); flagged visits are excluded from
daily and returned separately in outliers for review. daily has one
row per RFID/Date: n, RawWeightKG, BW_kg, and SDBW_kg
(standard deviation, same 1.76 scaling as BW_kg; NA if only one visit
that day).
library(smarttools)
# Single day's VR file
intakes <- process_VRfiles(
exp = "TRIAL01",
VRfile = "path/to/VR260721.DAT",
bins = seq(1, 32),
save_dir = "output/"
)
# Batch-process a folder of VR files against a treatment-group roster
result <- mixers(
start_date = "2026-07-13",
VRfolder = "path/to/vr_files/",
groups = "path/to/groups.csv",
bins = seq(1, 32)
)More examples are in examples/.
MIT