Compare commits

...

3 Commits

Author SHA1 Message Date
78ade29342 Example ENV 2025-10-05 15:26:27 +01:00
9ad012f88c Adding README 2025-10-05 15:24:45 +01:00
7e7889b157 InfluxDB support and dockerisation 2025-10-05 15:22:32 +01:00
7 changed files with 465 additions and 11 deletions

36
.env.example Normal file
View File

@@ -0,0 +1,36 @@
# Meteostick Logger Configuration
# Copy this file to .env and customize for your setup
# Meteostick Hardware Configuration
METEOSTICK_PORT=/dev/ttyACM0
METEOSTICK_BAUDRATE=115200
METEOSTICK_FREQUENCY=EU
METEOSTICK_RF_SENSITIVITY=90
# Weather Station Channels
# Set to 1-8 for active channels, 0 to disable
METEOSTICK_ISS_CHANNEL=1
METEOSTICK_ANEMOMETER_CHANNEL=0
METEOSTICK_LEAF_SOIL_CHANNEL=0
METEOSTICK_TH1_CHANNEL=0
METEOSTICK_TH2_CHANNEL=0
# Rain Bucket Configuration
# 0 = 0.01 inch buckets (US)
# 1 = 0.2 mm buckets (Metric)
METEOSTICK_RAIN_BUCKET=1
# InfluxDB Configuration
METEOSTICK_ENABLE_INFLUXDB=true
METEOSTICK_INFLUXDB_HOST=localhost
METEOSTICK_INFLUXDB_PORT=8086
METEOSTICK_INFLUXDB_DB=weather
METEOSTICK_INFLUXDB_MEASUREMENT=meteostick
# InfluxDB Authentication
# Generate a token in InfluxDB UI: Data -> Tokens -> Generate Token
METEOSTICK_INFLUXDB_TOKEN=your_influxdb_token_here
# Optional: Legacy username/password authentication (deprecated)
# METEOSTICK_INFLUXDB_USER=username
# METEOSTICK_INFLUXDB_PASS=password

15
.gitignore vendored
View File

@@ -214,3 +214,18 @@ __marimo__/
# Streamlit
.streamlit/secrets.toml
# Environment files (contains sensitive tokens)
.env
.env.local
.env.*.local
# Docker
.dockerignore
# IDE
.vscode/
.idea/
*.swp
*.swo
*~

191
README.md Normal file
View File

@@ -0,0 +1,191 @@
# Meteostick Logger
A Python application for reading weather data from Davis Instruments weather stations via a Meteostick USB receiver. The application can display real-time weather data and optionally send it to InfluxDB for storage and visualization.
## Features
- Real-time weather data collection from Davis weather stations
- Support for multiple sensor types (ISS, anemometer, temperature/humidity sensors)
- InfluxDB integration for data storage
- Docker support for easy deployment
- Configurable via environment variables
- Token-based authentication for InfluxDB
## Supported Sensors
- **ISS (Integrated Sensor Suite)**: Temperature, humidity, wind speed/direction, rain
- **Anemometer Kit**: Additional wind measurements
- **Temperature/Humidity Sensors**: Up to 2 additional T/H sensors
- **Leaf/Soil Station**: Soil moisture and temperature sensors
## Requirements
- Python 3.7+
- Meteostick USB device
- Davis Instruments weather station transmitters
- InfluxDB (optional, for data storage)
## Installation
### Python Dependencies
```bash
pip install pyserial influxdb
```
### Docker (Recommended)
1. Clone this repository
2. Create a `.env` file (see configuration below)
3. Build and run with Docker Compose:
```bash
docker-compose up --build
```
## Configuration
### Environment Variables
The application is configured using environment variables. Create a `.env` file in the project root:
```env
# Meteostick Hardware Configuration
METEOSTICK_PORT=/dev/ttyACM0
METEOSTICK_BAUDRATE=115200
METEOSTICK_FREQUENCY=EU
METEOSTICK_RF_SENSITIVITY=90
# Weather Station Channels (1-8, or 0 to disable)
METEOSTICK_ISS_CHANNEL=1
METEOSTICK_ANEMOMETER_CHANNEL=0
METEOSTICK_LEAF_SOIL_CHANNEL=0
METEOSTICK_TH1_CHANNEL=0
METEOSTICK_TH2_CHANNEL=0
# Rain Bucket Type (0=0.01in, 1=0.2mm)
METEOSTICK_RAIN_BUCKET=1
# InfluxDB Configuration
METEOSTICK_ENABLE_INFLUXDB=true
METEOSTICK_INFLUXDB_HOST=localhost
METEOSTICK_INFLUXDB_PORT=8086
METEOSTICK_INFLUXDB_DB=weather
METEOSTICK_INFLUXDB_MEASUREMENT=meteostick
METEOSTICK_INFLUXDB_TOKEN=your_influxdb_token_here
```
### Configuration Options
| Variable | Default | Description |
|----------|---------|-------------|
| `METEOSTICK_PORT` | `/dev/ttyUSB0` | Serial port for Meteostick device |
| `METEOSTICK_BAUDRATE` | `115200` | Serial communication baud rate |
| `METEOSTICK_FREQUENCY` | `EU` | RF frequency: `US`, `EU`, or `AU` |
| `METEOSTICK_RF_SENSITIVITY` | `90` | RF sensitivity (0-125) |
| `METEOSTICK_ISS_CHANNEL` | `1` | ISS transmitter channel (1-8) |
| `METEOSTICK_ANEMOMETER_CHANNEL` | `0` | Anemometer channel (0=disabled, 1-8) |
| `METEOSTICK_LEAF_SOIL_CHANNEL` | `0` | Leaf/soil station channel (0=disabled, 1-8) |
| `METEOSTICK_TH1_CHANNEL` | `0` | T/H sensor 1 channel (0=disabled, 1-8) |
| `METEOSTICK_TH2_CHANNEL` | `0` | T/H sensor 2 channel (0=disabled, 1-8) |
| `METEOSTICK_RAIN_BUCKET` | `1` | Rain bucket type: 0=0.01in, 1=0.2mm |
| `METEOSTICK_ENABLE_INFLUXDB` | `false` | Enable InfluxDB data logging |
| `METEOSTICK_INFLUXDB_HOST` | `localhost` | InfluxDB server hostname |
| `METEOSTICK_INFLUXDB_PORT` | `8086` | InfluxDB server port |
| `METEOSTICK_INFLUXDB_DB` | `weather` | InfluxDB database name |
| `METEOSTICK_INFLUXDB_MEASUREMENT` | `meteostick` | InfluxDB measurement name |
| `METEOSTICK_INFLUXDB_TOKEN` | - | InfluxDB authentication token |
## Usage
### Direct Python Execution
```bash
# Basic usage with console output only
python meteostick_reader.py --port /dev/ttyACM0
# With InfluxDB logging
python meteostick_reader.py --port /dev/ttyACM0 --enable-influxdb --influxdb-token "your_token"
# All options via command line
python meteostick_reader.py \
--port /dev/ttyACM0 \
--freq EU \
--iss-channel 1 \
--enable-influxdb \
--influxdb-host influxdb.example.com \
--influxdb-token "your_token"
```
### Docker Compose
```bash
# Start the service
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the service
docker-compose down
```
## Output Format
### Console Output
The application displays real-time weather data:
```
[2023-12-07 14:30:15] Message #123: I 1 0 48 89 8A 12 34 56 78 9A BC DE 95 1234567 0
Parsed data: {'channel': 1, 'temperature': 18.5, 'humidity': 65.0, 'wind_speed': 2.3, 'wind_dir': 225.0, 'rf_signal': 95}
```
### InfluxDB Data
Data is stored in InfluxDB with the following structure:
- **Measurement**: Configurable (default: `meteostick`)
- **Tags**: `channel` (transmitter channel)
- **Fields**: Weather data (temperature, humidity, wind_speed, wind_dir, pressure, etc.)
- **Timestamp**: UTC timestamp when data was received
## Troubleshooting
### Serial Port Issues
- Ensure the Meteostick device is connected and recognized by the system
- Check permissions: `sudo usermod -a -G dialout $USER`
- Verify the correct port with `ls /dev/tty*`
### InfluxDB Connection Issues
- Verify InfluxDB is running and accessible
- Check authentication token validity
- Ensure the database exists or the token has creation permissions
### No Weather Data
- Verify weather station transmitters are within range
- Check RF sensitivity settings
- Ensure correct channel configuration
- Verify transmitter battery levels
## License
This project is based on the WeewX meteostick driver and is released under the same license terms.
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
## Support
For issues and questions:
1. Check the troubleshooting section
2. Review Davis Instruments documentation
3. Open an issue on the project repository

16
docker-compose.yaml Normal file
View File

@@ -0,0 +1,16 @@
version: "3.9"
services:
metlogger:
build:
context: .
dockerfile: docker/Dockerfile
restart: unless-stopped
volumes:
- .:/app # optional: mount code for live editing
devices:
- "/dev/ttyACM0:/dev/ttyACM0"
env_file:
- .env
command: python -u meteostick_reader.py

17
docker/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# Use a lightweight Python image for ARM (Pi)
FROM python:3.12-slim-bullseye
# Set working directory
WORKDIR /app
# Copy requirements first (better caching)
COPY ../requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the code
COPY ../ ./
# Default command
CMD ["python", "main.py"]

View File

@@ -6,6 +6,7 @@ Based on the meteostick.py weewx driver
This script opens a Meteostick device, initializes it, and continuously
prints the raw messages received from Davis weather stations.
Can also send data to InfluxDB for storage and visualization.
"""
import time
@@ -13,6 +14,7 @@ import sys
import optparse
import math
import string
import os
try:
import serial
@@ -20,6 +22,12 @@ except ImportError:
print("Error: pyserial module is required. Install with: pip install pyserial")
sys.exit(1)
try:
from influxdb import InfluxDBClient
INFLUXDB_AVAILABLE = True
except ImportError:
INFLUXDB_AVAILABLE = False
# Simple CRC16 implementation for standalone use
def crc16(data):
"""Simple CRC16 implementation"""
@@ -510,37 +518,111 @@ class Meteostick(object):
logerr("unknown sensor identifier '%s' in %s" % (parts[0], raw))
return data
def send_to_influxdb(client, measurement, data, channel=None):
"""Send data to InfluxDB"""
if not client:
return
try:
# Prepare the data point
fields = {}
tags = {}
# Add channel as tag if available
if channel is not None:
tags['channel'] = str(channel)
# Convert data to appropriate fields
for key, value in data.items():
if key in ['channel', 'rf_signal', 'rf_missed']:
continue # Skip these as they're handled separately or not needed
# Convert to float if possible, otherwise keep as string
try:
fields[key] = float(value)
except (ValueError, TypeError):
fields[key] = str(value)
if not fields:
return # No data to send
# Add RF signal info if available
if 'rf_signal' in data:
fields['rf_signal'] = int(data['rf_signal'])
if 'rf_missed' in data:
fields['rf_missed'] = int(data['rf_missed'])
json_body = [
{
"measurement": measurement,
"tags": tags,
"time": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"fields": fields
}
]
client.write_points(json_body)
logdbg(f"Sent to InfluxDB: {len(fields)} fields")
except Exception as e:
logerr(f"Failed to send data to InfluxDB: {e}")
def main():
usage = """%prog [options] [--help]
Read and display messages from a Meteostick device."""
# Debug: Print environment variables
print("Environment Variables:")
for key in os.environ:
if key.startswith('METEOSTICK_'):
print(f" {key}={os.environ[key]}")
print()
parser = optparse.OptionParser(usage=usage)
parser.add_option('--port', dest='port', metavar='PORT',
help='serial port to which the meteostick is connected',
default=Meteostick.DEFAULT_PORT)
default=os.getenv('METEOSTICK_PORT', Meteostick.DEFAULT_PORT))
parser.add_option('--baud', dest='baud', metavar='BAUDRATE', type=int,
help='serial port baud rate',
default=Meteostick.DEFAULT_BAUDRATE)
default=int(os.getenv('METEOSTICK_BAUDRATE', Meteostick.DEFAULT_BAUDRATE)))
parser.add_option('--freq', dest='freq', metavar='FREQUENCY',
help='comm frequency: US (915MHz), EU (868MHz), or AU (915MHz)',
default=Meteostick.DEFAULT_FREQUENCY)
default=os.getenv('METEOSTICK_FREQUENCY', Meteostick.DEFAULT_FREQUENCY))
parser.add_option('--rfs', dest='rfs', metavar='RF_SENSITIVITY', type=int,
help='RF sensitivity (0-125, default 90)',
default=Meteostick.DEFAULT_RF_SENSITIVITY)
default=int(os.getenv('METEOSTICK_RF_SENSITIVITY', Meteostick.DEFAULT_RF_SENSITIVITY)))
parser.add_option('--iss-channel', dest='c_iss', metavar='ISS_CHANNEL', type=int,
help='channel for ISS (1-8)', default=1)
help='channel for ISS (1-8)', default=int(os.getenv('METEOSTICK_ISS_CHANNEL', '1')))
parser.add_option('--anemometer-channel', dest='c_anem', metavar='ANEM_CHANNEL', type=int,
help='channel for anemometer (0=none, 1-8)', default=0)
help='channel for anemometer (0=none, 1-8)', default=int(os.getenv('METEOSTICK_ANEMOMETER_CHANNEL', '0')))
parser.add_option('--leaf-soil-channel', dest='c_ls', metavar='LS_CHANNEL', type=int,
help='channel for leaf-soil station (0=none, 1-8)', default=0)
help='channel for leaf-soil station (0=none, 1-8)', default=int(os.getenv('METEOSTICK_LEAF_SOIL_CHANNEL', '0')))
parser.add_option('--th1-channel', dest='c_th1', metavar='TH1_CHANNEL', type=int,
help='channel for T/H sensor 1 (0=none, 1-8)', default=0)
help='channel for T/H sensor 1 (0=none, 1-8)', default=int(os.getenv('METEOSTICK_TH1_CHANNEL', '0')))
parser.add_option('--th2-channel', dest='c_th2', metavar='TH2_CHANNEL', type=int,
help='channel for T/H sensor 2 (0=none, 1-8)', default=0)
help='channel for T/H sensor 2 (0=none, 1-8)', default=int(os.getenv('METEOSTICK_TH2_CHANNEL', '0')))
parser.add_option('--rain-bucket', dest='bucket', metavar='BUCKET_TYPE', type=int,
help='rain bucket type: 0=0.01in, 1=0.2mm (default 1)', default=1)
help='rain bucket type: 0=0.01in, 1=0.2mm (default 1)', default=int(os.getenv('METEOSTICK_RAIN_BUCKET', '1')))
# InfluxDB options
parser.add_option('--influxdb-host', dest='influx_host', metavar='HOST',
help='InfluxDB host (default: localhost)', default=os.getenv('METEOSTICK_INFLUXDB_HOST', 'localhost'))
parser.add_option('--influxdb-port', dest='influx_port', metavar='PORT', type=int,
help='InfluxDB port (default: 8086)', default=int(os.getenv('METEOSTICK_INFLUXDB_PORT', '8086')))
parser.add_option('--influxdb-user', dest='influx_user', metavar='USER',
help='InfluxDB username (deprecated, use --influxdb-token)', default=os.getenv('METEOSTICK_INFLUXDB_USER', ''))
parser.add_option('--influxdb-pass', dest='influx_pass', metavar='PASSWORD',
help='InfluxDB password (deprecated, use --influxdb-token)', default=os.getenv('METEOSTICK_INFLUXDB_PASS', ''))
parser.add_option('--influxdb-token', dest='influx_token', metavar='TOKEN',
help='InfluxDB authentication token', default=os.getenv('METEOSTICK_INFLUXDB_TOKEN', ''))
parser.add_option('--influxdb-db', dest='influx_db', metavar='DATABASE',
help='InfluxDB database name (default: weather)', default=os.getenv('METEOSTICK_INFLUXDB_DB', 'weather'))
parser.add_option('--influxdb-measurement', dest='influx_measurement', metavar='MEASUREMENT',
help='InfluxDB measurement name (default: meteostick)', default=os.getenv('METEOSTICK_INFLUXDB_MEASUREMENT', 'meteostick'))
parser.add_option('--enable-influxdb', dest='enable_influx', action='store_true',
help='Enable sending data to InfluxDB', default=os.getenv('METEOSTICK_ENABLE_INFLUXDB', 'false').lower() in ('true', '1', 'yes', 'on'))
(opts, args) = parser.parse_args()
print("Meteostick Reader")
@@ -555,6 +637,75 @@ Read and display messages from a Meteostick device."""
print(f"T/H 1 Channel: {opts.c_th1}")
print(f"T/H 2 Channel: {opts.c_th2}")
print(f"Rain bucket type: {opts.bucket}")
# InfluxDB setup
influx_client = None
if opts.enable_influx:
if not INFLUXDB_AVAILABLE:
print("ERROR: InfluxDB client not available. Install with: pip install influxdb")
return 1
print(f"InfluxDB Host: {opts.influx_host}:{opts.influx_port}")
print(f"InfluxDB Database: {opts.influx_db}")
print(f"InfluxDB Measurement: {opts.influx_measurement}")
# Determine authentication method
if opts.influx_token:
print("Using token-based authentication")
auth_method = "token"
elif opts.influx_user or opts.influx_pass:
print("Using username/password authentication (deprecated)")
auth_method = "userpass"
else:
print("Using no authentication")
auth_method = "none"
try:
# Create client based on authentication method
if auth_method == "token":
# For token-based auth with influxdb library, pass token as password
influx_client = InfluxDBClient(
host=opts.influx_host,
port=opts.influx_port,
username='token', # Use 'token' as username
password=opts.influx_token, # Use token as password
database=opts.influx_db,
ssl=False, # Set to True if using HTTPS
verify_ssl=False
)
elif auth_method == "userpass":
influx_client = InfluxDBClient(
host=opts.influx_host,
port=opts.influx_port,
username=opts.influx_user if opts.influx_user else None,
password=opts.influx_pass if opts.influx_pass else None,
database=opts.influx_db
)
else:
influx_client = InfluxDBClient(
host=opts.influx_host,
port=opts.influx_port,
database=opts.influx_db
)
# Test connection
influx_client.ping()
# Create database if it doesn't exist
databases = influx_client.get_list_database()
if not any(db['name'] == opts.influx_db for db in databases):
influx_client.create_database(opts.influx_db)
print(f"Created InfluxDB database: {opts.influx_db}")
print("InfluxDB connection established")
except Exception as e:
print(f"Failed to connect to InfluxDB: {e}")
print("Continuing without InfluxDB support...")
influx_client = None
else:
print("InfluxDB support disabled")
print()
# Calculate rain per tip based on bucket type
@@ -584,10 +735,13 @@ Read and display messages from a Meteostick device."""
print("Configuring device...")
station.configure()
print("\nListening for messages (Press Ctrl+C to exit)...")
print(f"\nListening for messages (Press Ctrl+C to exit)...")
if influx_client:
print(f"Data will be sent to InfluxDB: {opts.influx_host}:{opts.influx_port}/{opts.influx_db}")
print("=" * 60)
message_count = 0
influx_sent_count = 0
while True:
try:
@@ -604,6 +758,13 @@ Read and display messages from a Meteostick device."""
parsed_data = station.parse_readings(raw_reading, rain_per_tip)
if parsed_data:
print(f" Parsed data: {parsed_data}")
# Send to InfluxDB if enabled and we have meaningful data
if influx_client and len(parsed_data) > 2: # More than just channel and rf_signal
channel = parsed_data.get('channel')
send_to_influxdb(influx_client, opts.influx_measurement, parsed_data, channel)
influx_sent_count += 1
except Exception as e:
print(f" Parse error: {e}")
@@ -631,7 +792,11 @@ Read and display messages from a Meteostick device."""
pass
print(f"Total messages received: {message_count}")
if influx_client:
print(f"Total messages sent to InfluxDB: {influx_sent_count}")
return 0
if __name__ == '__main__':
sys.exit(main())
if __name__ == '__main__':
sys.exit(main())

14
requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
certifi==2025.10.5
charset-normalizer==3.4.3
idna==3.10
influxdb==5.3.2
influxdb_client==1.49.0
msgpack==1.1.1
pyserial==3.5
python-dateutil==2.9.0.post0
pytz==2025.2
reactivex==4.0.4
requests==2.32.5
six==1.17.0
typing_extensions==4.15.0
urllib3==2.5.0