// Copyright (c) 2011, Yves Goergen, http://unclassified.software/source/temper
//
// Copying and distribution of this file, with or without modification, are permitted provided the
// copyright notice and this notice are preserved. This file is offered as-is, without any warranty.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Diagnostics;
///
/// TEMPer device types.
///
public enum TEMPerDeviceType
{
None,
TEMPer,
TEMPerHUM
}
///
/// Provides access to a PCsensor TEMPer and TEMPerHUM device for reading temperature and humidity
/// and setting calibration values.
///
///
///
/// This class uses native functions from the RDingUSB.dll library for TEMPerHUM and HidFTDll.dll
/// for TEMPer devices and should therefore be built for the x86 target. Only device types for
/// which the DLL file is present are supported.
///
///
/// Multiple indexed devices can only addressed for the TEMPer device type.
///
///
public class TEMPer : IDisposable
{
#region Native methods
///
/// Native methods for TEMPer access.
///
private static class EntryAPI
{
[DllImport("HidFTDll.dll")]
public static extern void EMyCloseDevice();
[DllImport("HidFTDll.dll")]
public static extern int EMyDetectDevice(long myhwnd);
[DllImport("HidFTDll.dll")]
public static extern bool EMyInitConfig(bool dOrc);
[DllImport("HidFTDll.dll")]
public static extern bool EMyReadEP(ref byte up1, ref byte up2, ref byte up3, ref byte up4, ref byte up5, ref byte up6);
[DllImport("HidFTDll.dll")]
public static extern bool EMyReadHid([MarshalAs(UnmanagedType.AnsiBStr)] ref string pcBuffer, byte btUrlIndex, int btUrlLen);
[DllImport("HidFTDll.dll")]
public static extern double EMyReadTemp(bool flag);
[DllImport("HidFTDll.dll")]
public static extern void EMySetCurrentDev(int nCurDev);
[DllImport("HidFTDll.dll")]
public static extern bool EMyWriteEP(ref byte fd1, ref byte fd2, ref byte fd3, ref byte fd4, ref byte fd5);
[DllImport("HidFTDll.dll")]
public static extern bool EMyWriteHid(ref char[] pcBuffer, byte btUrlIndex, int btUrlLen);
[DllImport("HidFTDll.dll")]
public static extern bool EMyWriteTempText(bool flag);
}
///
/// Native methods for TEMPerHUM access.
///
private static class RDing
{
[DllImport("RDingUSB.dll")]
public static extern IntPtr CloseUSBDevice(IntPtr deviceHandle);
[DllImport("RDingUSB.dll")]
public static extern uint GetErrorMsg(ref string[] lpErrorMsg, uint dwErrorMsgSize);
[DllImport("RDingUSB.dll")]
public static extern ushort GetInputLength(IntPtr deviceHandle);
[DllImport("RDingUSB.dll")]
public static extern ushort GetOutputLength(IntPtr deviceHandle);
[DllImport("RDingUSB.dll")]
public static extern IntPtr OpenUSBDevice(int vid, int pid);
[DllImport("RDingUSB.dll")]
public static extern bool ReadUSB(IntPtr deviceHandle, byte[] buffer, int bytesCount, ref ulong numberOfBytesRead);
[DllImport("RDingUSB.dll")]
public static extern bool WriteUSB(IntPtr deviceHandle, byte[] buffer, int bytesCount, ref ulong numberOfBytesWritten);
}
#endregion Native methods
#region USB data
private static int vid = 0xC45;
private static int pid = 0x7402;
private static byte[] readTemperCmd = new byte[] { 0, 1, 0x80, 0x33, 1, 0, 0, 0, 0 };
private static byte[] getCalibrationCmd = new byte[] { 0, 1, 130, 0x77, 1, 0, 0, 0, 0 };
private static byte[] getVersionCmd = new byte[] { 0, 1, 0x86, 0xff, 1, 0, 0, 0, 0 };
private static byte[] eraseFlashCmd = new byte[] { 0, 1, 0x85, 0xdd, 1, 1, 0, 0, 0 };
private static byte[] writeDataCmd = new byte[] { 0, 1, 0x81, 0x55, 1, 0, 0, 0, 0 };
private static byte[] saveFlashCmd = new byte[] { 0, 1, 0x85, 0x11, 0, 0, 0, 0, 0 };
#endregion USB data
#region Private fields
private object accessLock = new object();
private TEMPerDeviceType deviceType;
private TEMPerDeviceType preferredDeviceType;
private int preferredDeviceNum;
private IntPtr deviceHandle;
private string deviceVersion;
private double currentTemperature;
private double currentHumidity;
private double temperatureCorrection;
private double humidityCorrection;
#endregion Private fields
#region Static methods
///
/// Detects all present and supported devices.
///
/// Array of device types.
public static TEMPerDeviceType[] DetectDevices()
{
List deviceList = new List();
try
{
IntPtr deviceHandle = RDing.OpenUSBDevice(vid, pid);
if (deviceHandle.ToInt64() != -1)
{
deviceList.Add(TEMPerDeviceType.TEMPerHUM);
RDing.CloseUSBDevice(deviceHandle);
}
}
catch
{
// No error if DLL is missing
}
try
{
int deviceCount = EntryAPI.EMyDetectDevice(0);
while (deviceCount-- > 0)
{
deviceList.Add(TEMPerDeviceType.TEMPer);
}
}
catch
{
// No error if DLL is missing
}
return deviceList.ToArray();
}
#endregion Static methods
#region Properties
///
/// Gets the current temperature value in °C.
///
///
/// Call ReadValues() before accessing this value.
///
public double Temperature
{
get
{
if (!SupportsTemperature)
throw new NotSupportedException("This operation is not supported with the current device");
return currentTemperature;
}
}
///
/// Gets the current humidity value in %.
///
///
/// Call ReadValues() before accessing this value.
///
public double Humidity
{
get
{
if (!SupportsHumidity)
throw new NotSupportedException("This operation is not supported with the current device");
return currentHumidity;
}
}
///
/// Gets the current dew point value in °C.
///
///
/// Call ReadValues() before accessing this value.
///
public double DewPoint
{
get
{
if (!SupportsTemperature || !SupportsHumidity)
throw new NotSupportedException("This operation is not supported with the current device");
// Source: http://www.wetterochs.de/wetter/feuchte.html
double a = currentTemperature >= 0 ? 7.5 : 7.6;
double b = currentTemperature >= 0 ? 237.3 : 240.7;
double SDD = 6.1078 * Math.Pow(10, ((a * currentTemperature) / (b + currentTemperature)));
double DD = currentHumidity / 100.0 * SDD;
double v = Math.Log10(DD / 6.1078);
double TD = b * v / (a - v);
return Math.Round(TD, 2);
// Unvalidated code from PCsensor:
//double d = (0.66077 + ((7.5 * currentTemperature) / (237.3 + currentTemperature))) + (Math.Log10(currentHumidity) - 2.0);
//return Math.Round((double) (((d - 0.66077) * 237.3) / (8.16077 - d)), 2);
}
}
///
/// Gets the current absolute humidity value in g/m³.
///
///
/// Call ReadValues() before accessing this value.
///
public double AbsoluteHumidity
{
get
{
if (!SupportsTemperature || !SupportsHumidity)
throw new NotSupportedException("This operation is not supported with the current device");
// Source: http://www.wetterochs.de/wetter/feuchte.html
double a = currentTemperature >= 0 ? 7.5 : 7.6;
double b = currentTemperature >= 0 ? 237.3 : 240.7;
double mw = 18.016;
double R = 8314.3;
double TK = currentTemperature + 273.15;
double SDD = 6.1078 * Math.Pow(10, ((a * currentTemperature) / (b + currentTemperature)));
double DD = currentHumidity / 100.0 * SDD;
double AF = 1E5 * mw / R * DD / TK;
return Math.Round(AF, 2);
}
}
///
/// Gets the temperature correction value.
///
///
/// Call ReadCalibration() before accessing this value.
///
public double TemperatureCorrection
{
get
{
if (!SupportsCorrection)
throw new NotSupportedException("This operation is not supported with the current device");
return temperatureCorrection;
}
}
///
/// Gets the humidity correction value.
///
///
/// Call ReadCalibration() before accessing this value.
///
public double HumidityCorrection
{
get
{
if (!SupportsCorrection)
throw new NotSupportedException("This operation is not supported with the current device");
return humidityCorrection;
}
}
///
/// Gets the device version.
///
///
/// Call ReadVersion() before accessing this value.
///
public string DeviceVersion
{
get
{
return deviceVersion;
}
}
///
/// Gets the current device type.
///
public TEMPerDeviceType DeviceType
{
get
{
return deviceType;
}
}
///
/// Gets a value indicating whether the current device supports temperature measurement.
///
public bool SupportsTemperature
{
get
{
return deviceType == TEMPerDeviceType.TEMPer || deviceType == TEMPerDeviceType.TEMPerHUM;
}
}
///
/// Gets a value indicating whether the current device supports humidity measurement.
///
public bool SupportsHumidity
{
get
{
return deviceType == TEMPerDeviceType.TEMPerHUM;
}
}
///
/// Gets a value indicating whether the current device supports internal value correction.
///
public bool SupportsCorrection
{
get
{
return deviceType == TEMPerDeviceType.TEMPer || deviceType == TEMPerDeviceType.TEMPerHUM;
}
}
#endregion Properties
#region Constructors
///
/// Initialises a new instance of the TEMPer class.
///
public TEMPer()
{
this.preferredDeviceType = TEMPerDeviceType.None;
this.preferredDeviceNum = 0;
OpenDevice(preferredDeviceType, 0);
}
///
/// Initialises a new instance of the TEMPer class.
///
/// Preferred device type.
public TEMPer(TEMPerDeviceType preferredDeviceType)
{
this.preferredDeviceType = preferredDeviceType;
this.preferredDeviceNum = 0;
OpenDevice(preferredDeviceType, 0);
}
///
/// Initialises a new instance of the TEMPer class.
///
/// Preferred device type.
/// Zero-based index of the device.
public TEMPer(TEMPerDeviceType preferredDeviceType, int deviceNum)
{
this.preferredDeviceType = preferredDeviceType;
this.preferredDeviceNum = deviceNum;
OpenDevice(preferredDeviceType, deviceNum);
}
#endregion Constructors
///
/// Frees resources.
///
public void Dispose()
{
switch (deviceType)
{
case TEMPerDeviceType.TEMPer:
EntryAPI.EMyCloseDevice();
break;
case TEMPerDeviceType.TEMPerHUM:
if (deviceHandle.ToInt64() != -1)
{
RDing.CloseUSBDevice(deviceHandle);
}
break;
}
deviceType = TEMPerDeviceType.None;
}
///
/// Opens the device.
///
/// Preferred device type.
/// Zero-based index of the device.
private void OpenDevice(TEMPerDeviceType preferredDeviceType, int deviceNum)
{
deviceType = TEMPerDeviceType.None;
deviceHandle = new IntPtr(-1L);
switch (preferredDeviceType)
{
case TEMPerDeviceType.None:
if (!OpenTEMPerHUMDevice())
{
OpenTEMPerDevice(deviceNum);
}
break;
case TEMPerDeviceType.TEMPer:
OpenTEMPerDevice(deviceNum);
break;
case TEMPerDeviceType.TEMPerHUM:
OpenTEMPerHUMDevice();
break;
}
if (deviceType == TEMPerDeviceType.None)
throw new Exception("No supported TEMPer device found.");
//ReadValues();
}
///
/// Opens the TEMPer device.
///
/// true if successful, false otherwise.
private bool OpenTEMPerDevice(int deviceNum)
{
try
{
int deviceCount = EntryAPI.EMyDetectDevice(0);
if (deviceCount > 0 && deviceNum < deviceCount)
{
deviceType = TEMPerDeviceType.TEMPer;
EntryAPI.EMySetCurrentDev(deviceNum);
Thread.Sleep(50);
EntryAPI.EMyInitConfig(true);
ReadCalibration();
return true;
}
}
catch
{
// No error if DLL is missing
}
return false;
}
///
/// Opens the TEMPerHUM device.
///
/// true if successful, false otherwise.
private bool OpenTEMPerHUMDevice()
{
try
{
//Debug.WriteLine("calling RDing.OpenUSBDevice");
deviceHandle = RDing.OpenUSBDevice(vid, pid);
//Debug.WriteLine("returned from RDing.OpenUSBDevice");
if (deviceHandle.ToInt64() != -1)
{
Debug.WriteLine("TEMPerHUM device opened.");
deviceType = TEMPerDeviceType.TEMPerHUM;
Thread.Sleep(50);
ReadVersion();
ReadCalibration();
return true;
}
else
{
Debug.WriteLine("RDing.OpenUSBDevice failed.");
}
}
catch
{
// No error if DLL is missing
}
return false;
}
///
/// Reads the stored correction values from the device.
///
public void ReadCalibration()
{
if (!SupportsCorrection)
throw new NotSupportedException("This operation is not supported with the current device");
lock (accessLock)
{
switch (deviceType)
{
case TEMPerDeviceType.TEMPer:
byte[] buffer6 = new byte[6];
EntryAPI.EMyReadEP(ref buffer6[0], ref buffer6[1], ref buffer6[2], ref buffer6[3], ref buffer6[4], ref buffer6[5]);
if (buffer6[2] >= 0 & buffer6[2] <= 100)
{
double offset = buffer6[4] + (double) buffer6[5] / 10.0;
temperatureCorrection = buffer6[2] + (double) buffer6[3] / 10.0 - offset;
}
else
{
temperatureCorrection = 0.0;
}
break;
case TEMPerDeviceType.TEMPerHUM:
byte[] buffer = new byte[9];
ulong count = 0L;
RDing.WriteUSB(deviceHandle, getCalibrationCmd, getCalibrationCmd.Length, ref count);
Thread.Sleep(50);
RDing.ReadUSB(deviceHandle, buffer, buffer.Length, ref count);
if (buffer[1] == 130)
{
if (buffer[3] < 128)
{
temperatureCorrection = (double) (buffer[3] * 0x100 + buffer[4]) / 100.0;
}
else
{
temperatureCorrection = (double) -(0x10000 - buffer[3] * 0x100 - buffer[4]) / 100.0;
}
if (buffer[5] < 128)
{
humidityCorrection = (double) (buffer[5] * 0x100 + buffer[6]) / 100.0;
}
else
{
humidityCorrection = (double) -(0x10000 - buffer[5] * 0x100 - buffer[6]) / 100.0;
}
}
break;
}
}
}
///
/// Reads the hardware version from the device.
///
public void ReadVersion()
{
if (deviceType != TEMPerDeviceType.TEMPerHUM)
throw new NotSupportedException("This operation is not supported with the current device");
lock (accessLock)
{
byte[] buffer = new byte[9];
ulong count = 0L;
RDing.WriteUSB(deviceHandle, getVersionCmd, getVersionCmd.Length, ref count);
Thread.Sleep(50);
RDing.ReadUSB(deviceHandle, buffer, buffer.Length, ref count);
deviceVersion = Encoding.ASCII.GetString(buffer, 1, 8);
Thread.Sleep(50);
RDing.ReadUSB(deviceHandle, buffer, buffer.Length, ref count);
string str = Encoding.ASCII.GetString(buffer, 1, 8);
for (int i = 0; i < str.Length; i++)
{
if (str[i] != deviceVersion[i])
{
deviceVersion += str[i];
}
else
{
break;
}
}
}
}
///
/// Reads the current sensor values from the device.
///
public void ReadValues()
{
lock (accessLock)
{
if (deviceType == TEMPerDeviceType.None)
{
// Currently no device opened to read from
Debug.WriteLine("Currently no device opened to read from");
OpenDevice(preferredDeviceType, preferredDeviceNum);
}
switch (deviceType)
{
case TEMPerDeviceType.TEMPer:
currentTemperature = EntryAPI.EMyReadTemp(true) + temperatureCorrection;
if (currentTemperature < -80 || currentTemperature > 160)
{
// Must be a reading error, don't use that value.
currentTemperature = double.NaN;
}
break;
case TEMPerDeviceType.TEMPerHUM:
bool b = false;
byte[] buffer = new byte[9];
ulong count = 0L;
//Debug.WriteLine("Trying RDing.WriteUSB to read current value...");
//Debug.WriteLine("calling RDing.WriteUSB");
if (RDing.WriteUSB(deviceHandle, readTemperCmd, readTemperCmd.Length, ref count))
{
//Debug.WriteLine("returned from RDing.WriteUSB");
//Debug.WriteLine("calling RDing.ReadUSB");
b = RDing.ReadUSB(deviceHandle, buffer, buffer.Length, ref count);
//Debug.WriteLine("returned from RDing.ReadUSB");
}
else
{
//Debug.WriteLine("returned from RDing.WriteUSB");
Debug.WriteLine("RDing.WriteUSB failed, opening device again.");
OpenDevice(TEMPerDeviceType.TEMPerHUM, 0);
//Debug.WriteLine("calling RDing.WriteUSB");
RDing.WriteUSB(deviceHandle, readTemperCmd, readTemperCmd.Length, ref count);
//Debug.WriteLine("returned from RDing.WriteUSB");
//Debug.WriteLine("calling RDing.ReadUSB");
b = RDing.ReadUSB(deviceHandle, buffer, buffer.Length, ref count);
//Debug.WriteLine("returned from RDing.ReadUSB");
if (!b)
Debug.WriteLine("RDing.ReadUSB failed, device still not available.");
}
if (b && buffer[1] == 0x80)
{
currentTemperature = (double) (buffer[3] * 0x100 + buffer[4]) / 100.0 - 40.0 + temperatureCorrection;
if (currentTemperature < -80 || currentTemperature > 160)
{
// Must be a reading error, don't use that value.
// (Never seen it with this device though.)
currentTemperature = double.NaN;
}
double rh = buffer[5] * 0x100 + buffer[6];
double rh_lin = -2.8E-6 * rh * rh + 0.0405 * rh - 4.0;
double rh_true = (currentTemperature - 25.0) * (0.01 + 8E-5 * rh) + rh_lin + humidityCorrection;
if (rh_true < -50 || rh_true > 150)
{
// Must be a reading error, don't use that value.
// (Never seen it with this device though.)
currentHumidity = double.NaN;
}
else
{
if (rh_true > 100.0)
{
rh_true = 100.0;
}
if (rh_true < 0.0)
{
rh_true = 0.0;
}
currentHumidity = Math.Round(rh_true, 2);
}
}
break;
}
}
}
///
/// Writes new correction values to the device's flash memory.
///
/// New temperature correction offset.
/// New humidity correction offset.
/// true on success, false otherwise.
public bool WriteCalibration(double temp, double hum)
{
if (!SupportsCorrection)
throw new NotSupportedException("This operation is not supported with the current device");
if (temp < -50.0 || temp > 50.0) throw new ArgumentOutOfRangeException("temp", temp, "Temperature correction value must be between -50 and 50.");
if (hum < -50.0 || hum > 50.0) throw new ArgumentOutOfRangeException("hum", hum, "Humidity correction value must be between -50 and 50.");
lock (accessLock)
{
switch (deviceType)
{
case TEMPerDeviceType.TEMPer:
byte[] buffer6 = new byte[6];
EntryAPI.EMyReadEP(ref buffer6[0], ref buffer6[1], ref buffer6[2], ref buffer6[3], ref buffer6[4], ref buffer6[5]);
byte offset = 50;
double d1 = temp + offset;
byte[] buffer5 = new byte[5];
buffer5[0] = buffer6[1];
buffer5[1] = (byte) Math.Floor(d1);
buffer5[2] = (byte) Math.Floor((d1 - Math.Floor(d1)) * 10.0);
buffer5[3] = offset;
buffer5[4] = 0;
if (EntryAPI.EMyWriteEP(ref buffer5[0], ref buffer5[1], ref buffer5[2], ref buffer5[3], ref buffer5[4]))
{
// Save new calibration values for this instance
temperatureCorrection = temp;
// Success
return true;
}
break;
case TEMPerDeviceType.TEMPerHUM:
IntPtr localDeviceHandle = RDing.OpenUSBDevice(vid, pid);
if (localDeviceHandle.ToInt64() == -1)
{
throw new Exception("Device error");
}
byte[] buffer = new byte[9];
ulong count = 0L;
double Tx = 0.0;
double Tx1 = 0.0;
double Tx0 = 0.0;
double Hx = 0.0;
double Hx1 = 0.0;
double Hx0 = 0.0;
Tx = temp * 100.0;
Hx = hum * 100.0;
if (Tx < 0.0)
{
Tx = 65536.0 + Tx;
Tx1 = Math.Floor(Tx / 256.0);
Tx0 = Tx - Tx1 * 256.0;
writeDataCmd[5] = Convert.ToByte(Tx1);
writeDataCmd[6] = Convert.ToByte(Tx0);
}
else
{
Tx1 = Math.Floor(Tx / 256.0);
Tx0 = Tx - Tx1 * 256.0;
}
writeDataCmd[5] = Convert.ToByte(Tx1);
writeDataCmd[6] = Convert.ToByte(Tx0);
if (Hx < 0.0)
{
Hx = 65536.0 + Hx;
Hx1 = Math.Floor(Hx / 256.0);
Hx0 = Hx - Hx1 * 256.0;
}
else
{
Hx1 = Math.Floor(Hx / 256.0);
Hx0 = Hx - Hx1 * 256.0;
}
writeDataCmd[7] = Convert.ToByte(Hx1);
writeDataCmd[8] = Convert.ToByte(Hx0);
RDing.WriteUSB(localDeviceHandle, writeDataCmd, writeDataCmd.Length, ref count);
Thread.Sleep(50);
RDing.ReadUSB(localDeviceHandle, buffer, buffer.Length, ref count);
if (buffer[4] == 0x55)
{
Thread.Sleep(50);
RDing.WriteUSB(localDeviceHandle, saveFlashCmd, saveFlashCmd.Length, ref count);
Thread.Sleep(50);
RDing.ReadUSB(localDeviceHandle, buffer, buffer.Length, ref count);
if (buffer[4] == 0x55)
{
// Save new calibration values for this instance
temperatureCorrection = temp;
humidityCorrection = hum;
// Success
return true;
}
}
break;
}
// Error
return false;
}
}
}