HiveControl Data Collection Scripts Documentation Version 2.10 - 2026-01-17
- Overview
- Script Architecture
- Main Orchestrator
- Sensor Scripts
- Utility Scripts
- Configuration System
- Error Handling
- Security Features
HiveControl uses a collection of bash scripts to gather sensor data from physical hardware and store it in the SQLite database. The scripts run periodically via cron and are designed for robustness, error handling, and modularity.
Key Design Principles:
- Fail-Fast: Scripts exit immediately on errors (
set -eo pipefail) - Modularity: Separate scripts for each sensor type
- Error Logging: All errors logged to database or syslog
- Security: SQL/XML injection prevention, credential protection
- Portability: Configurable HOMEDIR for flexible installation paths
┌─────────────────────────────────────────────┐
│ cron (every 5 minutes) │
└──────────────────┬──────────────────────────┘
│
v
┌─────────────────────────────────────────────┐
│ currconditions.sh │
│ (Main Orchestrator) │
│ - Load configuration │
│ - Call sensor scripts │
│ - Aggregate data │
│ - Store in database │
│ - Send to Hivetool.org (optional) │
└──────┬──────────┬──────────┬────────────────┘
│ │ │
v v v
┌───────┐ ┌───────┐ ┌───────┐
│Weight │ │ Temp │ │Weather│
│Scripts│ │Scripts│ │Scripts│
└───────┘ └───────┘ └───────┘
/home/HiveControl/scripts/
├── system/
│ ├── currconditions.sh # Main data collection orchestrator
│ └── ...
├── weight/
│ ├── getweight.sh # Weight collection dispatcher
│ ├── hx711.sh # HX711 load cell reader
│ ├── hx711calibrate.sh # HX711 calibration wizard
│ ├── phidget1046.sh # Phidget bridge interface
│ └── cpw200plus.sh # Industrial scale reader
├── temp/
│ ├── gettemp.sh # Temperature dispatcher
│ ├── broodminder.sh # BroodMinder BLE sensor
│ ├── broodtemp.sh # Internal hive temp
│ ├── dht22.sh # DHT22/AM2302 sensor
│ ├── sht31-d.sh # SHT31-D I2C sensor
│ ├── bme680.sh # BME680 multi-sensor
│ ├── bme280.sh # BME280 sensor
│ └── temperhum.sh # USB Temper sensor
├── weather/
│ ├── getWeatherFlowLocal.sh # WeatherFlow Tempest (UDP)
│ ├── getAmbientWeather.sh # Ambient Weather (API)
│ ├── getOurWeather.sh # OurWeather stations
│ └── getWXunderground.sh # Weather Underground (XML)
├── lux/
│ ├── tsl2591.sh # TSL2591 light sensor
│ └── tsl2561.sh # TSL2561 light sensor
├── air/
│ └── getair.sh # PurpleAir quality data
├── beecounter/
│ └── beecounter.sh # Optical bee counter
├── data/
│ ├── hiveconfig.sh # Configuration loader
│ ├── check.inc # Data validation functions
│ ├── logger.inc # Logging functions
│ └── cloud.inc # Hivetool.org integration
└── hiveconfig.inc # Generated config variables
Purpose: Main data collection script that orchestrates all sensor readings
Version: 2026011705
Location: /home/HiveControl/scripts/system/currconditions.sh
Execution: Every 5 minutes via cron
Flow:
1. Load configuration from database
2. Check if collection is paused (RUN variable)
3. Call sensor collection functions:
- get_hive_weight()
- get_hive_temp()
- get_bee_count()
- get_weather()
- get_lux()
- get_air_quality()
4. Aggregate all data
5. Insert into SQLite database
6. Send to Hivetool.org (if enabled)
7. Exit with status codeKey Features:
Strict Mode (with compatibility):
set -eo pipefail # Exit on error, catch pipe failures
# Note: -u (unbound variables) not used for check.inc compatibilitySQL Injection Prevention:
function sql_escape() {
echo "${1//\'/\'\'}" # Double single quotes
}
# Usage:
HIVEWEIGHT_ESC=$(sql_escape "$HIVEWEIGHT")
sqlite3 "$DB" "INSERT INTO allhivedata (hiveweight) VALUES ('$HIVEWEIGHT_ESC')"XML Injection Prevention:
function xml_escape() {
local str="$1"
str="${str//&/&}"
str="${str//</<}"
str="${str//>/>}"
str="${str//\"/"}"
str="${str//\'/'}"
echo "$str"
}Error Handling:
function get_hive_weight() {
if [ "$ENABLE_HIVE_WEIGHT_CHK" = "yes" ]; then
local weight_output
if ! weight_output=$("$HOMEDIR/scripts/weight/getweight.sh" 2>&1); then
echo "ERROR: Failed to get weight data: $weight_output"
HIVEWEIGHT="null"
return 1
fi
# Parse and validate data
fi
}
# Call with || true to continue on failure
get_hive_weight || true
get_hive_temp || true
get_weather || truePerformance Optimization:
# OLD: Multiple awk calls
TEMP=$(echo "$output" | awk '{print $1}')
HUM=$(echo "$output" | awk '{print $2}')
DEW=$(echo "$output" | awk '{print $3}')
# NEW: Single read operation
read -r TEMP HUM DEW <<< "$output"Database Operations:
# Check database exists
if [ ! -f "$DATABASE" ]; then
echo "ERROR: Database not found: $DATABASE"
exit 1
fi
# Insert with error checking
if ! sqlite3 "$DATABASE" "INSERT INTO allhivedata (...) VALUES (...)"; then
echo "ERROR: Database insert failed"
loglocal "$DATE" MAIN ERROR "Failed to insert data into database"
exit 1
fi
# Use last_insert_rowid() for efficiency
ROWID=$(sqlite3 "$DATABASE" "SELECT last_insert_rowid();")Fixed in currconditions.sh:
- SQL injection in database inserts
- XML injection in Hivetool.org data sharing
- Hardcoded paths replaced with $HOMEDIR variable
- Comprehensive error handling added
- All variable expansions quoted
- Password exposure in command line (use .netrc instead)
Before (Vulnerable):
# SQL injection risk
sqlite3 "$DB" "INSERT INTO logs VALUES (\"$DATE\",\"$PROGRAM\",\"$TYPE\",\"$MESSAGE\")"
# XML injection risk
echo "<temp>$TEMP</temp>" > file.xml
# Unquoted variables
cd $HOMEDIRAfter (Secure):
# SQL injection prevented
MESSAGE_ESC=$(sql_escape "$MESSAGE")
sqlite3 "$DB" "INSERT INTO logs VALUES (\"$DATE\",\"$PROGRAM\",\"$TYPE\",\"$MESSAGE_ESC\")"
# XML injection prevented
TEMP_XML=$(xml_escape "$TEMP")
echo "<temp>$TEMP_XML</temp>" > file.xml
# Quoted variables
cd "$HOMEDIR"Purpose: Dispatcher for weight sensor types
Flow:
- Read
WEIGHT_TYPEfrom configuration - Call appropriate sensor script
- Return:
RAW_WEIGHT NET_WEIGHT
Supported Sensors:
hx711→ hx711.shphidget→ phidget1046.shcpw200→ cpw200plus.sh
Purpose: Read HX711 load cell amplifier
Requirements:
- Python 3 with pigpio library
- HX711 connected to GPIO pins
- Calibration values (slope, zero) from database
Operation:
# Get calibration from database
SLOPE=$(sqlite3 "$DB" "SELECT hx711_slope FROM hiveconfig WHERE id=1;")
ZERO=$(sqlite3 "$DB" "SELECT hx711_zero FROM hiveconfig WHERE id=1;")
# Read sensor (Python script)
OUTPUT=$(python3 "$HOMEDIR/scripts/weight/hx711.py" --dout "$DOUT_PIN" --pd_sck "$SCK_PIN")
# Calculate weight
RAW_WEIGHT=$(echo "$OUTPUT" | awk '{print $1}')
NET_WEIGHT=$(echo "scale=2; ($RAW_WEIGHT - $ZERO) * $SLOPE" | bc)
echo "$RAW_WEIGHT $NET_WEIGHT"Retry Logic:
# Try up to 3 times if reading is zero
for i in {1..3}; do
read_sensor
if [ "$RAW_WEIGHT" != "0" ]; then
break
fi
sleep 1
donePurpose: Interactive calibration wizard
Steps:
- Read sensor with no weight (get zero point)
- Place known weight on scale
- Read sensor with known weight
- Calculate slope:
slope = known_weight / (reading - zero) - Store in database
Purpose: Dispatcher for temperature sensor types
Flow:
- Read
TEMP_TYPEfrom configuration - Call appropriate sensor script
- Return:
TEMP_F TEMP_C HUMIDITY DEW_POINT
Supported Sensors:
dht22→ dht22.shsht31d→ sht31-d.shbme680→ bme680.shbme280→ bme280.shbroodminder→ broodminder.shtemper→ temperhum.sh
Purpose: Read DHT22/AM2302 humidity/temperature sensor
Requirements:
- Python with pigpio library
- DHT22 connected to GPIO pin
Operation:
# Read sensor using pigpio
OUTPUT=$(python3 "$HOMEDIR/scripts/temp/dht22.py" --gpio "$GPIO_PIN")
# Parse: "temp,humidity"
IFS=',' read -r TEMP_C HUMIDITY <<< "$OUTPUT"
# Convert to Fahrenheit
TEMP_F=$(echo "scale=1; ($TEMP_C * 9/5) + 32" | bc)
# Calculate dew point
DEW=$(calculate_dewpoint "$TEMP_C" "$HUMIDITY")
echo "$TEMP_F $TEMP_C $HUMIDITY $DEW"Error Handling:
# Check for lock file (prevents concurrent access)
LOCKFILE="/tmp/dht22.lock"
if [ -f "$LOCKFILE" ]; then
echo "ERROR: Sensor locked by another process"
exit 1
fi
# Create lock
touch "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
# Read sensor
# ... (lock automatically removed on exit)Purpose: Read SHT31-D I2C sensor (high precision)
Requirements:
- Python with Adafruit SHT31D library
- I2C enabled (
sudo raspi-config) - Sensor on I2C bus (default address 0x44)
Purpose: Read BME680 sensor (temp/humidity/pressure/gas)
Returns: TEMP_F TEMP_C HUMIDITY PRESSURE GAS_RESISTANCE
Purpose: Read WeatherFlow Tempest weather station via UDP broadcast
Version: 7
Operation:
# Listen for UDP broadcasts on port 50222
# Parse JSON with jq
# Extract: temp, humidity, pressure, rain, wind, solar, UV
# Calculate dewpoint and heat index
# Return CSV formatError Handling (Version 7 fixes):
# Suppress error messages
TEMP=$(echo "$JSON" | jq -r '.temp' 2>/dev/null)
# Set defaults before math operations
TEMP="${TEMP:-0}"
# Validate before calculations
if [ -z "$TEMP" ] || [ "$TEMP" = "null" ]; then
# Return null CSV instead of failing
echo "null,null,null,null,null,null,null"
exit 0
fiPurpose: Fetch data from Ambient Weather API
Requirements:
- API key and Application key from Ambient Weather
- Internet connection
- curl installed
Operation:
API_KEY=$(sqlite3 "$DB" "SELECT ambient_api_key FROM siteconfig;")
APP_KEY=$(sqlite3 "$DB" "SELECT ambient_app_key FROM siteconfig;")
MAC=$(sqlite3 "$DB" "SELECT ambient_mac FROM siteconfig;")
URL="https://api.ambientweather.net/v1/devices/$MAC"
URL+="?apiKey=$API_KEY&applicationKey=$APP_KEY&limit=1"
JSON=$(curl -s "$URL")
# Parse JSON and return CSVPurpose: Read OurWeather stations
Note: Specific to jhalt/Duncan's weather station setup
Purpose: Weather Underground XML feed (legacy)
Note: IBM deprecated API access; XML still available
Purpose: Read TSL2591 light sensor (high dynamic range)
Returns: LUX value
Requirements:
- Python with Adafruit TSL2591 library
- I2C enabled
- Sensor on I2C bus
Purpose: Read TSL2561 light sensor
Returns: LUX value
Purpose: Fetch PurpleAir air quality data
Operation:
SENSOR_ID=$(sqlite3 "$DB" "SELECT purpleair_id FROM siteconfig;")
URL="https://www.purpleair.com/json?show=$SENSOR_ID"
JSON=$(curl -s "$URL")
# Extract PM2.5 and PM10 values
PM25=$(echo "$JSON" | jq -r '.results[0].PM2_5Value')
PM10=$(echo "$JSON" | jq -r '.results[0].PM10Value')
echo "$PM25 $PM10"Purpose: Read optical bee counter for flight activity
Returns: BEES_IN BEES_OUT
Requirements:
- Beecounter hardware installed
- Computer vision libraries
- Camera module
Purpose: Load configuration from database and export as shell variables
Location: /home/HiveControl/scripts/data/hiveconfig.sh
Operation:
# Query database
RESULT=$(sqlite3 "$DATABASE" "SELECT * FROM hiveconfig WHERE id=1;")
# Parse fields (pipe-delimited)
IFS='|' read -r ID HIVENAME HIVEAPI YARDID CITY STATE \
COUNTRY LAT LON VERSION TIMEZONE SHARE_HIVETOOL \
HT_USER HT_PASS HT_URL ... <<< "$RESULT"
# Export variables
export HIVENAME="$HIVENAME"
export LATITUDE="$LAT"
export LONGITUDE="$LON"
# ... (all config variables)
# Write to hiveconfig.inc for sourcing
cat > "$HOMEDIR/scripts/hiveconfig.inc" <<EOF
HIVENAME="$HIVENAME"
LATITUDE="$LAT"
LONGITUDE="$LON"
# ... (all variables)
EOFUsed by: All scripts that need configuration
Purpose: Data validation functions
Key Function:
function check() {
local var_name="$1"
local value="${!var_name}"
# Check for invalid values
if [ -z "$value" ] || [ "$value" = "----" ] || [ "$value" = "null" ]; then
eval "$var_name=\"null\""
return 1
fi
# Check for negative numbers
if [[ "$value" =~ ^- ]]; then
eval "$var_name=\"null\""
return 1
fi
return 0
}
# Usage:
TEMP=25.5
check TEMP # Returns 0 (valid)
TEMP="----"
check TEMP # Sets TEMP="null", returns 1Purpose: Logging functions
Functions:
function loglocal() {
local date="$1"
local program="$2"
local type="$3"
local message="$4"
# Escape for SQL
message=$(sql_escape "$message")
# Insert into logs table
sqlite3 "$DATABASE" "INSERT INTO logs (date,program,type,message) \
VALUES ('$date','$program','$type','$message')"
}
function logsyslog() {
local message="$1"
logger -t "HiveControl" "$message"
}
# Usage:
loglocal "$DATE" "WEIGHT" "INFO" "Weight reading: $WEIGHT lbs"
logsyslog "Data collection completed successfully"Purpose: Hivetool.org data sharing
Functions:
function send_to_hivetool() {
if [ "$SHARE_HIVETOOL" != "yes" ]; then
return 0
fi
# Build XML payload
local xml="<data>"
xml+="<hive_id>$(xml_escape "$HIVEAPI")</hive_id>"
xml+="<datetime>$(xml_escape "$DATE_UTC")</datetime>"
xml+="<temp>$(xml_escape "$HIVETEMPF")</temp>"
xml+="<weight>$(xml_escape "$HIVEWEIGHT")</weight>"
# ... (all fields)
xml+="</data>"
# POST to Hivetool.org
# SECURITY NOTE: Use .netrc file for credentials, not command line
curl -s --netrc-file "$HOME/.netrc" \
-X POST \
-d "$xml" \
"https://hivetool.org/api/upload"
}Security Improvement: Instead of passing credentials in URL (visible in process list):
# OLD (INSECURE):
curl "https://user:pass@hivetool.org/api/upload"
# NEW (SECURE):
# Create ~/.netrc file:
# machine hivetool.org
# login your_username
# password your_password
#
# chmod 600 ~/.netrc
curl --netrc-file ~/.netrc "https://hivetool.org/api/upload"Fail-Fast with Recovery:
# Individual sensors can fail without stopping collection
get_hive_weight || true
get_hive_temp || true
get_weather || true
# But critical failures exit immediately
if ! sqlite3 "$DB" "INSERT INTO allhivedata ..."; then
loglocal "$DATE" MAIN ERROR "Database insert failed"
exit 1
fiRetry Logic:
# Retry sensor readings up to 3 times
function read_with_retry() {
local max_retries=3
local attempt=1
while [ $attempt -le $max_retries ]; do
if OUTPUT=$(sensor_read 2>&1); then
echo "$OUTPUT"
return 0
fi
echo "Retry $attempt/$max_retries" >&2
((attempt++))
sleep 1
done
return 1
}Default Values:
# Always provide defaults for critical variables
TIMEZONE="${TIMEZONE:-UTC}"
HOMEDIR="${HOMEDIR:-/home/HiveControl}"
DATABASE="${DATABASE:-$HOMEDIR/data/hive-data.db}"Error Logging:
# Log all errors to database
if ! result=$(command 2>&1); then
loglocal "$DATE" "SENSOR" "ERROR" "Failed: $result"
return 1
fiAlways escape user-controlled data:
# Get input
MESSAGE="User's input with 'quotes'"
# Escape for SQL
MESSAGE_ESC=$(sql_escape "$MESSAGE")
# Safe to use in query
sqlite3 "$DB" "INSERT INTO logs (message) VALUES ('$MESSAGE_ESC')"Escape XML special characters:
TEMP="25°F"
TEMP_XML=$(xml_escape "$TEMP")
echo "<temperature>$TEMP_XML</temperature>"
# Output: <temperature>25°F</temperature>Use .netrc for passwords:
# ~/.netrc (chmod 600)
machine hivetool.org
login your_username
password your_password
# In script:
curl --netrc-file ~/.netrc https://hivetool.org/api/uploadNever pass passwords on command line:
# BAD (visible in process list):
curl "https://user:password@site.com"
# GOOD:
curl --netrc-file ~/.netrc https://site.comCheck file existence:
if [ ! -f "$CONFIG_FILE" ]; then
echo "ERROR: Config file not found: $CONFIG_FILE"
exit 1
fiUse absolute paths:
# BAD (relative paths can be hijacked):
source config.inc
# GOOD:
source "$HOMEDIR/scripts/config.inc"Validate permissions:
# Check database is writable
if [ ! -w "$DATABASE" ]; then
echo "ERROR: Database not writable: $DATABASE"
exit 1
fiDefault Schedule:
# Run data collection every 5 minutes
*/5 * * * * /home/HiveControl/scripts/system/currconditions.sh >> /var/log/hivecontrol.log 2>&1
# Run system maintenance daily
0 2 * * * /home/HiveControl/scripts/system/maintenance.shInstallation:
# During install.sh
crontab -l > /tmp/cron.bak 2>/dev/null || true
echo "*/5 * * * * /home/HiveControl/scripts/system/currconditions.sh" | crontab -# Run main script manually
sudo /home/HiveControl/scripts/system/currconditions.sh
# Test individual sensor
sudo /home/HiveControl/scripts/weight/getweight.sh
# Output: 12345 98.5
# Test with debugging
bash -x /home/HiveControl/scripts/system/currconditions.sh# Check database was updated
sqlite3 /home/HiveControl/data/hive-data.db \
"SELECT * FROM allhivedata ORDER BY date DESC LIMIT 1;"
# Check logs
sqlite3 /home/HiveControl/data/hive-data.db \
"SELECT * FROM logs ORDER BY date DESC LIMIT 10;"
# Check cron is running
sudo systemctl status cron
# Check for errors in logs
grep -i error /var/log/hivecontrol.log# Check cron is active
sudo systemctl status cron
# Check crontab entry
crontab -l | grep currconditions
# Check script permissions
ls -la /home/HiveControl/scripts/system/currconditions.sh
# Should be executable: -rwxr-xr-x
# Make executable if needed
chmod +x /home/HiveControl/scripts/system/currconditions.sh# Test sensor script directly
sudo /home/HiveControl/scripts/weight/hx711.sh
# Look for error messages
# Check Python dependencies
python3 -c "import pigpio; print('OK')"
# Check I2C is enabled (for I2C sensors)
sudo i2cdetect -y 1
# Check GPIO permissions
sudo usermod -a -G gpio www-data# Check database exists
ls -la /home/HiveControl/data/hive-data.db
# Check permissions
sudo chown www-data:www-data /home/HiveControl/data/hive-data.db
# Test database
sqlite3 /home/HiveControl/data/hive-data.db "PRAGMA integrity_check;"# Test WeatherFlow UDP listener
sudo tcpdump -i any -n port 50222
# Should see packets if station is broadcasting
# Test Ambient Weather API
curl "https://api.ambientweather.net/v1/devices" \
-G --data-urlencode "apiKey=YOUR_KEY" \
--data-urlencode "applicationKey=YOUR_APP_KEY"Version 2026011705 (2026-01-17)
- Fixed WeatherFlow error handling
- Added 2>/dev/null to bc and jq commands
- Return null CSV when no weather data
Version 2026011704 (2026-01-17)
- Fixed getWeatherFlowLocal.sh error cascade
- Added early exit with null values when no data
Version 2026011703 (2026-01-17)
- Fixed script termination on sensor failures
- Added || true to sensor function calls
Version 2026011702 (2026-01-17)
- Fixed unbound variable errors
- Moved set -eo pipefail after config loading
Version 2026011701 (2026-01-17)
- Major security and performance refactoring
- Fixed SQL injection vulnerabilities
- Fixed XML injection vulnerabilities
- Added comprehensive error handling
- Refactored into modular functions
- Optimized parsing (reduced awk calls)
- Fixed all hardcoded paths