Hacking the Weather Station – Part 1

Hacking the Weather Station – Part 1

In my previous post MY FIRST WEATHER STATION I mentioned how support for my WH2310 weather station outside of the official software is somewhat lacking and that I’d look further into it and so this past weekend I spent some time hacking the device and figuring out how it ticks.

The first problem I had to deal with is, where do I begin?

I started by looking at the library and documentation that Tycon kindly supplied me which they in turn had gotten off of their supplier. The library comes as a set of files for linking against using a standard C/C++ compiler. The core of the header file looks something like this:

// receiver is connected?
USBDRIVE_API BOOL CheckHandle();

//  write data to device, len<=61
USBDRIVE_API BOOL WriteData(PUCHAR pBuff,int len);

//  read data from device
USBDRIVE_API BOOL ReadData(PUCHAR pBuff);

//  write data to EEPROM, nAddress is start address, len<=12
USBDRIVE_API BOOL Write_EEPROM(int nAddress, PUCHAR pBuff,int len);

//  read data from EEPROM, nAddress is start address, len<=56
USBDRIVE_API BOOL Read_EEPROM(int nAddress, PUCHAR pBuff,int len);

However I generally use the .NET Framework and C# and so I had to first translate these to that platform which you can see here:

const string LIB_NAME = "usb2300.dll";

// receiver is connected?
[DllImport(LIB_NAME)]
static extern bool CheckHandle();

//  write data to device, len<=61
[DllImport(LIB_NAME)]
static extern bool WriteData(byte[] pBuff, int len);

//  read data from device
[DllImport(LIB_NAME)]
static extern bool ReadData([Out] byte[] pBuff);

//  write data to EEPROM, nAddress is start address, len<=12
[DllImport(LIB_NAME)]
static extern bool Write_EEPROM(int nAddress, byte[] pBuff, int len);

//  read data from EEPROM, nAddress is start address, len<=56
[DllImport(LIB_NAME)]
static extern bool Read_EEPROM(int nAddress, [Out] byte[] pBuff, int len);

I created a test program, just a simple console application and figure the best and safest method to first call would be CheckHandle() as it simply returns and doesn’t take anything. However when I came to run it I encountered a problem. The specified external methods could not be found. But they’re there in the header file?!

I cranked out my PE viewer and had a look at the raw export table which looked like this:

usb2300_exports

It was then I realised they were exported mangled, a wonderful feature of C/C++ exports. To fix this I had to explicitly declare the function name as part of the DllImport attribute. So now my imports methods looked something like this:

[DllImport(LIB_NAME, EntryPoint = "[email protected]@YAHXZ")]
static extern bool CheckHandle();

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]")]
static extern bool WriteData(byte[] pBuff, int len);

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]")]
static extern bool ReadData([Out] byte[] pBuff);

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]")]
static extern bool Write_EEPROM(int nAddress, byte[] pBuff, int len);

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]")]
static extern bool Read_EEPROM(int nAddress, [Out] byte[] pBuff, int len);

I explicitly give the name of the exported function this time. However the second time I come to run the program I get a stack imbalance. Sigh. What now? Calling convention!

By default if no calling convention is specified it uses STDCALL however given this was initially aimed at C/C++ users I figured I’d try CDECL instead:

[DllImport(LIB_NAME, EntryPoint = "[email protected]@YAHXZ", 
    CallingConvention = CallingConvention.Cdecl)]
static extern bool CheckHandle();

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]", 
    CallingConvention = CallingConvention.Cdecl)]
static extern bool WriteData(byte[] pBuff, int len);

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]", 
    CallingConvention = CallingConvention.Cdecl)]
static exter bool ReadData([Out] byte[] pBuff);

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]", 
    CallingConvention = CallingConvention.Cdecl)]
static extern bool Write_EEPROM(int nAddress, byte[] pBuff, int len);

[DllImport(LIB_NAME, EntryPoint = "[email protected]@[email protected]", 
    CallingConvention = CallingConvention.Cdecl)]
static extern bool Read_EEPROM(int nAddress, [Out] byte[] pBuff, int len);

Now everything can be called correctly I expanded my test program to dump the EEPROM using the Read_EEPROM() function. The documentation states that it’s basically a 64K block which I have to read in 56 byte segments, so I wrote a program to dump the EEPROM to file and poked it in a hex editor.

I could then compare the data to the specifications document, although I noticed the EEPROM doesn’t really give live data but historic data and various settings. I assumed that was what ReadData() was for, however at this point it hit me, I’m coding myself into a corner because I’m tying myself to a x86 Windows library. Which both means no explicit x64 support and that I’m tied to Windows. I’d envisioned running this on my Raspberry Pi 3.

And so my next task is to attempt to access the weather station over USB raw, and replicate the functionality the Windows library makes but in a more portable fashion and I’ll pick that up in my next post.

Until then you can download my EEPROM dump program below but you will need a WH2310 station otherwise you’re not likely to have much use of it.

Leave a Reply