Skip to content

Porting to new hardware

Naturally, this is an advanced topic but, nevertheless, I'd like to encourage any Python wizards out there to give it a try. Of course, I have selfish reasons for this: I don't want to have to buy every weather station ever invented, and I don't want my roof to look like a weather station farm!

A driver communicates with hardware. Each driver is a single python file that contains the code that is the interface between a device and WeeWX. A driver may communicate directly with hardware using a MODBus, USB, serial, or other physical interface. Or it may communicate over a network to a physical device or a web service.

General guidelines

  • The driver should emit data as it receives it from the hardware (no caching).
  • The driver should emit only data it receives from the hardware (no "filling in the gaps").
  • The driver should not modify the data unless the modification is directly related to the hardware (e.g., decoding a hardware-specific sensor value).
  • If the hardware flags "bad data", then the driver should emit a null value for that datum (Python None).
  • The driver should not calculate any derived variables (such as dewpoint). The service StdWXService will do that.
  • However, if the hardware emits a derived variable, then the driver should emit it.

Implement the driver

Create a file in the user directory, say mydriver.py. This file will contain the driver class as well as any hardware-specific code. Do not put it in the weewx/drivers directory, or it will be deleted when you upgrade WeeWX.

Inherit from the abstract base class weewx.drivers.AbstractDevice. Try to implement as many of its methods as you can. At the very minimum, you must implement the first three methods, loader, hardware_name, and genLoopPackets.

loader()

This is a factory function that returns an instance of your driver. It has two arguments: the configuration dictionary, and a reference to the WeeWX engine.

hardware_name

This can as implemented as either an attribute, or as a property function. It should return a string with a short nickname for the hardware, such as "ACME X90"

genLoopPackets()

This should be a Python generator function that yields loop packets, one after another. Don't worry about stopping it: the engine will do this when an archive record is due. A "loop packet" is a dictionary. At the very minimum it must contain keys for the observation time and for the units used within the packet.

Required keys
dateTime The time of the observation in unix epoch time.
usUnits The unit system used. weewx.US for US customary, weewx.METRICWX, or weewx.METRIC for metric. See the Units for their exact definitions. The dictionaries USUnits, MetricWXUnits, and MetricUnits in file units.py, can also be useful.

Then include any observation types you have in the dictionary. Every packet need not contain the same set of observation types. Different packets can use different unit systems, but all observations within a packet must use the same unit system. If your hardware is capable of measuring an observation type but, for whatever reason, its value is bad (maybe a bad checksum?), then set its value to None. If your hardware is incapable of measuring an observation type, then leave it out of the dictionary.

A couple of observation types are tricky, in particular, rain. The field rain in a LOOP packet should be the amount of rain that has fallen since the last packet. Because LOOP packets are emitted fairly frequently, this is likely to be a small number. If your hardware does not provide this value, you might have to infer it from changes in whatever value it provides, for example changes in the daily or monthly rainfall.

Wind is another tricky one. It is actually broken up into four different observations: windSpeed, windDir, windGust, and windGustDir. Supply as many as you can. The directions should be compass directions in degrees (0=North, 90=East, etc.).

Be careful when reporting pressure. There are three observations related to pressure. Some stations report only the station pressure, others calculate and report sea level pressures.

Pressure types
pressure The Station Pressure (SP), which is the raw, absolute pressure measured by the station. This is the true barometric pressure for the station.
barometer The Sea Level Pressure (SLP) obtained by correcting the Station Pressure for altitude and local temperature. This is the pressure reading most commonly used by meteorologist to track weather systems at the surface, and this is the pressure that is uploaded to weather services by WeeWX. It is the station pressure reduced to mean sea level using local altitude and local temperature.
altimeter The Altimeter Setting (AS) obtained by correcting the Station Pressure for altitude. This is the pressure reading most commonly heard in weather reports. It is not the true barometric pressure of a station, but rather the station pressure reduced to mean sea level using altitude and an assumed temperature average.

genArchiveRecords()

If your hardware does not have an archive record logger, then WeeWX can do the record generation for you. It will automatically collect all the types it sees in your loop packets then emit a record with the averages (in some cases the sum or max value) of all those types. If it doesn't see a type, then it won't appear in the emitted record.

However, if your hardware does have a logger, then you should implement method genArchiveRecords() as well. It should be a generator function that returns all the records since a given time.

archive_interval

If you implement function genArchiveRecords(), then you should also implement archive_interval as either an attribute, or as a property function. It should return the archive interval in seconds.

getTime()

If your hardware has an onboard clock and supports reading the time from it, then you may want to implement this method. It takes no argument. It should return the time in Unix Epoch Time.

setTime()

If your hardware has an onboard clock and supports setting it, then you may want to implement this method. It takes no argument and does not need to return anything.

closePort()

If the driver needs to close a serial port, terminate a thread, close a database, or perform any other activity before the application terminates, then you must supply this function. WeeWX will call it if it needs to shut down your console (usually in the case of an error).

Define the configuration

You then include a new section in the configuration file weewx.conf that includes any options your driver needs. It should also include an entry driver that points to where your driver can be found. Set option station_type to your new section type and your driver will be loaded.

Examples

The fileparse driver is perhaps the simplest example of a WeeWX driver. It reads name-value pairs from a file and uses the values as sensor 'readings'. The code is actually packaged as an extension, located in examples/fileparse, making it a good example of not only writing a device driver, but also of how to package an extension. The actual driver itself is in examples/fileparse/bin/user/fileparse.py.

Another good example is the simulator code located in weewx/drivers/simulator.py. It's dirt simple, and you can easily play with it. Many people have successfully used it as a starting point for writing their own custom driver.

The Ultimeter (ultimeter.py) and WMR100 (wmr100.py) drivers illustrate how to communicate with serial and USB hardware, respectively. They also show different approaches for decoding data. Nevertheless, they are pretty straightforward.

The driver for the Vantage series is by far the most complicated. It actually multi-inherits from not only AbstractDevice, but also StdService. That is, it also participates in the engine as a service.

Naturally, there are a lot of subtleties that have been glossed over in this high-level description. If you run into trouble, look for help in the weewx-development group.