Source file: /~heha/hs/sht11.zip/src/SHT11.cpp

/* Ansteuerung des Temperatur- und Feuchte-Sensor-Schaltkreises
 * SHT11 mit dem PC am Parallelport, Seriellen Port oder HID-USB
 * h#s 12/03
*090602	mittels InpOut32.DLL, nur Win32, Teletubbie-Optik, auch COM-Port
-090626	falsche String-IDs bei den Fehlermeldungen
+090627	Ressource zweisprachig
*111027	Konfigurationsdaten nach HKEY_CURRENT_USER, Vista-Manifest
+1303xx	Win64-Kompilat
+130715	HID-USB dazu
 */

#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include <commctrl.h>
#include <setupapi.h>
#include <devguid.h>
extern "C"{
#include <hidsdi.h>
#include <hidpi.h>
}

#include <stdio.h>
#include <math.h>
#include <tchar.h>

struct TConfig{
 POINTS WinPos;		// Fensterposition des Dialogs
 BYTE Where;		// Welche Schnittstelle? 1-3=parallel (1=direkt, 2=PortTalk, 3=InpOut32),
			// 4=h#s-InpOut32.DLL, 5=seriell, 6=(PnPSA) HID-USB
 BYTE SerialNo;		// Nummer der seriellen Schnittstelle, 0 = COM1
 WORD ParallelAddr;	// Adresse des Parallelports (zumeist 0x378)
 FLOAT Ucc;		// Speisespannung - zur Berechnung der Temperatur
 BYTE SensVersion;	// Sensor-Version - zur Berechnung der rel. Feuchte, nur 3 oder 4, 5 == SHT21?
 BYTE ParallelNo;	// LPT-Nummer via h#s InpOut32.DLL, 0 = LPT1
 WORD Interval;		// Abfrage-Intervall in ms
 BYTE HidNo;		// Nummer des HID-USB, 0 = erstes
 BYTE unused;		// frei
 BYTE flags;		// AutoLog ...
 BYTE sht11ctl;		// Voreinstellungs-Bits für den Sensor (FastMode usw.)
 static void Changed();
}Config;

TCHAR LogFile[MAX_PATH];

int InstNr;		// Instanz-Nummer, nullbasiert
HWND MainWnd;		// Hauptfenster

#if _MSC_VER < 1400	// Visual C++ 6 (kein 64bit)
# include <conio.h>	// _inp und _out
#else
# include <intrin.h>	// __inbyte und __outbyte
#endif
#include <winioctl.h>
#include "PortTalk_IOCTL.h"
#include "../inpout32/InpOut32.h"
#ifdef _M_IX64
# define INPOUT32DLL "InpOutX64"
#else
# define INPOUT32DLL "InpOut32"
#endif

/**********
 * WUtils *
 **********/
HINSTANCE hInstance;
EXTERN_C int _fltused;
int _fltused;
typedef const TCHAR *PCTSTR, NEAR*NPCTSTR, FAR*LPCTSTR;

#define elemof(x) (sizeof(x)/sizeof((x)[0]))
#define T(x) TEXT(x)
#define nobreak

TCHAR MBoxTitle[64];
static TCHAR sDecimal[2];

int vMBox(HWND Wnd, UINT id, UINT style, va_list arglist) {
 TCHAR buf1[256],buf2[1024];
 LoadString(hInstance,id,buf1,elemof(buf1));
 _vsntprintf(buf2,elemof(buf2),buf1,arglist);
 return MessageBox(Wnd,buf2,MBoxTitle,style);
}

int _cdecl MBox(HWND Wnd, UINT id, UINT style,...) {
 va_list va;
 va_start(va,style);
 return vMBox(Wnd,id,style,va);
}

// Punkt durch Komma ersetzen
void PK(PTSTR s) {
 s=_tcschr(s,T('.'));
 if (s) *s=sDecimal[0];
}

bool GetDlgItemFloat(HWND Wnd, UINT id, float*f) {
 TCHAR s[32];
 GetDlgItemText(Wnd,id,s,elemof(s));
 PTSTR p=_tcschr(s,sDecimal[0]);
 if (p) *p='.';
 return _stscanf(s,T("%g"),f)==1 ? true : false;
}

void SetEditFocus(HWND Wnd, UINT id) {
 Wnd=GetDlgItem(Wnd,id);
 SetFocus(Wnd);
 Edit_SetSel(Wnd,0,-1);
}

void SetCheckboxGroup(HWND Wnd, UINT u, UINT o, UINT bits) {
 for (;u<=o; u++) {
  CheckDlgButton(Wnd,u,bits&1);
  bits>>=1;
 }
}

UINT GetCheckboxGroup(HWND Wnd, UINT u, UINT o) {
 UINT r=0;
 for (;o>=u; o--) {
  r<<=1;
  if (IsDlgButtonChecked(Wnd,o)) r++;
 }
 return r;
}

// Nur String setzen wenn verändert! (Flackern vermeiden)
void MySetDlgItemText(UINT id, LPCTSTR s) {
 TCHAR buf[64];
 GetDlgItemText(MainWnd,id,buf,elemof(buf));
 if (lstrcmp(buf,s)) SetDlgItemText(MainWnd,id,s);
}

void _cdecl ErrorPrint(UINT id,...) {
 TCHAR s[64],t[64];
 LoadString(hInstance,id,t,elemof(t));
 _vsntprintf(s,elemof(s),t,(va_list)(&id+1));
 MySetDlgItemText(111,s);
}

bool ShortYield(void) {	// true wenn OK zum Fortsetzen
 Sleep(1);
 MSG msg;
 if (!PeekMessage(&msg,0,0,0,PM_REMOVE)) return true;
 if (msg.hwnd==MainWnd) switch (msg.message) {
  case WM_CLOSE: PostMessage(msg.hwnd,msg.message,msg.wParam,msg.lParam);	// zurück
  return false;
  case WM_TIMER: return true;
 }
 TranslateMessage(&msg);
 DispatchMessage(&msg);
 return true;
}

struct dynaprocs{
 HINSTANCE hLib;	// Libname = erster String
 FARPROC proc[1];	// dynamisches Array aus Funktionszeigern; die Länge ergibt sich aus der Anzahl der
 bool dynaload(const char*e);		// nullterminierte Strings (doppel-null-terminiert)
};

bool dynaprocs::dynaload(const char*e) {
 int i;
 if (!(hLib=LoadLibraryA(e))) return false;
 for (i=0;;i++) {
  e+=lstrlenA(e)+1;
  if (!*e) return true;	// Ende erreicht
  if (!(proc[i]=GetProcAddress(hLib,*e=='#'?(const char*)atoi(e+1):e))) return false;
 }
}

struct crc8{
 BYTE crc;
 void put(BYTE x);
};

void crc8::put(BYTE x) {	// Prüfsummen-Maschinerie
 int i;
 for (i=0; i<8; i++) {
  crc=(crc>>1)^(((signed char)(x^(crc<<7))>>7)&0x8C);
  x<<=1;
 }
}

#define TIMEOUT 400	// Millisekunden

/******************************
 * Hardwarenahe Kommunikation *
 ******************************/
struct SHT11base{			// Negative Werte = Fehlerkode
 mutable bool ok;			// <true> solange Verbindung steht
 mutable bool verbose;			// vor Enum() oder Open() zu setzen, damit MessageBox erscheint
 virtual void Enum(HWND hCombo, const TConfig*Cfg) const=0;	// ComboBox füllen
 virtual void OnCb(HWND hCombo, TConfig *Cfg) const=0;		// ComboBox-Auswahl nach Cfg auslesen
 virtual void Open()=0;			// Verbindung herstellen
 virtual int GetRaw(BYTE addr, DWORD to=TIMEOUT)=0;	// I²C-Wert holen, 3=Temperatur, 5=Feuchte, 7=Flags
 virtual int SetRaw(BYTE addr, int v)=0;// I²C-Wert setzen: 6=Flags, v=Flag-Byte, 30=Soft-Reset
 virtual void Close()=0;		// Verbindung beenden
};

/*** Klasse ***/
struct SHT11port:SHT11base{	// Alles was direkt vom PC aus angesprochen wird
 bool ifsync;			// wenn Interface OK (in Sync), keine Dummy-Takte generieren
 DWORD inbuf;			// Ergebnisbits (alle I/O-Arten)
 BYTE inidx;			// Anzahl getätigter DATAQ()
 BYTE b;			// Spiegel des Ausgabeports
 BYTE crc_start;
 crc8 crc;
 virtual void SCKL()=0;
 virtual void SCKH()=0;
 virtual void DATAL()=0;
 virtual void DATAH()=0;
 virtual void DATAQ()=0;
 virtual DWORD InResult() {DWORD t=inbuf; inbuf=inidx=0; return t;};
 void TransmissionStart();
 void Open() {ZeroMemory(this,sizeof*this);}
 void SendByte(BYTE x);
 void RecvByte(bool more);
 bool WaitReady(DWORD timeout);
 int GetRaw(BYTE addr, DWORD to=TIMEOUT);
 int SetRaw(BYTE addr, int v);
};

// LptInOut: Benötigt 19*2 Bytes <outbuf>, 1 Byte <inbuf>
void SHT11port::SendByte(BYTE x) {
 for (int i=0;i<8;i++) {
  if (x&0x80) DATAH(); else DATAL();
  SCKH(); SCKL();
  x<<=1;
 }
 DATAH();
 SCKH();
 DATAQ();
 SCKL();
}

// LptInOut: Benötigt 19*2 Bytes <outbuf>, 8 Byte <inbuf>
void SHT11port::RecvByte(bool more) {
 for (int i=0;i<8;i++) {
  SCKH();
  DATAQ();
  SCKL();
 }
 if (more) DATAL();
 SCKH();
 SCKL();
 DATAH();
}

void SHT11port::TransmissionStart() {
 if (!ifsync) for (int i=0;i<9;i++) {SCKH();SCKL();}	// Startsequenz
 SCKH(); DATAL(); SCKL();
 SCKH(); DATAH(); SCKL();
}

bool SHT11port::WaitReady(DWORD timeout) {
 DWORD t=GetTickCount();
 while (DATAQ(),InResult()) {		// warten solange High
  if ((unsigned)GetTickCount()-t>timeout) return false;
  if (!ShortYield()) return false;
 }return true;
}

// Routine mit abgetrennter Ergebnisphase
int SHT11port::GetRaw(BYTE addr, DWORD to) {
 if (!ok) {ErrorPrint(9); return -3;}	// Port-Verbindung verloren
 DWORD w;
 crc.crc=crc_start;
 TransmissionStart();
 SendByte(addr);
 if (InResult()) {ifsync=false; ErrorPrint(13,addr); return -1;}	// Kein ACK? "Übertragungsfehler"!
 crc.put(addr);
 if (!WaitReady(to)) {ifsync=false; ErrorPrint(14,to); return -2;}	// "Zeitüberschreitung!"
 if (addr!=0x07) RecvByte(true);
 RecvByte(true);
 RecvByte(false);	// CRC

 w=InResult();		// Ergebnisbytes (16 oder 24 bit) auslesen
 if (addr!=0x07) crc.put(BYTE(w>>16));
 crc.put(BYTE(w>>8));
 if (crc.crc!=BYTE(w)) ErrorPrint(15,crc.crc,BYTE(w));	// "Prüfsumme, %02X!=%02X!"
 ifsync=true;
 if (addr==0x07) crc_start=(w>>8)&0x0F;
 return w>>8;
}

int SHT11port::SetRaw(BYTE addr, int v) {
 if (!ok) {ErrorPrint(9); return -3;}	// Port-Verbindung verloren
 TransmissionStart();
 SendByte(addr);
 if (addr!=30) SendByte(BYTE(v));
 if (InResult()) {ifsync=false; ErrorPrint(13); return -1;}	// irgendein NAK?
 ifsync=true;
 if (addr==0x06) crc_start=v&0x0F;
 return 0;
}

/*** Klasse ***/
struct SHT11ppt:SHT11port{
 union{
  WORD addr;		// für Direkt(1), PortTalk(2), InpOut32(3)
  HQUEUE hQueue;	// Für neue InpOut32-Schnittstelle (4)
 };
 virtual void SCKL() {out(b&~0x40);}
 virtual void SCKH() {out(b|0x40);}
 virtual void DATAL() {out(b&~0x80);}
 virtual void DATAH() {out(b|0x80);}
 virtual void DATAQ();
 virtual BYTE in()=0;
 virtual void out()=0;
 void out(BYTE n) {if (b==n) return; b=n; out();}
 void Enum(HWND hCombo, const TConfig*Cfg) const;
 void OnCb(HWND hCombo, TConfig*Cfg) const;
 void Open();
 void Close() {out(0x00);}	// Strom abschalten
};

static const WORD DefAddrs[]={0x378,0x278,0x3BC};

static SP_CLASSIMAGELIST_DATA ild;

// Füllt Combobox mit typischen Parallelportadressen
void SHT11ppt::Enum(HWND hCombo, const TConfig*Cfg) const {
 bool set=false;
 TCHAR buf[20];
 COMBOBOXEXITEM cbei;
 cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE;
 SetupDiGetClassImageIndex(&ild,(LPGUID)&GUID_DEVCLASS_PORTS,&cbei.iImage);
 cbei.iSelectedImage=cbei.iImage;
 cbei.pszText=buf;
 ComboBox_ResetContent(hCombo);
 SetWindowText(hCombo,NULL);

 for (cbei.iItem=0; cbei.iItem<elemof(DefAddrs); cbei.iItem++) {
  _sntprintf(buf,elemof(buf),T("%Xh (%u, LPT%u)"),
    DefAddrs[cbei.iItem],DefAddrs[cbei.iItem],cbei.iItem+1);
  SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
//  ComboBox_AddString(Wnd,buf);
  if (Cfg->ParallelAddr==DefAddrs[cbei.iItem]) {
   ComboBox_SetCurSel(hCombo,cbei.iItem);
   set=true;
  }
 }
 if (!set) {
  _sntprintf(buf,elemof(buf),T("%03Xh"),Cfg->ParallelAddr);
  SetWindowText(hCombo,buf);
 }
}

void SHT11ppt::OnCb(HWND hCombo, TConfig*Cfg) const{
 TCHAR s[32];
 GetWindowText(hCombo,s,elemof(s));
 Cfg->ParallelAddr=(WORD)_tcstol(s,NULL,16);
}

void SHT11ppt::Open() {
 inbuf=inidx=0;	// Ergebnispuffer vorbereiten
 b=0xA0; out();	// Stromversorgung zuschalten, SCK = L, DATA = H
}

void SHT11ppt::DATAQ() {
 if (!ok) return;
 if (++inidx>32) {ok=false; return;}
 inbuf<<=1;
 if (in()&0x40) inbuf|=1;
}

/*** Klasse ***/
struct SHT11dio:SHT11ppt{
 void Enum(HWND hCombo, const TConfig*Cfg) const;
 void Open();
 void Close() {}
 BYTE in();
 void out();
};

void SHT11dio::Enum(HWND hCombo, const TConfig*Cfg) const {
 if ((long)GetVersion()<0 || !verbose || MBox(GetParent(hCombo),1,MB_YESNO|MB_ICONQUESTION)==IDYES) {
  SHT11ppt::Enum(hCombo,Cfg);
 }else ok=false;
}

void SHT11dio::Open() {
 if ((long)GetVersion()<0 || !verbose || MBox(MainWnd,1,MB_YESNO|MB_ICONQUESTION)==IDYES) {
  addr=Config.ParallelAddr;
  ok=true;
  SHT11ppt::Open();
 }else ok=false;
}

BYTE SHT11dio::in() {
#if _MSC_VER < 1400	// Visual C++ 6 (kein 64bit)
 return _inp(addr+1);
#else
 return __inbyte(addr+1);
#endif
}

void SHT11dio::out() {
#if _MSC_VER < 1400	// Visual C++ 6 (kein 64bit)
 _outp(addr,b);
#else
 __outbyte(addr,b); 
#endif
}

/*** Klasse: InpOut32.dll ***/
struct SHT11par:SHT11ppt{
 mutable HINSTANCE hInpOut32;
 mutable Inp32_t pIn;
 mutable Out32_t pOut;
 void Enum(HWND hCombo, const TConfig*Cfg) const;
 void Open();
 void Close() {FreeLibrary(hInpOut32);}
 BYTE in() {return pIn(addr+1);}
 void out() {pOut(addr,b);}
};

void SHT11par::Enum(HWND hCombo, const TConfig*Cfg) const {
 SHT11ppt::Enum(hCombo,Cfg);
 ok=((dynaprocs*)&hInpOut32)->dynaload(INPOUT32DLL"\0Inp32\0Out32\0");
 if (!ok && verbose) MBox(GetParent(hCombo),3,MB_OK,T(INPOUT32DLL)T(".dll"));
}

void SHT11par::Open() {
 ok=((dynaprocs*)&hInpOut32)->dynaload(INPOUT32DLL"\0Inp32\0Out32\0");
 if (!ok) {
  if (verbose) MBox(MainWnd,3,MB_OK,T(INPOUT32DLL)T(".dll"));
  return;
 }
 addr=Config.ParallelAddr;
 SHT11ppt::Open();
}

/*** Klasse: PortTalk-IOCTL ***/
struct SHT11ioctl:SHT11ppt{
 HANDLE hDev;
 void Open();
 void Close() {CloseHandle(hDev);}
 BYTE in();
 void out();
};

void SHT11ioctl::Open() {
 hDev=CreateFile(T("\\\\.\\PortTalk"),GENERIC_READ,0,NULL,OPEN_EXISTING,
   FILE_ATTRIBUTE_NORMAL,NULL);
 if (hDev==INVALID_HANDLE_VALUE) hDev=0;
 ok=hDev!=0;
 if (!ok && verbose) MBox(MainWnd,2,MB_OK);
}

BYTE SHT11ioctl::in() {
 BYTE b=0xFF;
 if (!ok) return b;
 WORD a=addr+1;
 DWORD bret;
 if (!DeviceIoControl(hDev,IOCTL_READ_PORT_UCHAR,&a,2,&b,1,&bret,NULL) || bret!=1) ok=false;
 return b;
}

void SHT11ioctl::out() {
 DWORD bret;
 struct{
  WORD a;
  BYTE b;
 }s={addr,b};
 if (ok && !DeviceIoControl(hDev,IOCTL_WRITE_PORT_UCHAR,&s,3,NULL,0,&bret,NULL)) ok=false;
}

/*** Klasse: LPT-Mikrocode ***/
struct SHT11lpt:SHT11ppt{
 mutable HINSTANCE hInpOut32;
 mutable LptOpen_t pOpen;
 mutable LptInOut_t pInOut;
 mutable LptClose_t pClose;
 BYTE ucode[200];	// Mikrocode-Puffer
 int ucidx;		// Mikrocode-Füllstand
 void Enum(HWND hCombo, const TConfig *Cfg) const;
 void OnCb(HWND hCombo, TConfig *Cfg) const;
 void Open();
 BYTE in() {In();return 0xFF;}
 void In();
 void out();
 DWORD InResult();
 void Close();
};

void SHT11lpt::Enum(HWND hCombo, const TConfig *Cfg) const {
 COMBOBOXEXITEM cbei;
 cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_LPARAM;
 TCHAR buf[12];
 cbei.pszText=buf;
 cbei.iItem=0;
 SetupDiGetClassImageIndex(&ild,(LPGUID)&GUID_DEVCLASS_PORTS,&cbei.iImage);
 cbei.iSelectedImage=cbei.iImage;
 ComboBox_ResetContent(hCombo);
 SetWindowText(hCombo,NULL);

 if (((dynaprocs*)&hInpOut32)->dynaload(INPOUT32DLL"\0#21\0#22\0#27\0")) {
  for (int i=0; i<10; i++) {
   HQUEUE q=pOpen(i,0);
   if (q) {
    pClose(q);
    _sntprintf(buf,elemof(buf),T("LPT%u"),i+1);
    cbei.lParam=i;
    SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
    if (i==Cfg->ParallelNo) ComboBox_SetCurSel(hCombo,cbei.iItem);
    cbei.iItem++;
   }
  }
 }else if (verbose) MBox(GetParent(hCombo),hInpOut32?8:3,MB_OK,T(INPOUT32DLL)T(".dll"));
}

void SHT11lpt::OnCb(HWND hCombo, TConfig*Cfg) const{
 int i=ComboBox_GetCurSel(hCombo);
 if (i>=0) Cfg->ParallelNo=(BYTE)ComboBox_GetItemData(hCombo,i);
}

void SHT11lpt::Open() {
 pClose=NULL;
 ok=((dynaprocs*)&hInpOut32)->dynaload(INPOUT32DLL"\0#21\0#22\0#27\0");
 if (!ok) {
  if (verbose) MBox(MainWnd,hInpOut32?8:3,MB_OK,T(INPOUT32DLL)T(".dll"));
  hQueue=0;
  return;
 }
 hQueue=pOpen(Config.ParallelNo,0);
 if (!hQueue) {
  if (verbose) MBox(MainWnd,4,MB_OK,Config.ParallelNo+1,T(INPOUT32DLL)T(".dll"));
  ok=false;
 }
 ucidx=inidx=0;
}

void SHT11lpt::In() {
 if (ucidx>=sizeof ucode) {ok=false; return;}
 ucode[ucidx++]=0x11;	// Mikrocode generieren: In(+1)
}

void SHT11lpt::out() {
 if (ucidx>=sizeof ucode-1) {ok=false; return;}
 ucode[ucidx++]=0;
 ucode[ucidx++]=b;	// Mikrocode generieren: Out(+0)
}

// Aufgesammelten Mikrocode ausführen lassen (Flush) und Ergebnisbits bereitstellen
DWORD SHT11lpt::InResult() {
 if (!ok) return 0;
 BYTE buf[32];
 if (pInOut(hQueue,ucode,ucidx,buf,inidx)!=inidx) {ok=false; return 0;}
 DWORD ret=0;
 for (int i=0; i<inidx; i++) {
  ret<<=1;
  if (buf[i]&0x40) ret|=1;	// suche gesetzte ACK-Bits
 }
 ucidx=inidx=0;
 return ret;
}

void SHT11lpt::Close() {
 if (pClose) pClose(hQueue);
 FreeLibrary(hInpOut32);
}

/*** Klasse ***/
struct SHT11ser:SHT11port{
 HANDLE hCom;
 void Enum(HWND hCombo, const TConfig*Cfg) const;
 void OnCb(HWND hCombo, TConfig*Cfg) const;
 void Open();
 void Close();
 void SCKL() {if (ok && !EscapeCommFunction(hCom,CLRDTR)) ok=false;}	// DTR = Pin 4 = Taktausgang
 void SCKH() {if (ok && !EscapeCommFunction(hCom,SETDTR)) ok=false;}
 void DATAL() {if (!ok ||!b) return; if (!EscapeCommFunction(hCom,CLRRTS)) ok=false;}	// RTS = Pin 7 = Datenausgang
 void DATAH() {if (!ok || b) return; if (!EscapeCommFunction(hCom,SETRTS)) ok=false;}
 void DATAQ();
};

void SHT11ser::Enum(HWND hCombo, const TConfig*Cfg) const {
 COMBOBOXEXITEM cbei;
 cbei.mask=CBEIF_TEXT|CBEIF_LPARAM|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_OVERLAY;
 cbei.iItem=0;
 SetupDiGetClassImageIndex(&ild,(LPGUID)&GUID_DEVCLASS_PORTS,&cbei.iImage);
 cbei.iSelectedImage=cbei.iImage;
 ComboBox_ResetContent(hCombo);
 SetWindowText(hCombo,NULL);

 HANDLE devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
 if (devs!=INVALID_HANDLE_VALUE) {
  SP_DEVINFO_DATA devInfo;
  devInfo.cbSize=sizeof devInfo;
  for (UINT i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
   HKEY hKey;
   TCHAR s[16];
   DWORD slen=sizeof s;
   *s=0;
   if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
     ==INVALID_HANDLE_VALUE) continue;
   RegQueryValueEx(hKey,T("PortName"),NULL,NULL,(LPBYTE)s,&slen);
   RegCloseKey(hKey);
   if (*s=='C') {	// Fehlschläge und LPTx ausfiltern
    int i=_tcstol(s+3,NULL,0)-1;	// Nullbasierte COM-Nummer
    cbei.pszText=s;
    cbei.lParam=i;
    cbei.iOverlay=0;
    if (i==Cfg->SerialNo) {
     if (!hCom) cbei.iOverlay=2;	//durchkreuzt!
    }else{
     TCHAR buf[12];
     _sntprintf(buf,elemof(buf),T("\\\\.\\COM%u"),i+1);
     HANDLE h=CreateFile(buf,GENERIC_READ,0,NULL,OPEN_EXISTING,
       FILE_ATTRIBUTE_NORMAL,NULL);
     if (h!=INVALID_HANDLE_VALUE) CloseHandle(h);
     else cbei.iOverlay=2;
    }
    SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
    if (i==Cfg->SerialNo) ComboBox_SetCurSel(hCombo,cbei.iItem);
    cbei.iItem++;
   }
  }
 }
}

void SHT11ser::OnCb(HWND hCombo, TConfig*Cfg) const{
 int i=ComboBox_GetCurSel(hCombo);
 if (i>=0) Cfg->SerialNo=(BYTE)ComboBox_GetItemData(hCombo,i);
}

void SHT11ser::Open() {
 TCHAR buf[12];
 _sntprintf(buf,elemof(buf),T("\\\\.\\COM%d"),Config.SerialNo+1);
 hCom=CreateFile(buf,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
 if (hCom==INVALID_HANDLE_VALUE) {
  ok=false;
  hCom=0;
  if (verbose) MBox(MainWnd,5,MB_OK,Config.SerialNo+1);	// "Kann COM%d nicht öffnen"
 }else{
  ok=true;
  if (!EscapeCommFunction(hCom,SETBREAK)) ok=false;	// TxD = Pin 3 = Stromversorgung
  b=false;
  SCKL();
  DATAH();
  if (!ok && verbose) MBox(MainWnd,9,MB_OK);	//"Verbindungsverlust"
 }
}

void SHT11ser::Close() {
 if (ok) EscapeCommFunction(hCom,CLRBREAK);
 if (hCom) CloseHandle(hCom);
}

void SHT11ser::DATAQ() {
 if (!ok) return;
 DWORD ModemStat;
 if (!GetCommModemStatus(hCom,&ModemStat)) {ok=false; return;}
 if (++inidx>32) {ok=false; return;}		// hier: interner Programmfehler
 inbuf<<=1;
 if (ModemStat&MS_CTS_ON) inbuf|=1;	// CTS = Pin 8 = Dateneingang (verbunden mit RTS)
}


/*** Klasse ***/
struct SHT11hid:SHT11base{
 HANDLE hDev;
 PHIDP_PREPARSED_DATA pd;
 HIDP_CAPS hidcaps;
 HIDP_VALUE_CAPS *vcaps;
 BYTE *report;		// genügend großer Transferpuffer
 void Enum(HWND hCombo, const TConfig*Cfg) const;
 void OnCb(HWND hCombo, TConfig*Cfg) const;
 void Open();
 int GetRaw(BYTE addr, DWORD to=TIMEOUT);
 int SetRaw(BYTE addr, int v);
 void Close();
};

void SHT11hid::Enum(HWND hCombo, const TConfig*Cfg) const {
 COMBOBOXEXITEM cbei;
 cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE;
 TCHAR buf[12];
 cbei.pszText=buf;
 cbei.iItem=0;
 SetupDiGetClassImageIndex(&ild,(LPGUID)&GUID_DEVCLASS_HIDCLASS,&cbei.iImage);
 cbei.iSelectedImage=cbei.iImage;
 ComboBox_ResetContent(hCombo);
 SetWindowText(hCombo,NULL);

 GUID hidGuid;
 HidD_GetHidGuid(&hidGuid);
 HANDLE devs=SetupDiGetClassDevs(&hidGuid,NULL,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
 if (devs!=INVALID_HANDLE_VALUE) {
  SP_DEVICE_INTERFACE_DATA di;
  for (DWORD i=0; di.cbSize=sizeof di,SetupDiEnumDeviceInterfaces(devs,0,&hidGuid,i,&di); i++) {
   struct{
    SP_DEVICE_INTERFACE_DETAIL_DATA d;
    TCHAR space[MAX_PATH];
   }d;
   d.d.cbSize=sizeof d.d;
   if (SetupDiGetDeviceInterfaceDetail(devs,&di,&d.d,sizeof d,NULL,NULL)) {
    HANDLE h=CreateFile(d.d.DevicePath,GENERIC_READ|GENERIC_WRITE,
      FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
    if (h!=INVALID_HANDLE_VALUE) {
     PHIDP_PREPARSED_DATA pd;
     if (HidD_GetPreparsedData(h,&pd)) {
      HIDP_CAPS hidcaps;
      HIDP_VALUE_CAPS *vcaps;
      if ((int)HidP_GetCaps(pd,&hidcaps)>=0
      && hidcaps.NumberFeatureValueCaps>=2
      && (vcaps=new HIDP_VALUE_CAPS[hidcaps.NumberFeatureValueCaps])) {
       if ((int)HidP_GetValueCaps(HidP_Feature,vcaps,&hidcaps.NumberFeatureValueCaps,pd)>=0
// Nach einem Messwert "Temperatur" suchen (hier: erster Messwert)
       && vcaps[0].Units==0x00010001	// Einheit K (Kelvin)
       && vcaps[0].IsAbsolute
// Nach einem Messwert "Feuchte" suchen (hier: zweiter Messwert)
       && vcaps[1].Units==0x00000001	// dimensionslos
       && vcaps[1].IsAbsolute) {
        _sntprintf(buf,elemof(buf),T("HID%u"),cbei.iItem);
        SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
        if (cbei.iItem==Cfg->HidNo) ComboBox_SetCurSel(hCombo,cbei.iItem);
        cbei.iItem++;
       }
       delete[] vcaps;
      }
      HidD_FreePreparsedData(pd);
     }
     CloseHandle(h);
    }
   }
  }
  SetupDiDestroyDeviceInfoList(devs);
 }
}

void SHT11hid::OnCb(HWND hCombo, TConfig*Cfg) const{
 int i=ComboBox_GetCurSel(hCombo);
 if (i>=0) Cfg->HidNo=(BYTE)ComboBox_GetItemData(hCombo,i);
}

void SHT11hid::Open() {
 int n=Config.HidNo+1;
 ok=false;
 vcaps=NULL;
 pd=NULL;
 report=NULL;
 hDev=0;
// Leider dasselbe fast komplett noch einmal! (Siehe Enum()) Lässt sich hier kaum vereinfachen.
// Denn die simple HID-Gerätenummer zu speichern hätte den Nachteil, dass diese sich ändern kann,
// wenn der Anwender eine USB-Maus umsteckt.
 GUID hidGuid;
 HidD_GetHidGuid(&hidGuid);
 HDEVINFO devs=SetupDiGetClassDevs(&hidGuid,NULL,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
 if (devs!=INVALID_HANDLE_VALUE) {
  SP_DEVICE_INTERFACE_DATA di;
  for (DWORD i=0; di.cbSize=sizeof di, SetupDiEnumDeviceInterfaces(devs,0,&hidGuid,i,&di);i++) {
   struct{
    SP_DEVICE_INTERFACE_DETAIL_DATA d;
    TCHAR space[MAX_PATH];
   }d;
   d.d.cbSize=sizeof d.d;
   if (SetupDiGetDeviceInterfaceDetail(devs,&di,&d.d,sizeof d,NULL,NULL)) {
    HANDLE h=CreateFile(d.d.DevicePath,GENERIC_READ|GENERIC_WRITE,
      FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
    if (h!=INVALID_HANDLE_VALUE) {
     PHIDP_PREPARSED_DATA pd;
     if (HidD_GetPreparsedData(h,&pd)) {
      HIDP_CAPS hidcaps;
      HIDP_VALUE_CAPS *vcaps;
      if ((int)HidP_GetCaps(pd,&hidcaps)>=0
      && hidcaps.NumberFeatureValueCaps>=2
      && (vcaps=new HIDP_VALUE_CAPS[hidcaps.NumberFeatureValueCaps])) {
       if ((int)HidP_GetValueCaps(HidP_Feature,vcaps,&hidcaps.NumberFeatureValueCaps,pd)>=0
// Nach einem Messwert "Temperatur" suchen (hier: erster Messwert)
       && vcaps[0].Units==0x00010001	// Einheit K (Kelvin)
       && vcaps[0].IsAbsolute
// Nach einem Messwert "Feuchte" suchen (hier: zweiter Messwert)
       && vcaps[1].Units==0x00000001	// dimensionslos
       && vcaps[1].IsAbsolute) {
        if (!--n) {	// gewünschtes passendes PnPSA gefunden
         hDev=h;
         this->pd=pd;
         this->hidcaps=hidcaps;
         this->vcaps=vcaps;
         report=new BYTE[hidcaps.FeatureReportByteLength];
         if (report) ok=true;
         SetupDiDestroyDeviceInfoList(devs);
         return;
        }
       }
       delete[] vcaps;
      }
      HidD_FreePreparsedData(pd);
     }
     CloseHandle(h);
    }
   }
  }
  SetupDiDestroyDeviceInfoList(devs);
 }
 if (verbose) MBox(MainWnd,6,MB_OK,Config.HidNo);
}

int SHT11hid::GetRaw(BYTE addr, DWORD to) {
 if (!ok) {ErrorPrint(9); return -3;}
 report[0]=addr;
 if (!HidD_GetFeature(hDev,report,hidcaps.FeatureReportByteLength)) ok=false;
 return addr==0x07 ? report[1] : *(WORD*)(&report[1]);
}

int SHT11hid::SetRaw(BYTE addr, int v) {
 if (!ok) {ErrorPrint(9); return -3;}
 report[0]=addr|1;
 report[1]=BYTE(v);
 if (!HidD_SetFeature(hDev,report,hidcaps.FeatureReportByteLength)) ok=false;
 return 0;
}

void SHT11hid::Close() {
 if (report) delete[] report;
 if (pd) HidD_FreePreparsedData(pd);
 if (vcaps) delete[] vcaps;
}

// Erzeugt ein Schnittstellen-Objekt, bereit zum Enumerieren, für OnCb sowie Open
SHT11base *SHT11factory(int what) {
 switch (what) {
  case 1: return new SHT11dio;
  case 2: return new SHT11ioctl;
  case 3: return new SHT11par;
  case 4: return new SHT11lpt;
  case 5: return new SHT11ser;
  case 6: return new SHT11hid;
 }
 return NULL;
}

/************************
 * Konfigurationsdialog *
 ************************/

SHT11base *comobj;

void TConfig::Changed() {
 KillTimer(MainWnd,1);
 if (comobj) {
  comobj->Close();
  delete comobj;
 }
 comobj=SHT11factory(Config.Where);
 if (comobj) {
  comobj->verbose=true;
  comobj->Open();
  if (comobj->ok) {
   PostMessage(MainWnd,WM_TIMER,1,0);	// sofort loslegen
   SetTimer(MainWnd,1,Config.Interval,NULL);
  }else{
   MySetDlgItemText(108,T("--"));
   MySetDlgItemText(110,T("--"));
   MySetDlgItemText(112,T("--"));
   MySetDlgItemText(114,T("--"));
  }
 }
}

// Lädt Konfigurationsdaten aus Registrierung
static void LoadConfig() {
 static const TConfig DefConfig={
  {16,16},	// WinPos
  5,		// Where: Seriell
  0,		// SerialNo: COM1
  0x378,	// ParallelAddr: LPT1
  5.0F,		// Ucc: 5 V
  4,		// Sensor-Version (jeweils andere Linearisierungs-Koeffizienten)
  0,		// USB2LPT native: LPT1
  1000,		// Abfrage-Intervall in ms
  0,0,0,0};	// UsbHid: 0, PnpHid: 0, flags: 0, sht11ctl: 0
 HKEY key;
 bool ok=false;
 if (!RegOpenKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\SHT11"),
   0,KEY_QUERY_VALUE,&key)) {
  TCHAR tag[16];
  _sntprintf(tag,elemof(tag),T("Config%d"),InstNr);
  DWORD size=sizeof(Config);
  if (!RegQueryValueEx(key,tag,NULL,NULL,(LPBYTE)&Config,&size)
  && size==sizeof(Config) && Config.Interval) ok=true;
  _sntprintf(tag,elemof(tag),T("LogFile%d"),InstNr);
  size=sizeof(LogFile);
  RegQueryValueEx(key,tag,NULL,NULL,(LPBYTE)&LogFile,&size);
  RegCloseKey(key);
 }
 if (!ok) Config=DefConfig;
 Config.Changed();
}

// Speichert Konfigurationsdaten in Registrierung
static void SaveConfig() {
 HKEY key;
 if (RegCreateKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\SHT11"),
   0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) return;
 TCHAR s[256];
 RegSetValue(key,NULL,REG_SZ,s,LoadString(hInstance,0,s,elemof(s))*sizeof(TCHAR));
 TCHAR tag[16];
 _sntprintf(tag,elemof(tag),T("Config%d"),InstNr);
 RegSetValueEx(key,tag,0,REG_BINARY,(LPBYTE)&Config,sizeof(Config));
 _sntprintf(tag,elemof(tag),T("LogFile%d"),InstNr);
 if (*LogFile) RegSetValueEx(key,tag,0,REG_SZ,(LPBYTE)&LogFile,(lstrlen(LogFile)+1)*sizeof(TCHAR));
 else RegDeleteValue(key,tag);
 RegCloseKey(key);
}

#ifdef _M_IX86
__forceinline long lround(float f) {	// "lround" aus avr-gcc 4.x
 long i;
 _asm	fld	f
 _asm	fistp	i
 return i;
}
#else
# define lround		// Win64: Type-Cast rundet nicht, halb so schlimm
#endif

static INT_PTR CALLBACK SetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 TConfig *D=(TConfig*)GetWindowLongPtr(Wnd,DWLP_USER);
 switch (Msg) {
  case WM_INITDIALOG: {
   D=new TConfig;
   *D=Config;
   SetWindowLongPtr(Wnd,DWLP_USER,(LONG_PTR)D);
   ild.cbSize=sizeof(ild);
   SetupDiGetClassImageList(&ild);
   SendDlgItemMessage(Wnd,110,CBEM_SETIMAGELIST,0,(LPARAM)ild.ImageList);
   CheckDlgButton(Wnd,100+D->Where,TRUE);
   CheckDlgButton(Wnd,10+D->SensVersion,TRUE);
   SendMessage(Wnd,WM_TIMER,2,0);
   TCHAR s[32];
   _sntprintf(s,elemof(s),T("%g"),D->Ucc);
   PK(s);
   SetDlgItemText(Wnd,111,s);
   _sntprintf(s,elemof(s),T("%g"),D->Interval*0.001F);
   PK(s);
   SetDlgItemText(Wnd,112,s);
  }return TRUE;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 13:
   case 14:
    D->SensVersion=(BYTE)(wParam-10);
   break;
   case 101:
   case 102:
   case 103:
   case 104:
   case 105:
   case 106:
    D->Where=(BYTE)(wParam-100);
    SendMessage(Wnd,WM_TIMER,1,0);
   break;

   case IDOK: {
    float f;
    if (!GetDlgItemFloat(Wnd,112,&f) || f<0.1) {
     SetEditFocus(Wnd,112);
     break;
    }
    D->Interval=(WORD)lround(f*1000);
    if (!GetDlgItemFloat(Wnd,111,&D->Ucc)) {
     SetEditFocus(Wnd,111);
     break;
    }
    SHT11base*t=SHT11factory(D->Where);
    if (t) {
     t->OnCb(GetDlgItem(Wnd,110),D);
     delete t;
    }
    Config=*D;
    SetTimer(MainWnd,1,D->Interval,NULL);
    Config.Changed();
    SaveConfig();
   }nobreak;
   case IDCANCEL: {
    if (D) delete D;
    SetWindowLongPtr(Wnd,DWLP_USER,0);
    EndDialog(Wnd,wParam);
   }break;
  }break;

  case WM_TIMER: switch (wParam) {
   case 1: KillTimer(Wnd,1); nobreak;	// verbose
   case 2: {				// silent
    SHT11base *t=SHT11factory(D->Where);
    if (t) {
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
     t->verbose = wParam!=2;
     t->Enum(GetDlgItem(Wnd,110),D);
     delete t;
    }
   }break;
  }break;

  case WM_DEVICECHANGE: {
   SetTimer(Wnd,1,1500,NULL);
  }break;

  case WM_DESTROY: {
   SetupDiDestroyClassImageList(&ild);
  }break;
 }
 return FALSE;
}

/**************
 * DDE-Server *
 **************/

HFONT LargeFont,MediumFont;
HBRUSH BlueBrush;
HBRUSH YellowBrush;	// für DDE-Advise-Variablen
ATOM AtomBack;
DWORD DdeInst;
HSZ hszService,hszItem[4];
#define hszTopic hszService

HSZ CreateStringHandle(PCTSTR Str) {
 return DdeCreateStringHandle(DdeInst,Str,CP_WINNEUTRAL);
}

void FreeStringHandle(HSZ hsz) {
 if (hsz) DdeFreeStringHandle(DdeInst,hsz);
}

int CheckHsz(HSZ hsz) {
// liefert Fenster-ID für zu holenden String, 0 bei Fehler
 if (!DdeCmpStringHandles(hsz,hszItem[0])) return 108;
 if (!DdeCmpStringHandles(hsz,hszItem[1])) return 110;
 if (!DdeCmpStringHandles(hsz,hszItem[2])) return 112;
 if (!DdeCmpStringHandles(hsz,hszItem[3])) return 114;
 return 0;			// alles andere ist Fehler
}

HDDEDATA CALLBACK DdeCallback(UINT type, UINT fmt, HCONV conv,
 HSZ hsz1, HSZ hsz2, HDDEDATA data, ULONG_PTR data1, ULONG_PTR data2) {
 switch (type) {
  case XTYP_CONNECT: {
   if (hsz1==hszTopic) {
    ErrorPrint(11);	// "DDE-Verbindung gestartet"
    return (HDDEDATA)TRUE;
   }
  }break;
  case XTYP_DISCONNECT: {
   ErrorPrint(12);	// "DDE-Verbindung beendet"
  }break;
  case XTYP_REQUEST:
  case XTYP_ADVREQ: {
   if (fmt!=CF_TEXT) break;
   int id=CheckHsz(hsz2);
   if (!id) break;
   char s[16];
   return DdeCreateDataHandle(DdeInst,(BYTE*)s,
     GetDlgItemTextA(MainWnd,id,s,elemof(s))+1,
     0,hsz2,fmt,0);
  }
  case XTYP_ADVSTART:
  case XTYP_ADVSTOP: {
   if (fmt!=CF_TEXT) break;
   int id=CheckHsz(hsz2);
   if (!id) break;
   HWND w=GetDlgItem(MainWnd,id);
   id=(int)GetProp(w,(LPTSTR)AtomBack);
   id+=type==XTYP_ADVSTART?+1:-1;
   if (id) SetProp(w,(LPTSTR)AtomBack,(HANDLE)id);
   else RemoveProp(w,(LPTSTR)AtomBack);
   InvalidateRect(w,NULL,TRUE);
   return (HDDEDATA)TRUE;
  }
 }
 return (HDDEDATA)FALSE;
}

void DdeStart(void) {
 DdeInitialize(&DdeInst,DdeCallback,
   CBF_FAIL_POKES|CBF_FAIL_EXECUTES|
   CBF_SKIP_REGISTRATIONS|CBF_SKIP_UNREGISTRATIONS,0);
 hszService=CreateStringHandle(T("SHT11"));
 hszItem[0]=CreateStringHandle(T("T"));
 hszItem[1]=CreateStringHandle(T("H"));
 hszItem[2]=CreateStringHandle(T("D"));
 hszItem[3]=CreateStringHandle(T("A"));
 AtomBack=AddAtom(T("DdeAdvises"));
 YellowBrush=CreateSolidBrush(0xC0FFFFL);
 DdeNameService(DdeInst,hszService,0,DNS_REGISTER);
}

void DdeStop(void) {
 DdeUninitialize(DdeInst);
 DeleteObject(YellowBrush);
}

/***************
 * Haupfenster *
 ***************/
// Lineare Approximation
static float trafo(float x, float a, float e, float A, float E) {
 e-=a;
 if (!e) return 0;
 return (x-a)*(E-A)/e+A;
} 

// Ermittelt D1 in Abhängigkeit von der Betriebsspannung
static float GetD1() {
// Tabellenwerte aus Datenblatt für Temperaturberechnung
 static const float u[]={2.5F,3.0F,3.5F,4.0F,5.0F};
 static const float d[]={-39.4F,-39.6F,-39.7F,-39.8F,-40.1F};
 int i;
 for (i=1; i<elemof(u); i++) {
  if (Config.Ucc<u[i]) break;
 }
 return trafo(Config.Ucc,u[i-1],u[i],d[i-1],d[i]);
}

static float GetRH(WORD w, float t, bool w8bit) {
 float f;
 if (Config.SensVersion==4) {
  f=-2.0468F+ (w8bit ? 0.5872F*w-4.0845E-6F*w*w : 0.0367F*w-1.5955E-6F*w*w);
  if (f>=99) f=100;
 }else{
  f=-4+ (w8bit ? 0.648F*w-7.2E-6F*w*w : 0.0405F*w-2.8E-6F*w*w);
  f+=(t-25)*(0.01F+0.00008F*w);
 }
 if (f>100) f=100;
 if (f<0) f=0;
 return f;
}

static BOOL RetrieveWinPos() {	// liefert <TRUE> bei Änderung
 WINDOWPLACEMENT WP;
 WP.length=sizeof(WP);
 GetWindowPlacement(MainWnd,&WP);
 POINTS p={(short)WP.rcNormalPosition.left,(short)WP.rcNormalPosition.top};
 BOOL ret=*(BOOL*)&Config.WinPos^*(BOOL*)&p;	// Bits unterschiedlich?
 Config.WinPos=p;
 return ret;
}

INT_PTR CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam){
 switch (Msg) {
  case WM_INITDIALOG: {
   MainWnd=Wnd;
   GetWindowText(Wnd,MBoxTitle,elemof(MBoxTitle));
   LoadConfig();
   HFONT f=(HFONT)SendDlgItemMessage(Wnd,108,WM_GETFONT,0,0);
   LOGFONT fnt;
   GetObject(f,sizeof(fnt),&fnt);
   LargeFont=CreateFont(MulDiv(fnt.lfHeight,40,-13),0,0,0,700,0,0,0,0,0,0,0,0,T("Times"));
   MediumFont=CreateFont(MulDiv(fnt.lfHeight,26,-13),0,0,0,700,0,0,0,0,0,0,0,0,T("Times"));
   BlueBrush=CreateSolidBrush(0xFFC0C0L);
   SendDlgItemMessage(Wnd,108,WM_SETFONT,(WPARAM)LargeFont,0);
   SendDlgItemMessage(Wnd,110,WM_SETFONT,(WPARAM)LargeFont,0);
   SendDlgItemMessage(Wnd,112,WM_SETFONT,(WPARAM)MediumFont,0);
   SendDlgItemMessage(Wnd,114,WM_SETFONT,(WPARAM)MediumFont,0);
   DdeStart();
   SetTimer(Wnd,1,Config.Interval,NULL);
   SendMessage(Wnd,WM_WININICHANGE,0,0);
   HICON ico=LoadIcon(hInstance,MAKEINTRESOURCE(100));
   SetClassLongPtr(Wnd,GCLP_HICON,(LONG_PTR)ico);
   SetClassLongPtr(Wnd,GCLP_HICONSM,(LONG_PTR)ico);
   SetWindowPos(Wnd,0,Config.WinPos.x,Config.WinPos.y,0,0,SWP_NOSIZE|SWP_NOZORDER);
   STARTUPINFO si;
   si.cb=sizeof(si);
   GetStartupInfo(&si);
   ShowWindow(Wnd,si.wShowWindow);
  }return TRUE;

  case WM_WININICHANGE: {
   GetProfileString(T("intl"),T("sDecimal"),T("."),sDecimal,elemof(sDecimal));
  }break;

  case WM_TIMER: {
   static int Fehler;
   bool ok=true;
   int NK;		// Nachkommastellen: 2 (voll), 1 (reduzierte Auflösung)
   int w;
   float t,f;
   TCHAR s[16];
   MySetDlgItemText(111,T(""));	// Statuszeile leeren
   w=comobj->GetRaw(0x07);	// Statusregister
   _sntprintf(s,elemof(s),T("%02X"),(BYTE)w); MySetDlgItemText(102,s);
   SetCheckboxGroup(Wnd,32,35,w&7|(w>>4)&8);
   NK=w&1?1:2;
   w=comobj->GetRaw(0x03);		// Temperatur
   _sntprintf(s,elemof(s),T("%0*X"),NK+2,(WORD)w); MySetDlgItemText(107,s);
   if (w>=0) {
    t=GetD1()+ (NK==1 ? 0.04F: 0.01F)*w;
    _sntprintf(s,elemof(s),T("%.*f °C"),NK,t);
    PK(s);
    MySetDlgItemText(108,s);
    DdePostAdvise(DdeInst,hszTopic,hszItem[0]);
    Fehler=0;
   }else{
    ok=false;
    Fehler++;		// Ab 10 Fehler Fenstertext löschen!
    if (Fehler>10) MySetDlgItemText(108,T("--"));
   }
   w=comobj->GetRaw(0x05);
   _sntprintf(s,elemof(s),T("%0*X"),NK+1,(WORD)w); MySetDlgItemText(109,s);
   if (w>=0) {
    f=GetRH(w,t,NK==1);
    _sntprintf(s,elemof(s),T("%.*f %%"),NK,f);
    PK(s);
    MySetDlgItemText(110,s);
    DdePostAdvise(DdeInst,hszTopic,hszItem[1]);
    Fehler=0;
   }else{
    ok=false;
    Fehler++;
    if (Fehler>10) MySetDlgItemText(110,T("--"));
   }
   if (ok) {
/* Taupunkt, in °C */
    f*=0.01F;				// 0..1
    float M = t>=0 ? 17.62F : 22.46F;	//dimensionslos; über Wasser oder über Eis
    float N = t>=0 ? 243.12F : 272.62F;	//[°C]
    float d = -273.15F;
    if (f) {
     float h = (float)log(f);		//dimensionslos
     float j = M*t/(N+t);		//dimensionslos
     d = N*(h+j)/(M-h-j);		//[°C]
    }
    _sntprintf(s,elemof(s),T("%.*f °C"),NK,d);
    PK(s);
    MySetDlgItemText(112,s);
    DdePostAdvise(DdeInst,hszTopic,hszItem[2]);
/* absolute Feuchte, in g/m³ */
    float a=216.7F*(f*6.112F*(float)exp(17.62F*t/(243.12F+t))/(273.15F+t));
    _sntprintf(s,elemof(s),T("%.*f g/m³"),NK,a);
    PK(s);
    MySetDlgItemText(114,s);
    DdePostAdvise(DdeInst,hszTopic,hszItem[3]);
   }else if (Fehler>10) {
    MySetDlgItemText(112,T("--"));
    MySetDlgItemText(114,T("--"));
   }
   if (IsIconic(Wnd)) goto settitle;
  }break;

  case WM_SIZE: if (wParam==SIZEICONIC) {
   TCHAR buf[32];
settitle:
   GetDlgItemText(Wnd,108,buf,16);
   lstrcat(buf,T("; "));
   GetDlgItemText(Wnd,110,buf+lstrlen(buf),16);
   SetWindowText(Wnd,buf);
  }else{
   SendDlgItemMessage(Wnd,111,Msg,wParam,lParam);
  }break;

  case WM_QUERYOPEN: {
   SetWindowText(Wnd,MBoxTitle);
  }break;

  case WM_CTLCOLORSTATIC: {
   switch (GetDlgCtrlID((HWND)lParam)) {
    case 108:
    case 110:
    case 112:
    case 114: {
     SetBkMode((HDC)wParam,TRANSPARENT);
     if (GetProp((HWND)lParam,(LPTSTR)AtomBack)) return (BOOL)YellowBrush;
    }return (BOOL)BlueBrush;
   }
  }break;
  
  case WM_DEVICECHANGE: {
   if (comobj && !comobj->ok) {
    comobj->Close();
    comobj->verbose=false;
    comobj->Open();
   }
  }break;

  case WM_COMMAND: switch (LOWORD(wParam)){
   case 48:
   case 49:
   case 50: {
//    ConnectionReset();
    if (comobj->SetRaw(0x06,GetCheckboxGroup(Wnd,48,50))<0) ErrorPrint(10);	//"Schreibversuch"
   }break;
   case 11: DialogBox(hInstance,MAKEINTRESOURCE(101),Wnd,SetupDlgProc); break;
   case 1:
   case 2:
   if (RetrieveWinPos()) SaveConfig();
   Config.Where=0;
   Config.Changed();		// Handles ordentlich schließen
   EndDialog(Wnd,wParam);
  }break;

  case WM_ENDSESSION: if (wParam) {
   if (RetrieveWinPos()) SaveConfig();
  }break;

  case WM_DESTROY: {
   DdeStop();
   DeleteObject(LargeFont);
   DeleteObject(MediumFont);
   DeleteObject(BlueBrush);
  }break;
 }
 return FALSE;
}

INT_PTR CALLBACK WinMainCRTStartup() {
 hInstance=GetModuleHandle(NULL);
 INITCOMMONCONTROLSEX icc={sizeof(icc),ICC_USEREX_CLASSES};
 InitCommonControlsEx(&icc);

 return DialogBox(hInstance,MAKEINTRESOURCE(100),0,MainDlgProc);
}

Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded