Source file: /~heha/mb-iwp/Kalibrator/TecalNT.zip/src/TecalNT.cpp

#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <windowsx.h>	// Makros
#include <setupapi.h>
#include <devguid.h>
#include <commctrl.h>
#include <ddeml.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>

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

#ifdef _DEBUG
# define _debug(x) DebugPrintf x
void _cdecl DebugPrintf(LPCTSTR s,...) {
 TCHAR buf[256];
 _sntprintf(buf,elemof(buf),s,(va_list)(&s+1));
 OutputDebugString(buf);
}
#else
# define _debug(x)
#endif

#ifdef WIN32
# define Send_WM_Command(ToWnd,CtlId,NotifyCode,FromWnd)\
  SendMessage(ToWnd,WM_COMMAND,MAKELONG(CtlId,NotifyCode),(LPARAM)FromWnd);
#else	// Win16
# define Send_WM_Command(ToWnd,CtlId,NotifyCode,FromWnd)\
  SendMessage(ToWnd,WM_COMMAND,CtlId,MAKELONG((UINT)FromWnd,NotifyCode));
#endif

#ifdef UNICODE
# define CF_TXT CF_UNICODETEXT
# define FMT_STR L"%S"
#else
# define CF_TXT CF_TEXT
# define FMT_STR "s"
#endif

#define WM_CONTINUEINIT	(WM_USER+0x100)

HINSTANCE ghInstance;
HWND ghMainWnd;
HFONT gBigFont,gExFont;
TCHAR StdMBoxTitle[64];
DWORD gComNum;
int gNK;		// angezeigte Nachkommastellen (1 oder 2)
TCHAR sDecimal[2];	// je nach Windows-Einstellung
TCHAR sNegativeSign[2];
TCHAR sPositiveSign[2];
DWORD gDdeInst;
HSZ hszService;
UINT CF_XlTable;	// Clipboard-Format
EXTERN_C int _fltused;	// Linker ruhig stellen
int _fltused;

/****************
 * aus WUTILS.C *
 ****************/

//Win32-typische Strukturen mit DWORD-Ausrichtung initialisieren
void _fastcall InitStruct(LPVOID p, UINT len) {
 LPUINT p2=(LPUINT)p;		// sonst hat C++ Verdauungsstörungen
 *p2=len; len/=sizeof(UINT); len--;
 if (len) do *++p2=0; while (--len);
}

int vMBox(HWND Wnd, LPCTSTR Text, UINT Type, va_list va) {
 TCHAR buf[256],buf2[256];
 if (!((DWORD_PTR)Text>>16)) {
  LoadString(ghInstance,(UINT)(DWORD_PTR)Text,buf2,elemof(buf2));
  Text=buf2;
 }
 _vsntprintf(buf,elemof(buf),Text,va);
 return MessageBox(Wnd,buf,StdMBoxTitle,Type);
}

int _cdecl MBox(HWND Wnd, LPCTSTR Text, UINT Type, ...) {
 return vMBox(Wnd,Text,Type,(va_list)(&Type+1));
}

/**********************************
 * Gleitkommazahlen-Konvertierung *
 **********************************/

#if _MSC_VER >= 1400 && defined(_M_IX86)
EXTERN_C long _declspec(naked) _cdecl _ftol2_sse(double v) {
 _asm {	push	eax
	fistp	dword ptr [esp]	// die Zahl wurde auf ST0 übergeben
	pop	eax
	ret
 }
}
#endif

// Hier: als Festkommawert ausgeben
int DoubleToString(double v, int nk, LPTSTR buf, UINT buflen) {
// if (!_finite(v)) return _sntprintf(buf,buflen,T("NaN"));
 int i=_sntprintf(buf,buflen,T("%.*f"),nk,v);
 LPTSTR p=_tcschr(buf,'.');
 if (p) *p=*sDecimal;
 return i;
}

bool StringToDoubleA(LPCSTR s, double*v) {
 if (!s || !*s) return false;
 char buf[32];
 lstrcpynA(buf,s,elemof(buf));
 char *p=strchr((char*)s,',');
 if (p) *p='.';		// Komma durch Punkt ersetzen (unabhängig von sDecimal)
 double t=strtod(buf,(char**)&s);
 if (v) *v=t;
 return !*s;		// OK nur wenn String-Ende erreicht
}

#ifdef UNICODE
bool StringToDouble(LPCWSTR s, double*v) {
 if (!s) return false;
 CHAR buf[32];
 WideCharToMultiByte(CP_ACP,0,s,-1,buf,elemof(buf),0,0);
 return StringToDoubleA(buf,v);
}
#else
# define StringToDouble StringToDoubleA
#endif

/*********************
 * Temperaturabfrage *
 *********************/

HSZ hszSoll;		// DDE-String-Handles
double gSoll,gIst;	// DDE-Variablen (in °C)
HANDLE hCom;

void SetWindowTitle(void) {
 TCHAR s[64];
 if (IsIconic(ghMainWnd)) {
  if (gIst==gIst) {
   int l=DoubleToString(gIst,2,s,elemof(s)-3);
   lstrcpyn(s+l,T(" °C"),elemof(s)-l);
  }else{
   LoadString(ghInstance,3,s,elemof(s));	// "Fehler"
  }
 }else{
  LoadString(ghInstance,1,s,elemof(s));
 }
 SetWindowText(ghMainWnd,s);
}

void OpenCom(void) {
 TCHAR ComName[12];
 _sntprintf(ComName,elemof(ComName),T("\\\\.\\COM%u"),gComNum);
 hCom=CreateFile(ComName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,0);
 if (hCom==INVALID_HANDLE_VALUE) {hCom=0; return;}
 DCB dcb;
 InitStruct(&dcb,sizeof(dcb));
 dcb.BaudRate=9600;
 dcb.ByteSize=8;
 dcb.fBinary=TRUE;
 SetCommState(hCom,&dcb);
 static const COMMTIMEOUTS to={0,0,100,0,0};
 SetCommTimeouts(hCom,(LPCOMMTIMEOUTS)&to);
}

// Der dumme Kalibrator will zwischen jedem gesendeten Zeichen eine kleine
// Pause (> 2 Stoppbits) haben! Bei 9600 Baud braucht 1 Zeichen ca. 1 ms
void SendSlowly(const char*s) {
 for(;*s;s++) {
  DWORD dwBytes;
  WriteFile(hCom,s,1,&dwBytes,NULL);
  Sleep(10);
 }
}

bool SendPassword(void) {
 if (!hCom) return false;
 PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
 SendSlowly("O");
 char ReadMsg[3];
 DWORD dwBytes;
 if (ReadFile(hCom,ReadMsg,3,&dwBytes,NULL)
 && dwBytes==3
 && ReadMsg[0]=='O'
 && ReadMsg[1]=='\r'
 && ReadMsg[2]=='\n') {
  SendSlowly("^<%+@/*{");
  return true;
 }
 return false;
}

bool QueryDevice(LPTSTR buf, UINT buflen) {
 if (!hCom) return false;
 SendSlowly("D");
 char ReadMsg[32];
 DWORD dwBytes;
 if (ReadFile(hCom,ReadMsg,sizeof ReadMsg-1,&dwBytes,NULL)
 && ReadMsg[0]=='D'
 && ReadMsg[1]=='\r'
 && ReadMsg[2]=='\n') {
  ReadMsg[dwBytes]=0;
  char*p=strchr(ReadMsg+3,'\r');
  if (!p) return false;
  *p=0;
  buf[_sntprintf(buf,buflen-1,FMT_STR,ReadMsg+3)]=0;
  return true;
 }
 return false;
}

bool QueryVersion(LPTSTR buf, UINT buflen) {
 if (!hCom) return false;
 SendSlowly("V");
 char ReadMsg[256];
 DWORD dwBytes;
 if (ReadFile(hCom,ReadMsg,sizeof ReadMsg-1,&dwBytes,NULL)
 && ReadMsg[0]=='V'
 && ReadMsg[1]=='\n'	// wirklich andersherum hier!!
 && ReadMsg[2]=='\r') {
  ReadMsg[dwBytes]=0;
  buf[_sntprintf(buf,buflen,FMT_STR,ReadMsg+3)]=0;
  return true;
 }
 return false;
}

static const union{
 __int64 i;
 double d;
}_NaN={-1};		// alle Bits 1
#define NaN _NaN.d

bool SetIst(double Ist) {
 if (gIst!=Ist && (gIst==gIst || Ist==Ist)) {	// NaN != NaN
  gIst=Ist;
  if (IsIconic(ghMainWnd)) SetWindowTitle();
  else{
   RECT r;
   GetClientRect(ghMainWnd,&r);
   r.top=r.bottom>>1;
   InvalidateRect(ghMainWnd,&r,TRUE);	// untere Hälfte neu zeichnen
  }
  DdePostAdvise(gDdeInst,0,0);
 }
 return true;
}

bool QueryIst(void) {
 if (hCom) {
  PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
  SendSlowly("T");
// Zurück kommt {"TB±0xxx.xx\r\n"}
// mit xxx.xx = Temperatur in Hundertstel, in °C
  char ReadMsg[13];
  DWORD dwBytes;
  double Ist;
  if (ReadFile(hCom,ReadMsg,12,&dwBytes,NULL)
  && dwBytes==12
  && sscanf(ReadMsg,"TB%lf\r\n",&Ist)==1) return SetIst(Ist);
 }
 SetIst(NaN);
 return false;
}

bool CalcSoll(WORD hexval, double*val) {
 static const WORD hexvals[]={
// -40    -30    -20    -10    0      10     20     30     40     50     60     70     80     90     100    110    120    130    140 °C
   0x36A5,0x38FD,0x3B55,0x3DAD,0x4005,0x425D,0x44B5,0x470D,0x4965,0x4BBD,0x4E15,0x506D,0x52C5,0x551D,0x5775,0x59CD,0x5C25,0x5E7D,0x60D5+1};
 int i;
 for (i=0; i<elemof(hexvals); i++) if (hexval<hexvals[i]) break;
 if (i==0) return false;
 if (i==elemof(hexvals)) return false;
// stückweise Geraden-Approximation
 if (val) *val=((double)(hexval-hexvals[i-1])/(hexvals[i]-hexvals[i-1])+i)*10-50;
 return true;
}

void InternalSetSoll(double Soll) {
 if (gSoll!=Soll && (gSoll==gSoll || Soll==Soll)) {
  gSoll=Soll;
  RECT r;
  GetClientRect(ghMainWnd,&r);
  r.bottom>>=1;
  InvalidateRect(ghMainWnd,&r,TRUE);	// obere Hälfte neu zeichnen
  DdePostAdvise(gDdeInst,0,hszSoll);
 }
}

bool QuerySoll(void) {
 if (hCom) {
  PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
  SendSlowly("s");
// Zurück kommt {"s\r\nxxxx00\r\n"}
  char ReadMsg[12];
  DWORD dwBytes;
  long hexval;
  double Soll;
  if (ReadFile(hCom,ReadMsg,11,&dwBytes,NULL)
  && dwBytes==11
  && sscanf(ReadMsg,"s\r\n%lx\r\n",&hexval)==1
  && !(hexval&0xFF)
  && CalcSoll(WORD(hexval>>8),&Soll)) {
   InternalSetSoll(Soll);
   return true;
  }
 }
 InternalSetSoll(NaN);
 return false;
}

bool SendSoll(double Soll) {
 if (!hCom) return false;
 PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
 Soll*=100;
 long val=(long)Soll;		// ruft _ftol2_sse
 char vz='+';			// Vorzeichen
 if (val<0) val=-val, vz='-';
 char WriteMsg[12],ReadMsg[12];
 _snprintf(WriteMsg,elemof(WriteMsg),"S%c%04d.%02d\r\n",vz,val/100,val%100);
 SendSlowly(WriteMsg);
 DWORD dwBytes;
 ReadMsg[11]=0;
 if (ReadFile(hCom,ReadMsg,11,&dwBytes,NULL)
 && dwBytes==11
 && !lstrcmpA(WriteMsg,ReadMsg)) return true;
 return false;
}

bool SetSoll(double Soll) {
 if (Soll<-40.0) return false;
 if (Soll>140.0) return false;
 if (gSoll!=Soll) {
  if (!SendSoll(Soll)) return false;
  InternalSetSoll(Soll);
 }
 return true;
}

void InitCom(void) {
 OpenCom();
 if (hCom) {
  SendPassword();
  QuerySoll();
  QueryIst();
 }else{
  MBox(ghMainWnd,(LPCTSTR)8,MB_OK|MB_ICONEXCLAMATION,gComNum);
  Send_WM_Command(ghMainWnd,0x40,0,0);	// sofort zur Konfiguration
 }
}

/*******
 * DDE *
 *******/

#pragma pack(2)
typedef struct{
 short tdtTable,dimensions,rows,cols;	// 0x0010,4,1,1
 short tdtType,tdtSize;			// 1,sizeof(double)=8
 double tdtData;
}TXlTableDouble;
typedef struct{
 short tdtTable,dimensions,rows,cols;	// 0x0010,4,1,1
 short tdtType,tdtSize;			// 4,sizeof(XlError)=2
 short tdtErrorCode;			// 36 = #NUM! (0=#NULL!,7=#DIV/0!,15=#VALUE!,23=#REF!,29=#NAME?,42=#N/A)
}TXlTableError;
#pragma pack()

HDDEDATA CALLBACK DdeCallback(UINT uType,UINT uFmt,HCONV,HSZ,HSZ hsz2,
  HDDEDATA hData,ULONG_PTR,ULONG_PTR) {
 HDDEDATA ret=DDE_FNOTPROCESSED;
 switch (uType) {
  case XTYP_ADVSTART: switch (uFmt) {
#ifdef UNICODE
   case CF_TEXT:
#endif
   case CF_TXT: break;
   default: if (uFmt==CF_XlTable) break;
   return ret;
  }
  case XTYP_CONNECT: ret=(HDDEDATA)TRUE; break;

  case XTYP_WILDCONNECT: {		// Automatische Enumeration durch Logger ("Item" fehlt noch!)
   HSZ pairs[3]={hszService,hszService};
   ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)pairs,sizeof pairs,0,0,0,0);
  }break;
  
  case XTYP_POKE: if (!DdeCmpStringHandles(hsz2,hszSoll)) {
   switch (uFmt) {
#ifdef UNICODE
    case CF_TEXT: {
     LPCSTR s=(LPCSTR)DdeAccessData(hData,NULL);
     double v;
     if (StringToDoubleA(s,&v) && SetSoll(v)) ret=(HDDEDATA)DDE_FACK;
     DdeUnaccessData(hData);
    }break;
#endif
    case CF_TXT: {
     LPCTSTR s=(LPCTSTR)DdeAccessData(hData,NULL);
     double v;
     if (StringToDouble(s,&v) && SetSoll(v)) ret=(HDDEDATA)DDE_FACK;
     DdeUnaccessData(hData);
    }break;
    default: if (uFmt==CF_XlTable) {
     DWORD len;
     const TXlTableDouble*pXlTable=(const TXlTableDouble*)DdeAccessData(hData,&len);
     if (len>=sizeof(TXlTableDouble)	// Mindest-Datenmenge
     && pXlTable->tdtTable==0x0010	// richtiger Kopf
     && pXlTable->tdtType==1		// richtiger Typ-Kode
     && SetSoll(pXlTable->tdtData)) ret=(HDDEDATA)DDE_FACK;
     DdeUnaccessData(hData);
    }
   }
  }break;
  
  case XTYP_ADVREQ:
  case XTYP_REQUEST: {
   double report;
   if (!DdeCmpStringHandles(hsz2,hszSoll)) report=gSoll;
   else report=gIst;			// alles andere ist Istwert
   switch (uFmt) {
#ifdef UNICODE
    case CF_TEXT: {
     TCHAR buf[32];
     CHAR bufc[32];
     DoubleToString(report,2,buf,elemof(buf));
     int len=_snprintf(bufc,elemof(bufc),"%S",buf)+1;
     ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)bufc,len,0,hsz2,uFmt,0);
    }break;
#endif

    default: if (uFmt==CF_XlTable) {
     TXlTableDouble XlTable={
      0x0010,	// Header
      4,	// 4 Bytes folgen
      1,	// 1 Spalte
      1,	// 1 Zeile
      1,	// DOUBLE
      sizeof(double),
      report};
     if (report!=report) {	// NaN
      XlTable.tdtType=4;
      XlTable.tdtSize=2;
      ((TXlTableError*)&XlTable)->tdtErrorCode=36;
     }
     ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)&XlTable,sizeof(XlTable),0,hsz2,uFmt,0);
    }else{	// Typ CF_TEXT oder CF_UNICODETEXT liefern
     TCHAR buf[32];
     ret=DdeCreateDataHandle(gDdeInst,(LPBYTE)buf,
       (DoubleToString(report,2,buf,elemof(buf))+1)*sizeof(TCHAR),0,hsz2,CF_TXT,0);
    }
   }
  }break;
  
 }
 return ret;
}

/*************
 * Dialog(e) *
 *************/

INT_PTR CALLBACK HardwareDlgProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
   SetTimer(Wnd,1,1,NULL);
   CheckDlgButton(Wnd,10+gNK-1,TRUE);
   TCHAR s[32];
   DoubleToString(gSoll,gNK,s,elemof(s));
   SetDlgItemText(Wnd,16,s);
   SendDlgItemMessage(Wnd,16,EM_SETMODIFY,0,0);
  }return TRUE;

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

  case WM_TIMER: {
   KillTimer(Wnd,wParam);
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
   HWND hCombo=GetDlgItem(Wnd,13);
   ComboBox_ResetContent(hCombo);
   HANDLE devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
   if (devs!=INVALID_HANDLE_VALUE) {
    SP_DEVINFO_DATA devInfo;
    devInfo.cbSize=sizeof devInfo;
    for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
     HKEY hKey;
     TCHAR s[16];
     DWORD slen=sizeof s;
     DWORD num;
     if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
       ==INVALID_HANDLE_VALUE) continue;
     if (!RegQueryValueEx(hKey,T("PortName"),NULL,NULL,(LPBYTE)s,&slen)
     && _stscanf(s,T("COM%u"),&num)==1) {	// LPTx ausfiltern
      int idx=ComboBox_AddString(hCombo,s);
      ComboBox_SetItemData(hCombo,idx,num);
      if (num==gComNum) ComboBox_SetCurSel(hCombo,idx);
     }
     RegCloseKey(hKey);
    }
    SetupDiDestroyDeviceInfoList(devs);
   }
  }break;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 10: switch (HIWORD(wParam)) {
    case EN_CHANGE: SetTimer(Wnd,10,200,NULL); break;
   }break;
   
   case 17: {	// Taste „Setzen“
    TCHAR s[32];
    HWND w=GetDlgItem(Wnd,16);
    GetWindowText(w,s,elemof(s));
    double v;
    LPCTSTR error=(LPCTSTR)9;
    if (StringToDouble(s,&v)) {
     error=(LPCTSTR)10;
     if (SetSoll(v)) break;
    }
    MBox(Wnd,error,MB_OK|MB_ICONEXCLAMATION);		// Fehler (je nach Kode)
    SetFocus(w);
    Edit_SetSel(w,0,-1);
    Edit_SetModify(w,FALSE);
   }break;
   
   case IDOK: {
    HWND hCombo=GetDlgItem(Wnd,13);
    int i=ComboBox_GetCurSel(hCombo);
    if (i<0) {
     MBox(Wnd,(LPCTSTR)2,MB_OK|MB_ICONEXCLAMATION);
     SetFocus(hCombo);
     break;
    }
    gComNum=(DWORD)ComboBox_GetItemData(hCombo,i);
    gNK=IsDlgButtonChecked(Wnd,10)?1:2;
    if (SendDlgItemMessage(Wnd,16,EM_GETMODIFY,0,0)) Send_WM_Command(Wnd,17,BN_CLICKED,Wnd);
   }nobreak;
   case IDCANCEL: EndDialog(Wnd,wParam); break;
  }break;

 }
 return FALSE;
}

/****************
 * Hauptfenster *
 ****************/
 
void LoadConfig(void) {
 HKEY key;
 gNK=2;		// nicht auf 0 stehen lassen
 if (RegOpenKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\TecalNT"),
   0,KEY_QUERY_VALUE,&key)) return;
 WINDOWPLACEMENT wp;
 InitStruct(&wp,sizeof(wp));
 GetWindowPlacement(ghMainWnd,&wp);
 DWORD size=sizeof(wp.rcNormalPosition);
 RegQueryValueEx(key,T("WindowPos"),NULL,NULL,(LPBYTE)&wp.rcNormalPosition,&size);
 SetWindowPlacement(ghMainWnd,&wp);
 size=sizeof(gComNum);
 RegQueryValueEx(key,T("ComNum"),NULL,NULL,(LPBYTE)&gComNum,&size);
 size=sizeof(gNK);
 RegQueryValueEx(key,T("Decimals"),NULL,NULL,(LPBYTE)&gNK,&size);
 RegCloseKey(key);
}
 
void SaveConfig(void) {
 HKEY key;
 if (RegCreateKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\TecalNT"),
   0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) return;
 RegSetValue(key,NULL,REG_SZ,StdMBoxTitle,(lstrlen(StdMBoxTitle)+1)*sizeof(TCHAR));
 WINDOWPLACEMENT wp;
 InitStruct(&wp,sizeof(wp));
 GetWindowPlacement(ghMainWnd,&wp);
 RegSetValueEx(key,T("WindowPos"),0,REG_BINARY,(LPBYTE)&wp.rcNormalPosition,sizeof(wp.rcNormalPosition));
 RegSetValueEx(key,T("ComNum"),0,REG_DWORD,(LPBYTE)&gComNum,sizeof(DWORD));
 RegSetValueEx(key,T("Decimals"),0,REG_DWORD,(LPBYTE)&gNK,sizeof(DWORD));
 RegCloseKey(key);
}

LRESULT CALLBACK MainWndProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 switch (Msg) {
  case WM_CREATE: {
   ghMainWnd=Wnd;
   HMENU SysMenu=GetSystemMenu(Wnd,FALSE);
   TCHAR s[64];
   for (int i=4; i<8; i++) {
    LoadString(ghInstance,i,s,elemof(s));	// Menüpunkte ergänzen
    InsertMenu(SysMenu,SC_CLOSE,0,i*0x10,s);
   }
   SendMessage(Wnd,WM_WININICHANGE,0,0);
   SendMessage(Wnd,WM_QUERYOPEN,0,0);	// Titel setzen
   LoadConfig();
   PostMessage(Wnd,WM_CONTINUEINIT,0,0);
  }break;
  
  case WM_CONTINUEINIT: {
   if (gComNum) InitCom();
   else Send_WM_Command(Wnd,0x40,0,0);		// beim ersten Start sofort zur Konfiguration
   SetTimer(Wnd,100,1000,NULL);
  }break;
  
  case WM_WININICHANGE: {
   GetProfileString(T("intl"),T("sDecimal"),T("."),sDecimal,elemof(sDecimal));
   GetProfileString(T("intl"),T("sNegativeSign"),T("-"),sNegativeSign,elemof(sNegativeSign));
   GetProfileString(T("intl"),T("sPositiveSign"),T(""),sPositiveSign,elemof(sPositiveSign));
  }return 0;

  case WM_SIZE: {
   if (wParam==SIZE_MINIMIZED) SetWindowTitle();
   else{
    if (gBigFont) DeleteFont(gBigFont);
    if (gExFont) DeleteFont(gExFont);
    gBigFont=CreateFont(-GET_Y_LPARAM(lParam)/2,GET_X_LPARAM(lParam)/12,0,0,0,0,0,0,0,0,0,0,0,T("Arial"));
    gExFont=CreateFont(-GET_Y_LPARAM(lParam)/8,GET_X_LPARAM(lParam)/48,0,0,0,0,0,0,0,0,0,0,0,T("Arial"));
   }
  }break;
  
  case WM_PAINT: {
   PAINTSTRUCT ps;
   BeginPaint(Wnd,&ps);
   SendMessage(Wnd,WM_PRINTCLIENT,(WPARAM)ps.hdc,0);
   EndPaint(Wnd,&ps);
  }return 0;
  
  case WM_PRINTCLIENT: {
   RECT r;
   TCHAR s[32];
   GetClientRect(Wnd,&r);
   int l;
// Istwert
   SelectFont((HDC)wParam,gBigFont);
   SetTextAlign((HDC)wParam,TA_BOTTOM|TA_RIGHT);
   if (gIst==gIst) {
    l=DoubleToString(gIst,gNK,s,elemof(s));
    lstrcpyn(s+l,T(" °C"),elemof(s)-l);
    l+=3;
   }else l=LoadString(ghInstance,3,s,elemof(s));	// "Fehler"
   TextOut((HDC)wParam,r.right,r.bottom,s,l);
// Ist-Beschreibung
   SelectFont((HDC)wParam,gExFont);
   SetTextAlign((HDC)wParam,TA_TOP|TA_LEFT);
   TextOut((HDC)wParam,0,r.bottom/2,s,LoadString(ghInstance,15/*Istwert*/,s,elemof(s)));
// Sollwert
   SetTextColor((HDC)wParam,RGB(192,192,192));
   SelectFont((HDC)wParam,gBigFont);
   SetTextAlign((HDC)wParam,TA_BOTTOM|TA_RIGHT);
   if (gSoll==gSoll) {
    l=DoubleToString(gSoll,gNK,s,elemof(s));
    lstrcpyn(s+l,T(" °C"),elemof(s)-l);
    l+=3;
   }else l=LoadString(ghInstance,3,s,elemof(s));	// "Fehler"
   TextOut((HDC)wParam,r.right,r.bottom/2,s,l);
// Soll-Beschreibung
   SelectFont((HDC)wParam,gExFont);
   SetTextAlign((HDC)wParam,TA_TOP|TA_LEFT);
   TextOut((HDC)wParam,0,0,s,LoadString(ghInstance,14/*Sollwert*/,s,elemof(s)));
  }break;
  
  case WM_QUERYOPEN: {
   TCHAR s[64];
   LoadString(ghInstance,1,s,elemof(s));
   SetWindowText(Wnd,s);
  }break;
  
  case WM_COPY: {
   HGLOBAL hMem=GlobalAlloc(GMEM_MOVEABLE,32*sizeof(TCHAR));
   if (!hMem) break;
   if (OpenClipboard(Wnd) && EmptyClipboard()) {
    DoubleToString(gSoll,gNK,(LPTSTR)GlobalLock(hMem),32);
    GlobalUnlock(hMem);
    SetClipboardData(CF_TXT,hMem);
    CloseClipboard();
   }else GlobalFree(hMem);
  }break;
  
  case WM_PASTE: if (OpenClipboard(Wnd)) {
   HANDLE h=GetClipboardData(CF_TXT);
   if (h) {
    double Soll;
    StringToDouble((LPCTSTR)GlobalLock(h),&Soll);
    GlobalUnlock(h);
    SetSoll(Soll);
   }
   CloseClipboard();
  }break;
  
  case WM_COMMAND:	// von TranslateAccelerator
  case WM_SYSCOMMAND: switch (wParam&0xFFF0) {
   case 0x40: if (DialogBox(ghInstance,MAKEINTRESOURCE(100),Wnd,HardwareDlgProc)==IDOK) {
    if (hCom) CloseHandle(hCom);
    InitCom();
   }break;
   case 0x50: SendMessage(Wnd,WM_COPY,0,0); break;
   case 0x60: SendMessage(Wnd,WM_PASTE,0,0); break;
   case 0x70: {
    TCHAR s1[32],s2[256];
    lstrcpyn(s1,T("--"),elemof(s1));
    lstrcpyn(s2,T("--"),elemof(s2));
    QueryDevice(s1,elemof(s1));
    QueryVersion(s2,elemof(s2));
    MBox(Wnd,(LPCTSTR)11,MB_OK|MB_ICONINFORMATION,s1,s2);
   }break;
  }break;
  
  case WM_TIMER: if (!QuerySoll()) SendPassword(); QueryIst(); break;	// Temperaturabfrage
  
  case WM_CLOSE: SendMessage(Wnd,WM_ENDSESSION,1,0); break;	// speichern auslösen!
  
  case WM_ENDSESSION: if (wParam) SaveConfig(); break;
  
  case WM_DESTROY: {
   if (hCom) CloseHandle(hCom);
   if (gBigFont) DeleteFont(gBigFont);
   if (gExFont) DeleteFont(gExFont);
   PostQuitMessage(0);
  }break;
 }
  
 return DefWindowProc(Wnd,Msg,wParam,lParam);
}

/******************************
 * Hauptprogramm (lächerlich) *
 ******************************/
 
int _stdcall WinMainCRTStartup(void) {
 ghInstance=GetModuleHandle(NULL);
 
 WNDCLASSEX wc;
 InitStruct(&wc,sizeof(wc));
  wc.style=CS_HREDRAW|CS_VREDRAW;
  wc.lpfnWndProc=MainWndProc;
  wc.hInstance=ghInstance;
  wc.hCursor=LoadCursor(0,IDC_ARROW);
  wc.hIcon=LoadIcon(ghInstance,MAKEINTRESOURCE(100));
  wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
  wc.lpszMenuName=MAKEINTRESOURCE(100);
  wc.lpszClassName=T("TecalNT");

 RegisterClassEx(&wc);
 InitCommonControls();		// wichtig für Teletubbieoptik!
 LoadString(ghInstance,1,StdMBoxTitle,elemof(StdMBoxTitle));
 ghMainWnd=CreateWindowEx(WS_EX_ACCEPTFILES|WS_EX_OVERLAPPEDWINDOW,
   T("TecalNT"),NULL,WS_OVERLAPPEDWINDOW,
   CW_USEDEFAULT,0,
   CW_USEDEFAULT,0,
   0,0,ghInstance,GetCommandLine());
 ShowWindow(ghMainWnd,SW_SHOWDEFAULT);
 
 CF_XlTable=RegisterClipboardFormat(T("XlTable"));	// Excel-Tabellenformat
 DdeInitialize(&gDdeInst,DdeCallback,APPCLASS_STANDARD
   |CBF_FAIL_EXECUTES
   |CBF_SKIP_CONNECT_CONFIRMS|CBF_SKIP_REGISTRATIONS
   |CBF_SKIP_DISCONNECTS|CBF_SKIP_UNREGISTRATIONS,0);
 hszService=DdeCreateStringHandle(gDdeInst,T("TecalNT"),CP_WINNEUTRAL);
 hszSoll=DdeCreateStringHandle(gDdeInst,T("Soll"),CP_WINNEUTRAL);
 DdeNameService(gDdeInst,hszService,0,DNS_REGISTER|DNS_FILTERON);
 
 MSG Msg;
 HACCEL hAccel=LoadAccelerators(ghInstance,MAKEINTRESOURCE(100));
 while (GetMessage(&Msg,0,0,0)) {
  if (TranslateAccelerator(ghMainWnd,hAccel,&Msg)) continue;
  TranslateMessage(&Msg);
  DispatchMessage(&Msg);
 }
 DdeFreeStringHandle(gDdeInst,hszSoll);
 DdeFreeStringHandle(gDdeInst,hszService);
 DdeUninitialize(gDdeInst);
 ExitProcess((UINT)Msg.wParam);
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded