Python tools to communicate with Davis Vantage Pro2 weather stations.
CI targets Python 3.10–3.14 on Linux. Current source requires Python 3; historical Python 2 compatibility claims no longer apply.
The documentation index covers:
- Installation and connections
- Python API usage and API reference
- CLI commands
- Data formats, units, and known limitations
- Streaming and troubleshooting
- Development, CI, and release delivery
Built HTML is available as the documentation artifact on successful
CI runs.
This project provides:
- A Python API (
VantagePro2) for live data, archives, and station metadata - A CLI (
pyvantagepro) for common operational workflows - Parsers for LOOP and archive records
- Read current station time (
gettime) and set it (settime) - Read live LOOP data (
get_current_data) - Download archive records (
get_archives) - Get normalized live payloads (
get_current_data_as_json,get_current_data_as_list) - Get normalized archive payloads (
get_archives_as_json,get_archives_as_list) - Read station diagnostics, firmware info, and barometer calibration data
- Export data to CSV from API and CLI
- Connection recovery on
BrokenPipeError(automatic reconnect/retry)
- Full runnable catalog:
examples/README.md - Includes
examples/14_stream.pyfor production-style streaming with:- hourly UTC CSV rotation,
- MQTT store-and-forward buffering,
- optional per-key filtering via
examples/parameters.json, - dry/no-csv/no-mqtt runtime switches,
- flat
config.jsondeployment schema.
git clone https://github.com/CCMMMA/PyVantagePro.git
cd PyVantagePro
python3 -m pip install -U .python3 -m pip install -U -e .python -m pip uninstall -y pyvantagepro PyVantagePro
python -m pip install --no-cache-dir -U git+https://github.com/CCMMMA/PyVantagePro.git@mainfrom pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222', timeout=10)
station_time = device.gettime()
print(station_time)
current = device.get_current_data()
print(current['TempIn'])
print(current['RainRate'])
# Keep only selected fields and serialize as CSV
print(current.filter(('Datetime', 'TempIn', 'TempOut', 'RainRate')).to_csv())
archives = device.get_archives()
print(len(archives))
# Always close when done
device.close()from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
meta = device.meta() # {"TempIn": {"internal_unit": "degF", "si_unit": "degC"}, ...}
print(meta["TempIn"]["internal_unit"]) # degF
print(meta["TempIn"]["si_unit"]) # degC
print(meta["RainRate"]["internal_unit"]) # in/h
print(meta["RainRate"]["si_unit"]) # mm/h
device.close()from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
data = device.get_current_data()
subset = data.filter(('Datetime', 'TempIn', 'TempOut', 'HumOut', 'RainRate', 'WindSpeed'))
print(subset)
print(subset.to_csv())
device.close()import json
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
payload = device.get_current_data_as_json()
print(type(payload)) # dict
print(payload['TempIn']) # degC
print(payload['HumOut']) # percent (0..100)
print(payload['Datetime']) # ISO8601 datetime string
# Optional: serialize to JSON string
print(json.dumps(payload))
device.close()get_current_data_as_list() returns values in the same order as meta().
If a value fails sanity checks (or matches known sentinel values), the element is None.
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
meta = device.meta()
values = device.get_current_data_as_list()
for info, value in zip(meta, values):
print(info, value, meta[info]["internal_unit"], "=>", meta[info]["si_unit"])
device.close()New code should use get_current_data_as_list().
If you need compatibility with older snippets that call get_current_data_as_csv(), use:
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
get_values = getattr(device, "get_current_data_as_csv", device.get_current_data_as_list)
values = get_values()
print(values)
device.close()- Date/time fields are exported as ISO8601 strings.
- LOOP storm dates use their own packed date format, distinct from archive timestamps; missing or invalid calendar dates become
None. - LOOP alarm flags follow the console's least-significant-bit-first numbering, including separate soil/leaf alarm bits.
- Values are converted to SI where applicable (for example:
degF -> degC,in -> mm,mph -> m/s,inHg -> hPa). HumInandHumOutare normalized as percent values in the0..100range.- Selected fields are rounded to fixed precision for stable downstream processing.
- Alarm/sentinel fields are filtered in JSON output and set to
Nonein list output. - Sanity checks are applied; JSON drops invalid values and records their names in
failed, while list output keeps position and sets invalid entries toNone.
Some console values are sentinel readings that indicate missing/invalid sensor data (for example UV=255, SolarRad=32767).
get_current_data_as_json() behavior:
payload = device.get_current_data_as_json()
# When UV/SolarRad are sentinel values:
# - keys are removed from payload
# - failed list includes dropped keys
print(payload.get("UV")) # None
print(payload.get("SolarRad")) # None
print(payload.get("failed")) # ['UV', 'SolarRad'] (if both failed)get_current_data_as_list() behavior:
meta = device.meta()
row = device.get_current_data_as_list()
uv_idx = list(meta).index("UV")
solar_idx = list(meta).index("SolarRad")
# Same failing sensors become None in-place to preserve column order.
print(row[uv_idx]) # None
print(row[solar_idx]) # Nonefrom datetime import datetime
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
start = datetime(2026, 2, 19, 0, 0)
stop = datetime(2026, 2, 19, 23, 59)
archives = device.get_archives(start_date=start, stop_date=stop)
print(f"records: {len(archives)}")
print(archives[0])
print(archives[-1])
device.close()from datetime import datetime
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
start = datetime(2026, 2, 19, 0, 0)
stop = datetime(2026, 2, 19, 23, 59)
rows = device.get_archives_as_json(start_date=start, stop_date=stop)
print(type(rows)) # list
print(type(rows[0])) # dict
print(rows[0]) # ISO8601 + SI-normalized values
device.close()from datetime import datetime
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
start = datetime(2026, 2, 19, 0, 0)
stop = datetime(2026, 2, 19, 23, 59)
meta = device.meta()
rows = device.get_archives_as_list(start_date=start, stop_date=stop)
print(len(rows))
if rows:
print(rows[0]) # archive field order; different from live meta() order
device.close()Archive normalization reuses live conversion rules and has known limitations for archive-only fields and offset temperature sensors. See data formats before using normalized archives for analysis.
from pathlib import Path
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
db_path = Path("weather_archive.csv")
new_rows = device.get_archives()
if new_rows:
csv_text = new_rows.to_csv(header=not db_path.exists())
mode = "a" if db_path.exists() else "w"
with db_path.open(mode) as f:
f.write(csv_text)
device.close()from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
print("Firmware date:", device.firmware_date)
print("Firmware version:", device.firmware_version)
print("Archive period (min):", device.getperiod())
print("Timezone:", device.timezone)
print("Diagnostics:", device.getdiagnostics())
print("Barometer calibration:", device.getbar())
device.close()from datetime import datetime
from pyvantagepro import VantagePro2
device = VantagePro2.from_url('tcp:127.0.0.1:22222')
before = device.gettime()
device.settime(datetime.now())
after = device.gettime()
print("time:", before, "->", after)
old_period = device.getperiod()
device.setperiod(10) # allowed values: 1, 5, 10, 15, 30, 60, 120
print("archive period:", old_period, "->", device.getperiod())
device.close()from pyvantagepro import VantagePro2
from pyvantagepro.device import NoDeviceException, BadAckException
device = VantagePro2.from_url('tcp:127.0.0.1:22222', timeout=10)
try:
data = device.get_current_data()
except (NoDeviceException, BadAckException) as exc:
# The library retries internally; this handles final failure.
print("station read failed:", exc)
finally:
device.close()Commonly used methods/properties on VantagePro2:
- Constructors:
from_url,from_serial - Clock/period:
gettime,settime,getperiod,setperiod - Live data:
get_current_datametaget_current_data_as_jsonget_current_data_as_list
- Archives:
get_archivesget_archives_as_jsonget_archives_as_list
- Diagnostics/info:
getdiagnostics,getbarfirmware_date,firmware_version,timezone
PyVantagePro uses pylink URLs, for example:
tcp:host:portserial:/dev/ttyUSB0:19200:8N1
Use the transport format that matches your hardware setup.
pyvantagepro --helpAvailable commands include:
gettimesettimegetinfogetbargetdatagetarchivesupdategetperiodsetperiod
# Read station time
pyvantagepro gettime tcp:127.0.0.1:22222
# Read one live packet and print CSV to stdout
pyvantagepro getdata tcp:127.0.0.1:22222
# Download archives between two timestamps
pyvantagepro getarchives \
--start "2026-02-19 00:00" \
--stop "2026-02-19 23:59" \
tcp:127.0.0.1:22222
# Update a local CSV database file with new archive rows
pyvantagepro update tcp:127.0.0.1:22222 weather_archive.csvIf your station/proxy drops idle connections, writes can fail with BrokenPipeError.
Recent versions of this repository include automatic reconnect and retry logic.
If you still see this error:
- Make sure your environment is running the latest code (
pip install -U ...). - Recreate the
VantagePro2instance after long idle periods. - Verify host/port serial bridge health (e.g. TCP proxy, USB serial adapter).
- Increase timeout if your link is slow.
If stack traces point to .../site-packages/pyvantagepro/..., your virtualenv may use an older installed package.
Reinstall from this repository in that exact virtualenv.
Install development dependencies and run the checks:
python3 -m pip install -e . -r requirements-dev.txt
python3 -m pytest -q
python3 -m build
python3 -m twine check --strict dist/*
python3 -m sphinx -W --keep-going -b html docs docs/_build/htmlOpen docs/_build/html/index.html for the local guide. Repository guidance
for agents/contributors is documented in AGENTS.md.
- CI: pull requests, pushes to
main, and manual runs test Python 3.10–3.14, verify the CLI, build/check packages, test the source distribution, smoke-test the installed wheel, and build documentation with warnings as errors. - Release delivery: publishing a GitHub Release reruns CI on its tag, then
attaches validated wheel/source packages and a documentation ZIP using
GITHUB_TOKEN. No additional secret is needed.
See the release guide for versioning, reruns, and repository requirements. PyPI publishing and GitHub Pages are not configured.
- Historical release information remains in
CHANGES.rst. setup.pycurrently readsREADME.rstfor package metadata.
GNU GPL v3 (see COPYING).