Roman Bezlepkin’s Website

Reverse engineering EasyWeatherIP TCP data stream of an Ambient Weather WS-1000-WIFI station

Ambient Weather WS-1001-WIFI Ambient Weather WS-1001-WiFi (image credit: Ambient Weather)

I bought this weather station a long time ago to keep track of local weather and record observations. Since then I have encountered many issues with its reliability (mostly related to poor design with battery management and solar system) and maintenance (such as pulling the ladder out to replace batteries when they inevitably run out), these issues are out of scope for this article but perhaps I will revisit some of the simple modding techniques in a later article.

EasyWeatherIP

The weather station (sensor array) transmits its data in the 915MHz ISM band to a base station which appears to run some sort of custom Windows CE build (judging by the Microsoft license sticker on the back). This base station also receives data from an indoor temperature/humidity sensor. The base station has a very clunky and unappealing UI for configuring some interesting things, and displaying current and historical weather conditions. For sake of this article, I have connected the base station to a dedicated IoT WiFi access point, which has very restrictive network firewall rules among many things and would recommend anyone else letting «Internet of Things» devices into their networks.

Ambient Weather has developed a software package EasyWeatherIP (which for my effort runs on Windows) that lets you connect to the base station and retrieve/download weather data on a PC. This immediately made me curious to pull weather data into something more enjoyable, such as InfluxDB and display on a Grafana dashboard. The vendor software itself has some peculiar grammatical errors and UI/UX choices which I will let the reader decide for themselves.

My effort began with gathering operational evidence in the form of packet capture and sample data once the base station and the application are connected to the network. I have the software installed in a Windows 10 VM along with wireshark, the VM connected to the same isolated network the base station is connected to.

NMap scan of the base station did not reveal any open ports.

EasyWeatherIP inital screen Initial state of EasyWeatherIP

Upon launch, EasyWeatherIP sends several UDP packets to the subnet broadcast IP, presumably telling the base station that EasyWeatherIP is looking for a reverse connection (that is, base station connecting to EasyWeatherIP). The software asks the user for which IP address it should bind to (in the case of multiple network interfaces) and starts a TCP listener on that IP and port 6500 (waiting for a connection from the base station).

UDP packet sent by EasyWeatherIP to subnet broadcast (192.168.41.255 in my case)
0000   50 43 32 30 30 30 00 00 53 45 41 52 43 48 00 00   PC2000..SEARCH..
0010   00 57 40 da 0e 00 07 00 64 f9 2f 0d 0f 00 00 00   .W@.....d./.....
0020   00 00 00 00 00 ef 29 77                           ......)w

Eventually the base station sends back a UDP packet to local port 6000.

0000   48 50 32 30 30 30 00 00 53 45 41 52 43 48 00 00   HP2000..SEARCH..
0010   49 50 4d 41 43 00 00 00 d8 32 06 40 00 00 00 00   IPMAC....2.@....
0020   50 00 00 00 27 58 07 00 43 30 2d 32 31 2d 30 44   P...'X..C0-21-0D
0030   2d 31 45 2d 45 42 2d 37 38 00 00 00 00 00 00 00   -1E-EB-78.......
0040   31 39 32 2e 31 36 38 2e 34 31 2e 32 34 00 00 00   192.168.41.24...

Finally the base station tries to connect to the local TCP listener on port 6500. Several binary messages are exchanged; it would seem that EasyWeatherIP begins polling the base station for various data such as current date and time, online reporting configuration, current conditions, min/max data, etc.

EasyWeatherIP requesting date/time configuration
0000   50 43 32 30 30 30 00 00 52 45 41 44 00 00 00 00   PC2000..READ....
0010   44 41 54 45 54 49 4d 45 00 00 00 00 00 00 00 00   DATETIME........
0020   b8 01 00 00 00 00 00 00                           ........
Base station responds with date, time, and NTP server configuration
0000   48 50 32 30 30 30 00 00 57 52 49 54 45 00 00 00   HP2000..WRITE...
0010   44 41 54 45 54 49 4d 45 00 00 00 00 00 00 00 00   DATETIME........
0020   58 00 00 00 3d 00 00 00 e4 07 0a 05 0c 1f 3b 05   X...=.........;.
0030   00 00 01 01 00 00 00 c1 74 69 6d 65 2e 6e 69 73   ........time.nis
0040   74 2e 67 6f 76 00 00 00 00 00 00 00 00 00 00 00   t.gov...........
0050   00 00 00 00 00 00 00 00                           ........

Packet format

Perhaps of mild interest is the WRITE ABOUT command.

EasyWeatherIP requesting base station info
0000   50 43 32 30 30 30 00 00 52 45 41 44 00 00 00 00   PC2000..READ....
0010   41 42 4f 55 54 00 00 00 00 00 00 00 00 00 00 00   ABOUT...........
0020   b8 01 00 00 00 00 00 00                           ........
Base station info (model, firmware version, MAC and IP addresses clearly identifiable)
0000   48 50 32 30 30 30 00 00 57 52 49 54 45 00 00 00   HP2000..WRITE...
0010   41 42 4f 55 54 00 00 00 00 00 00 00 00 00 00 00   ABOUT...........
0020   a0 00 00 00 1c 00 00 00 57 53 2d 31 30 30 32 00   ........WS-1002.
0030   00 00 00 00 00 00 00 00 33 37 30 35 20 4d 42 00   ........3705 MB.
0040   33 36 39 35 20 4d 42 00 32 2e 30 00 00 00 00 00   3695 MB.2.0.....
0050   35 2e 30 2e 38 00 00 00 32 2e 34 2e 35 00 00 00   5.0.8...2.4.5...
0060   39 31 35 4d 00 00 00 00 30 44 00 00 00 00 00 00   915M....0D......
0070   41 36 00 00 00 00 00 00 43 30 2d 32 31 2d 30 44   A6......C0-21-0D
0080   2d 31 45 2d 45 42 2d 37 38 00 00 00 00 00 00 00   -1E-EB-78.......
0090   31 39 32 2e 31 36 38 2e 34 31 2e 32 34 00 00 00   192.168.41.24...

Based on cursory observation, most of the packets contain either PC2000 (software to station) or HP2000 (station to software) prefix, command name (READ or WRITE, odd choice of verbiage) along with data.

Current Conditions

EasyWeatherIP connected showing current conditions EasyWeatherIP connected showing current conditions in the lab

Of my primary interest is obtaining current conditions and relaying that information to my influxdb server. Simply sending/replaying a READ NOWRECORD packet to the base station should reply with a corresponding WRITE.

Query base station for current conditions
0000   50 43 32 30 30 30 00 00 52 45 41 44 00 00 00 00   PC2000..READ....
0010   4e 4f 57 52 45 43 4f 52 44 00 00 00 00 00 00 00   NOWRECORD.......
0020   b8 01 00 00 00 00 00 00                           ........

And the base station (after a short delay) replies with:

0000   48 50 32 30 30 30 00 00 57 52 49 54 45 00 00 00   HP2000..WRITE...
0010   4e 4f 57 52 45 43 4f 52 44 00 00 00 00 00 00 00   NOWRECORD.......
0020   68 00 00 00 15 00 00 00 4a 01 41 3b 9a 99 9d 41   h.......J.A;...A
0030   33 53 3e 44 66 c6 40 44 9a 99 b1 41 cd cc 5c 41   3S>Df.@D...A..\A
0040   9a 99 b1 41 00 00 00 00 00 00 00 00 00 00 00 00   ...A............
0050   00 00 00 00 9a 99 99 3e 00 00 00 00 9a 99 87 42   .......>.......B
0060   00 00 00 00 00 ff 00 00                           ........

At the same time, I took some screenshots of the software view in order to cross-correlate the numerical data. Typically, decimal units (such as temperature and pressure) would be encoded as a floating-point value (data type). Knowing that the base station runs on Windows CE, I would assume that the developers took the easy route and used a standard single data-type which encodes a floating point value into four bytes.

Still, there is a lot of information presented here in a span of 104 bytes, where should I look first?

At this point I took the weather station inside the house into my lab in which the temperature, humidity, and light levels are much more stable (at least stable enough to determine value offsets over multiple packet captures and cross-correlations). I can also control the wind direction and (roughly) the wind speed.

Turning the vane reads 348 degress on the base station and the following packet was captured. Though there is some data that was changed from the previous packet, of significant change is the 5c 01 present at offset 0x28.

Changed bytes starting at offset 0x28
0000   48 50 32 30 30 30 00 00 57 52 49 54 45 00 00 00   HP2000..WRITE...
0010   4e 4f 57 52 45 43 4f 52 44 00 00 00 00 00 00 00   NOWRECORD.......
0020   68 00 00 00 7d 00 00 00 5c 01 41 3b 33 33 9f 41   h...}...\.A;33.A
0030   00 60 3e 44 9a d9 40 44 9a 99 b1 41 cd cc 5c 41   .`>D..@D...A..\A
0040   9a 99 b1 41 00 00 00 00 00 00 00 00 00 00 00 00   ...A............
0050   00 00 00 00 9a 99 99 3e 00 00 00 00 9a 99 87 42   .......>.......B
0060   00 00 00 00 00 ff 00 00

Based on the endianness of how this value is represented, these two bytes should be reversed in order to decode the decimal value: 0x015c = 348. There it is, the angle of wind direction, in degress, is here represented as a 16-bit integer at offset 0x28.

With that in mind, we can cross-correlate decimal readings on the base station (or the EasyWeatherIP software) with expected bytes we potentially find within each packet. Keep in mind that the weather station itself (sensor array) transmits readings to the base station infrequently, so a fast poll packet to the base station will retrieve values as of the last sensor update.

Decoding the rest of the packet content reveals the following offsets and data:

There are a few more bytes that I skipped over around this data, such as what seems to be daily/weekly/monthly/YTD rainfall totals beginning at offset 0x50.

With this information in hand, I now have a simple C# snippet of code which can be used to decode the packet bytes into variables:

// READ NOWRECORD packet to the base station
var getWeather = new byte[] { 0x50, 0x43, 0x32, 0x30, 0x30, 0x30, 0x00, 0x00, 0x52, 0x45, 0x41, 0x44, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x4f, 0x57, 0x52, 0x45, 0x43, 0x4f, 0x52, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
networkStream.Write(getWeather, 0, getWeather.Length);

// WRITE NOWRECORD reply seems to max at 104 bytes, so fixed array here for this example
var gotWeather = new byte[104];
networkStream.Read(gotWeather, 0, gotWeather.Length);

// variables at offsets in packet
short windDirection = BitConverter.ToInt16(gotWeather, 0x28);
byte indoorHumidity = gotWeather[0x2a];
byte outdoorHumidity = gotWeather[0x2b];
float indoorTemp = BitConverter.ToSingle(gotWeather, 0x2c);
float barometerAbs = BitConverter.ToSingle(gotWeather, 0x30);
float barometerRel = BitConverter.ToSingle(gotWeather, 0x34);
float outdoorTemp = BitConverter.ToSingle(gotWeather, 0x38);
float dewPoint = BitConverter.ToSingle(gotWeather, 0x3c);
float windChill = BitConverter.ToSingle(gotWeather, 0x40);
float windSpeed = BitConverter.ToSingle(gotWeather, 0x44);
float windGust = BitConverter.ToSingle(gotWeather, 0x48);
float rainfallRate = BitConverter.ToSingle(gotWeather, 0x4c);
float solarFlux = BitConverter.ToSingle(gotWeather, 0x60);
byte uvIndex = gotWeather[0x64];

Console.WriteLine($"Wind direction={windDirection} IndoorH={indoorHumidity}% OutdoorH={outdoorHumidity} IndoorTemp={indoorTemp}C");
Console.WriteLine($"Bar Abs={barometerAbs} Rel={barometerRel} OutdoorTemp={outdoorTemp}C DewPt={dewPoint}C");
Console.WriteLine($"Wind Chill={windChill}C Speed={windSpeed}km/h Gust={windGust}km/h  Rainfall={rainfallRate}mm/h");
Console.WriteLine($"Solar flux={solarFlux} UVI={uvIndex}");

Enter influxdb+grafana

The final steps were to create a simple C# service which replicates the initial network tambourine dance to trigger the base station to connect to it and begin polling weather data. I have decided to use a timer (thread) which fires every 10 seconds to poll weather data, decode it, and then format an influxdb HTTP «packet».

Putting it all together reveals a much nicer weather data graph, gauges, and single stats! Combined with the power of influxdb, I can keep historical data for longer and integrate it into home automation.

Weather dashboard in Grafana Weather dashboard in Grafana