Source file: /~heha/hs/UNI-T/dmm.zip/src/ut.cpp

#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include "dmm.h"
#include <stdio.h>

/*********************
 * inherited objects *
 *********************/

struct UTxxx:INDATA{
 DWORD collect(const BYTE BitTable[F_Phase1],char if0Beep=1,char if0Diode=1); 
 bool check(bool twice=false);	// common routine for odd parity of data,
	// and "\r\n" terminator (UT60G*, UT61E, UT70B*, UT71BCDE), * with repetition
 bool check(DWORD);
		// common routine for full or mostly 0x30..0x3F data
		// and "\r\n" terminator (UT61BCD, UT233, UT325)
};

struct UT60A:UTxxx{
 void _stdcall Handler();
 static char SevenSegToChar(const BYTE*, int&);
};

struct UT60G:UTxxx{
 void _stdcall Handler();
};

struct UT61B:UTxxx{
 void _stdcall Handler();
};

struct UT61E:UTxxx{
 void _stdcall Handler();
};

struct UT70B:UTxxx{
 void _stdcall Handler();
};

struct UT70D:UTxxx{
 void _stdcall Handler();
};

struct UT71B:UTxxx{
 void _stdcall Handler();
};

struct UT81B:UTxxx{
 void SmoothTrace();
 void Decode(const BYTE *src, DWORD f, READOUT&ro);
 void _stdcall Handler();
};

struct UT109:UTxxx{
 void _stdcall Handler();
};

struct UT233:UTxxx{
 static void Decode(const BYTE *src, char neg, char ovl, char u, READOUT&ro);
 void _stdcall Handler();
};

struct UT325:UTxxx{
 void _stdcall Handler();
};


/******************
 * Common helpers *
 ******************/

// Common collector for simpler UT multimeters (not for UT233)
DWORD UTxxx::collect(const BYTE BitTable[F_Phase1],char if0Beep,char if0Diode) {
 DWORD ret=CollectBits(rbuf,BitTable,F_Phase1);
 if (!if0Beep)  ret|=1<<F_Beep;
 if (!if0Diode) ret|=1<<F_Diode;
 return ret;
}

// Parity checker for a byte, returns <true> on odd parity, <false> on even parity
#ifdef _M_IX86
static bool _declspec(naked) _fastcall ParOddB(BYTE b) { _asm{
	or	cl,cl
	setnp	al
	ret
}}
#else
static bool ParOddB(BYTE b) {
 while (b&~1) b=b>>1^b&1;	// XOR all upper bits (if any) onto Bit 0
 return *(bool*)&b;
}
#endif

// checks for parity AND every character between 0x30 and 0x3F
bool UTxxx::check(bool twice) {
 if (rlen<rmax) {
  ok=true;			// await more data (should be done in reception routine!)
  return false;
 }
 if (twice) {
  int h=rmax>>1;		// rmax counts both packets (and must be even)
  if (memcmp(rbuf,rbuf+h,h)) return false;	// must contain the same data
 }
 rlen-=rmax;
 for (int i=0; i<rmax; i++) {
  BYTE b=rbuf[i];
  if (!ParOddB(b)) return false;
  b&=0x7F;			// remove parity bit
  rbuf[i]=b;
  if (i<rmax-2 && b>>4!=3) return false;
 }
 if (rbuf[rmax-2]!=0x0D) return false;
 if (rbuf[rmax-1]!=0x0A) return false;
 ok=true;
 return true;
}

// checks for character between 0x30 and 0x3F for <mask> positions
bool UTxxx::check(DWORD mask) {
 if (rlen!=rmax) return false;
 for (PBYTE p=rbuf; mask; mask>>=1,p++) {
  if (mask&1 && *p>>4!=3) return false;
 }
 if (rbuf[rmax-2]!=0x0D) return false;
 if (rbuf[rmax-1]!=0x0A) return false;
 return true;
}

/****************************
 * Decoding data for UT60AE *
 ****************************/

char UT60A::SevenSegToChar(const BYTE*p, int&z) {
 BYTE c=((p[0]<<4)+p[1])&0x7F;		// segment bits: 0 e f a d c g b
 static const BYTE segments[10]={	// possible combinations
//  0    1    2    3    4    5    6    7    8    9
  0x7D,0x05,0x5B,0x1F,0x27,0x3E,0x7E,0x15,0x7F,0x3F};
 switch (c) {
  case 0x68: z=-1; return 'L';		// make negative to indicate NaN
  case 0x67: z=-1; return 'H';
  case 0x00: return ' ';
  default: {
   BYTE*p=(BYTE*)memchr(segments,c,elemof(segments));
   if (!p) return '?';			// unknown combination! (Is a transmission error)
   int i=(int)(p-segments);
   z=z*10+i;				// calculate on integer value
   return i+'0';			// returns '0' to '9'
  }
 }
}

// Unter den Tisch gefallen ist die Bearbeitung der Wiederholungsdaten!
void UT60A::Handler() {
 switch (msg) {
  case init:{
   rmax=14;
   trto=5000;			// frequency measurement has longer delay
   counts=(int)lParam;		// 3999 (both)
   baud=2400;
  }break;

  case data:{
// Irgendetwas an der seriellen (nicht USB-) Schnittstelle vermurkst die Bytes
// mit den High-Nibbles D und E zu Nibbles F! Windows?
// Nein! Übersteuerungseffekt am Optokoppler! Verschwindet, wenn man den Adapter halb zieht!
// Nur beim PC in Chemnitz! Datenempfang streikt auch bei chinesischer Software.
   for (int j=0; j<rlen; j++) {
    if (rbuf[j]>>4 != j+1) return;	// check position nibble
   }
   ok=true;
   if (rlen<rmax) return;		// let concatenate
   for (int k=0; k<rmax; k++) {
    rbuf[k]&=0x0F;			// remove position nibble
   }
// Generate Number string - and decode number
   READOUT ro;
   char *p=ro.digits;
   char neg=rbuf[1]&8;
   char dppos=4;				// number of digits before decimal point
   int z=0;
   if (rbuf[1]&8) *p++='-';
   BYTE *q=rbuf;
   for (int i=0; i<4; i++) {
    q++;
    if (i && *q&8) *p++='.', dppos=i;
    *p++=SevenSegToChar(q,z);		// This multimeter does not suppress leading zeroes
    q++;
   }
   *p=0;
// Generate Unit string
   ro.unitprefix=0;				// no prefix
   if      (rbuf[9] &0x04) ro.unitprefix=-9;	// "n"
   else if (rbuf[9] &0x08) ro.unitprefix=-6;	// "µ"
   else if (rbuf[10]&0x08) ro.unitprefix=-3;	// "m"
   else if (rbuf[9] &0x02) ro.unitprefix=3;	// "k"
   else if (rbuf[10]&0x02) ro.unitprefix=6;	// "M"

   ro.unitcode=0;
   if      (rbuf[10]&0x04) ro.unitcode=9;	// "%"
   else if (rbuf[13]&0x01) ro.unitcode=6;	// °C
   else if (rbuf[11]&0x08) ro.unitcode=3;	// F
   else if (rbuf[12]&0x02) ro.unitcode=4;	// Hz
   else if (rbuf[11]&0x04) ro.unitcode=2;	// Ohm
   else if (rbuf[12]&0x08) ro.unitcode=5;	// A
   else if (rbuf[12]&0x04) ro.unitcode=1;	// V

// Now generate additional information
   static const BYTE BitTable[]={0140,0120,0110,0131,0377,0377,0130,0001,0377,0377,0377,0377,0377,0003,0002};
   DWORD features=collect(BitTable);

// Now generate double value for DDE
   dppos+=ro.unitprefix-4;
   if (z>=0) {	// regular value scanned
    ro.value=e10(neg,z,dppos);
   }else ro.value=NaN;

// Auto-generate min/max values and send data to window and DDE
   GenMinMax(&ro,features,counts,dppos);
   ro.ec=ro.ev=NaN;	// not set (unknown) for now
   SetData(features,&ro);
   rlen-=rmax;
   RtlMoveMemory(rbuf,rbuf+rmax,rlen);	// keep additional data
  }break;
 }
}

/*****************************************************
 * Decoding data for UT60F, UT60G (similar to UT70B) *
 *****************************************************/

void UT60G::Handler() {
 switch (msg) {
  case init:{
   rmax=22;
   counts=(int)lParam;		// 6000 (UT60G), 4000 (UT60F)
   baud=19200;
  }break;

  case data:{
   if (!check(true)) return;
   char mode=rbuf[5]&=0x0F;
   static const char modeunit[16]= {0,0x01,0x04,0x02,0x06,0x02,0x03,0x00,0x00,0x05,0,0x01,0,0x05,0x00,0x05};
   static const char modescale[16]={0,0x0F,0x12,0x11,0x00,0x11,0x06,0x00,0x00,0x10,0,0x2F,0,0x2B,0x00,0x0D};
   READOUT ro;
   ro.unitcode=modeunit[mode];
   char scalebase=modescale[mode];
   char scaleadd=rbuf[0]&0x07;
   if (mode==2 && scaleadd==0) scalebase=0;	// handle 6000 Hz case
   if (mode==11 && scaleadd==4) scalebase=0x0E-4;	// handle 600 mV case
   char dppos=dp(scalebase,scaleadd,&ro.unitprefix);

// Now generate string (and detect singularities +INF, -INF, NAN, TODO!)
   char *p=ro.digits;
   char neg=rbuf[6]&4;
   char ovl=rbuf[6]&1;
   if (neg) *p++='-';
   char*numbers=(char*)rbuf+1;
   if (ovl) numbers=" 0L  ";	// on overflow, replace numbers (these are wrong)
   bool passdigit=false;		// Suppress leading zeroes
   for (int i=0; i<4; i++) {
    if (i==dppos) *p++='.';
    char c=numbers[i];
    if (i>=dppos-1 || c!='0') passdigit=true;
    if (passdigit) *p++=c;
   }
   *p=0;

// Now generate additional information
   static const BYTE BitTable[]={0061,0377,0377,0377,0377,0377,0073,0101,0377,0377,0377,0377,0377,0102,0103};
   DWORD features=collect(BitTable,mode-5,mode-1);

// Now generate double value for DDE
   if (scalebase) scalebase=(scalebase&0x1F)-0x12;
   scalebase+=scaleadd;
   if (ovl) ro.value=NaN;
   else{				// regular value
    int z=strtoul(numbers,NULL,10);
    ro.value=e10(neg,z,scalebase);
   }

   GenMinMax(&ro,features,counts,scalebase);
   SetData(features,&ro);
  }break;
 }
}

/*****************************
 * Decoding data for UT61BCD *
 *****************************/

void UT61B::Handler() {
 switch (msg) {
  case init:{
   rmax=14;
   counts=(int)lParam;		// 4000 (UT61B), 6000 (other)
   baud=2400;
  }break;

  case data:{
// This multimeter doesn't have a 100% save way to detect packet delimiters,
// a "\r\n" can be, with negative luck, a valid data sequence.
// So use protocol gaps to help resynchronizing.
   if (!check(0x5Eul)) return;
   char dppos=rbuf[6]&0x0F;	// calculate dppos as "number of digits before decimal point"
   switch (dppos) {
    case 4: dppos--; break;
    case 0: dppos=4; break;	// assume (but don't emit later) decimal point after last digit
   }
// Generate Number string, this multimeter does not suppress leading zeroes
   READOUT ro;
   char *p=ro.digits;
   if (rbuf[0]=='-') *p++='-';	// don't prepend '+' sign to DisplayW output
   for (int i=0; i<4; i++) {
    if (i==dppos) *p++='.';
    char z=rbuf[i+1];
    switch (z) {
     case '?': z=' '; break;	// space character
     case ':': z='L'; break;	// for "overflow" (OL) and "Low" (LO) display
    }
    *p++=z;
   }
   *p=0;
// Generate Unit string
   ro.unitprefix=0;			// no prefix
   if (rbuf[8]&0x02) ro.unitprefix=-9;	// "n"
   else switch (rbuf[9]>>4) {	// should only be one bit set!
    case 1: ro.unitprefix=6; break;	// "M"
    case 2: ro.unitprefix=3; break;	// "k"
    case 4: ro.unitprefix=-3; break;	// "m"
    case 8: ro.unitprefix=-6; break;	// "µ"
   }
   ro.unitcode=0;
   if (rbuf[9]&0x02) ro.unitcode=9;	// "%"
   else switch (rbuf[10]) {		// should only be one bit set!
    case 0x01: ro.unitcode=7; break;	// °F
    case 0x02: ro.unitcode=6; break;	// °C
    case 0x04: ro.unitcode=3; break;	// F
    case 0x08: ro.unitcode=4; break;	// Hz
    case 0x20: ro.unitcode=2; break;	// Ohm
    case 0x40: ro.unitcode=5; break;	// A
    case 0x80: ro.unitcode=1; break;	// V
   }

// Now generate additional information
   static const BYTE BitTable[]={0102,0113,0112,0072,0377,0377,0071,0075,0377,0377,0377,0104,0105,0073,0074};
   DWORD features=collect(BitTable);

// Now generate double value for DDE
   unsigned char *endp;
   dppos+=ro.unitprefix-4;
   int z=strtol((char*)rbuf,(char**)&endp,10);
   if (endp-rbuf==5) {	// regular value scanned
    ro.value=e10(false,z,dppos);
   }else ro.value=NaN;

// Auto-generate min/max values and send data to window and DDE
   GenMinMax(&ro,features,counts,dppos);
   SetData(features,&ro);
   rlen=0;
   ok=true;
  }break;
 }
}

/***************************
 * Decoding data for UT61E *
 ***************************/

void UT61E::Handler() {
 switch (msg) {
  case init:{
   rmax=14;
   counts=(int)lParam;		// 22000
   baud=19200;
  }break;

  case data:{
   if (!check()) return;

   char mode=rbuf[6]&=0x0F;	// also, "terminate" string for later strtoul()
   if (rbuf[10]&1) mode=2;	// Use frequency or duty-cycle
   if (rbuf[7]&8) mode=4;	// "%" display (mode 4 is unused by UT60E)
				//   A  Diode  Hz  Ohm   %  Pieps   F  - - - - V/mV -  µA  -  mA
   static const char modeunit[16] ={0x05,0x01,0x04,0x02,0x09,0x02,0x03,0,0,0,0,0x01,0,0x05,0,0x05};
   static const char modescale[16]={0x10,0x0F,0x31,0x11,0x00,0x11,0x07,0,0,0,0,0x4E,0,0x2B,0,0x0D};
   READOUT ro;
   ro.unitcode=modeunit[mode];
   char scalebase=modescale[mode];
   char scaleadd=rbuf[0]&0x07;
   switch (mode) {
    case 2: {	// Hz
     if (scaleadd>=2) scalebase=0x10;		// skip scaleadd==2, all prefixes allowed
    }break;
    case 4: {	// %
     scaleadd=-1;
    }break;
    case 0x0B: {	// V and mV: make a useful scaleadd value
     if (scaleadd==4/*mV*/) scaleadd=0; else scaleadd++;
    }break;
   }
   char dppos=dp(scalebase,scaleadd,&ro.unitprefix);

// Now generate string (and detect singularities +INF, -INF, NAN, TODO!)
   char *p=ro.digits;
   char neg=rbuf[7]&4;
   char ovl=rbuf[7]&1;
   if (neg) *p++='-';
   char*numbers=(char*)rbuf+1;
   if (ovl) numbers=" 0L  ";	// on overflow, replace numbers (these are wrong)
   else if (rbuf[9]&8) {
    numbers=" UL  ";		// on "UL" display, numbers are wrong
    ovl=true;			// generate NaN later
   }
   bool passdigit=false;		// Suppress leading zeroes - The multimeter does, the UT61E.exe does not.
   for (int i=0; i<5; i++) {
    if (i==dppos) *p++='.';
    char c=numbers[i];
    if (i>=dppos-1 || c!='0') passdigit=true;
    if (passdigit) *p++=c;
   }
   *p=0;

// Now generate additional information
   static const BYTE BitTable[]={0071,0377,0377,0101,0377,0377,0131,0121,0377,0377,0377,0111,0112,0122,0123};
   DWORD features=collect(BitTable,mode-5,mode-1);

// Now generate double value for DDE
   if (scalebase) scalebase=(scalebase&0x1F)-0x13;
   scalebase+=scaleadd;
   if (ovl) ro.value=NaN;
   else{				// regular value
    int z=strtoul(numbers,NULL,10);
    ro.value=e10(neg,z,scalebase);
   }

   GenMinMax(&ro,features,counts,scalebase);
   SetData(features,&ro);
  }break;
 }
}

/*****************************************************
 * Decoding data for UT70B (similar to UT71B), UT803 *
 *****************************************************/
// BUG (possibly hardware bug at UT803:)
// HE2325U seems to swallow one byte somewhere!
// Don't know how UNI-T's software UT803.exe handles this.

void UT70B::Handler() {
 switch (msg) {
  case init:{
   rmax=22;
   counts=(int)lParam;		// 4000 (UT70B), 6000 (UT803)
   baud=counts>4000?19200:2400;
   trto=6000;		// very low rate for 6mF range
  }break;

  case data:{
   bool dbl=true;
   if (rlen==21) {		// "Correct" such packets
    if (rbuf[9]==0x0D && rbuf[10]==0x8A) {		// first copy seems OK
     rmax=rlen=11; dbl=false;
    }else if (rbuf[19]==0x0D && rbuf[20]==0x8A) {	// second copy seems OK
     memmove(rbuf,rbuf+10,11);
     rmax=rlen=11; dbl=false;
    }
   }
   if (!check(dbl)) {rmax=22; return;}
   rmax=22;

   char mode=rbuf[5]&=0x0F;	// "terminate" string for later strtoul()

   if (rbuf[6]&0x08) switch (mode) {
    case 2: if (counts==4000) mode=7; break;	// switch Hz (2) to 1/min (7, unused)
    case 4: mode=8; break;	// switch °F (4) to °C (8, unused)
    case 9: if (counts>4000) mode=15; break;
    case 15: if (counts>4000) mode=9; break;	// swap mA and A for UT803
   }
				//    Diode Hz   Ohm  °F  Beep  F   RPM   °C   mA     V      µA  hFE    A
   static const char modeunit[16]= {0,0x01,0x04,0x02,0x07,0x02,0x03,0x0D,0x06,0x05,0,0x01,0,0x05,0x00,0x05};
   static const char modescale[16]={0,0x0F,0x12,0x11,0x00,0x11,0x06,0x13,0x00,0x0D,0,0x0E,0,0x2B,0x00,0x10};
   READOUT ro;
   ro.unitcode=modeunit[mode];
   char scalebase=modescale[mode];
   char scaleadd=rbuf[0]&0x07;
   switch (ro.unitcode) {
    case 0x3D: scalebase=0x2B; break;	// 400.00 µA, never mA
    case 0x09: scalebase=0x0D; break;	// 40.000 mA
   }
   if (counts>4000) {
    if (mode==11) {	// Special handling for voltage range of UT803
     scalebase=0x2F; if (scaleadd==4) scalebase=0x0A;
    }
    if (mode==2 && scaleadd==0) scalebase=0;	// Special handling of lowest frequency range
   }
   char dppos=dp(scalebase,scaleadd,&ro.unitprefix);

// Now generate string (and detect singularities +INF, -INF, NAN, TODO!)
   char *p=ro.digits;
   char neg=rbuf[6]&4;
   char ovl=rbuf[6]&1;
   if (neg) *p++='-';
   char*numbers=(char*)rbuf+1;
   if (ovl) numbers=" 0L  ";	// on overflow, replace numbers (these are wrong)
   bool passdigit=false;		// Suppress leading zeroes
   for (int i=0; i<4; i++) {
    if (i==dppos) *p++='.';
    char c=numbers[i];
    if (i>=dppos-1 || c!='0') passdigit=true;
    if (passdigit) *p++=c;
   }
   *p=0;

// Now generate additional information
//				LoBat Beep Diode Delta Sigma Beta Hold Auto hFE  EF   Man  Min  Max  AC   DC
   static const BYTE BitTable[]={0061,0377,0377, 0377, 0377, 0377,0073,0101,0377,0377,0377,0071,0072,0102,0103};
   DWORD features=collect(BitTable,mode-5,mode-1);
   if (mode==14) features|=1<<F_Beta;

// Now generate double value for DDE
   if (scalebase) scalebase=(scalebase&0x1F)-0x12;
   scalebase+=scaleadd;
   if (ovl) ro.value=NaN;
   else{				// regular value
    int z=strtoul(numbers,NULL,10);
    ro.value=e10(neg,z,scalebase);
   }

   GenMinMax(&ro,features,counts,scalebase);
   SetData(features,&ro);
  }break;
 }
}

/***************************
 * Decoding data for UT70D *
 ***************************/

void UT70D::Handler() {
 switch (msg) {
  case init:{
   rmax=12;
   icto=100;
   counts=(int)lParam;		// 8000 (but 99999 for frequency and conductance)
   //baud=9600;
  }break;

  case data:{
// This multimeter both has a bi-directional interface
// and a clean packet delimiter detection
   if (rlen!=rmax) goto rep;
   if (rbuf[11]!=0x0A) goto rep;
   for (int j=0; j<11; j++) {
    if (!(rbuf[j]&0xF0)) goto rep;	// there must be upper bits
   }

   char mode=rbuf[1]&0x7F;
   if (mode==0x61) mode=0;	// "F"
   mode>>=3;
   switch (rbuf[2]&0x07) {
    case 4: mode=2; break;	// "Hz - 5 digits"
    case 5: mode=1; break;	// "%"
   }
				//    F   %    Hz  - -   A   mA  - - - - Diode Ohm  mV   V=   V~ 
   static const char modeunit[16]= {0x03,0x09,0x04,0,0,0x05,0x05,0,0,0,0,0x01,0x02,0x01,0x01,0x01};
   static const char modescale[16]={0x06,0x0F,0x10,0,0,0x0F,0x0D,0,0,0,0,0x0F,0x11,0x0D,0x2F,0x4E};
   READOUT ro;
   ro.unitcode=modeunit[mode];
   char scalebase=modescale[mode];
   char scaleadd=rbuf[2]>>3&0x07;
   if (ro.unitcode==2 && scaleadd==6) {	// "Ohm" changes to "nanoSiemens"
    ro.unitcode=10;	// "S"
    scalebase=1;
   }
   char dppos=dp(scalebase,scaleadd,&ro.unitprefix);

// Now generate string (and detect singularities +INF, -INF, NAN, TODO!)
   char *p=ro.digits;
   char neg=rbuf[4]&0x10;
   if (neg) *p++='-';
   char*numbers=(char*)rbuf+5;
   char digits=5;
   if (*numbers==0x3F) {numbers++; digits--;}
   for (int i=0; i<digits; i++) {
    if (i==dppos) *p++='.';
    char c=numbers[i];
    switch (c) {
     case 0x3F: c=' '; break;
     case 0x3E: c='L'; break;
    }
    *p++=c;
   }
   *p=0;

// Now generate additional information
   static const BYTE BitTable[]={0377,0032,0377,0377,0377,0377,0040,0026,0377,0377,0377,0377,0377,0377,0377};
   DWORD f=collect(BitTable,1,mode-11);
   f^=1<<F_Auto;
   switch (rbuf[1]) {
    case 0xF8:
    case 0xA9:
    case 0xB1: f|=1<<F_AC; break;
    case 0xF0:
    case 0xA8:
    case 0xB0: f|=1<<F_DC; break;
   }
// AddFeatures(f);

// Now generate double value for DDE
   if (scalebase) scalebase=(scalebase&0x1F)-0x12;
   scalebase+=scaleadd;
   rbuf[10]=0;			// terminate string deleting the CRC/bargraph?? value
   char*endptr;
   int z=strtoul(numbers,&endptr,10);
   if (*endptr) ro.value=NaN;
   else ro.value=e10(neg,z,scalebase);		// regular value

   GenMinMax(&ro,f,counts,scalebase);
   SetData(f,&ro);
   rlen=0;
   ok=true;
  }nobreak;

  case tout:
  case open:{
rep:
   SendBytes((const BYTE*)"\x89",1);
  }break;

 }
}


/*************************************
 * Decoding data for UT71BCDE, UT804 *
 *************************************/

// On complete data block received:
void UT71B::Handler() {
 switch (msg) {
  case init:{
   rmax=11;
   counts=(int)lParam;	// 20000 (UT71B), 40000 (all other)
   baud=2400;
  }break;

  case data:{
   if (!check()) return;

   char mode=rbuf[6]&0x0F;
//				    mV~  V=   V~   mV=  Ohm   F   °C   µA   mA   A   Beep Diode Hz   °F    W    %
   static const char modeunit[16]= {0x01,0x01,0x01,0x01,0x02,0x03,0x06,0x05,0x05,0x05,0x02,0x01,0x04,0x07,0x08,0x09};
   static const char modescale[16]={0x4E,0x4E,0x4E,0x4E,0x10,0x06,0x00,0x2B,0x0D,0x0F,0x11,0x0F,0x10,0x00,0x00,0x11};
   READOUT ro;
   ro.unitcode=modeunit[mode];
   char scalebase=modescale[mode];
   char scaleadd=rbuf[5]&0x07;
   if (mode==12 && rbuf[8]&4) {
    ro.unitcode=9;
    scalebase=0x11;
   }
   char dppos=dp(scalebase,scaleadd,&ro.unitprefix);

   char neg=rbuf[8]&4;	// negative sign?
   if (mode==12) neg=0;		// Don't show it!

// Now generate string (and detect singularities +INF, -INF, NAN, TODO!)
   char *p=ro.digits;
   if (neg) *p++='-';
   bool letter=false;
   for (int i=0; i<5; i++) {
    if (i==dppos) *p++='.';
    char z=rbuf[i];
    switch (z) {	// Substitute space to zero for four-digit mode, for later DDE value extraction
     case 0x3A: if (i==4) {rbuf[4]='0'; goto fourdigit;} z=' '; break;
     case 0x3B: z='-'; break;
     case 0x3C: z='L'; letter=true; break;// for "overflow" (0L) and "Low" (LO) display
     case 0x3F: z='H'; letter=true; break;// for "High" (HI) display
     case '0': if (letter) z='O'; break;	// for "LO" (not "L0")
     case '1': if (letter) z='I'; break;	// for "LI" (not "H1")
    }
    *p++=z;
   }
fourdigit:
   *p=0;

// Now generate additional information
   static const BYTE BitTable[]={0377,0377,0377,0377,0377,0377,0377,0100,0377,0377,0377,0377,0377,0070,0071};
   DWORD f=collect(BitTable,mode-10,mode-11);
   if (ro.unitcode==9) f&=~(1<<F_Auto);	// remove "auto" when measuring duty cycle
   switch (mode) {
    case 1:
    case 3:
    case 7:
    case 8:
    case 9: if (!(f&1<<F_AC)) f|=1<<F_DC; break;		// add DC attribute
   }

// Now generate double value for DDE
   if (scalebase) scalebase=(scalebase&0x1F)-0x12;
   scalebase+=scaleadd-1;
   rbuf[5]=0;
   char *endp;
   int z=strtoul((char*)rbuf,&endp,10);
   if (*endp) ro.value=NaN;	// TODO: -INF, +INF
   else ro.value=e10(neg,z,scalebase);

   GenMinMax(&ro,f,counts,scalebase);
   SetData(f,&ro);
  }break;
 }
}

/******************************************
 * Decoding data for UT81AB (scope meter) *
 ******************************************/

// Half the samples
void UT81B::SmoothTrace() {
 char *s=(char*)(rbuf+41);
 char *d=s;
 if (!rbuf[15]) s++;	// The trigger edge seems to dictate which samples should be used.
 for (int i=0; i<160; i++) {
//  *d++=(*s+++*s++)/2;	// The UT81B software simply halfs the samples for smoothing!
  *d++=*s;
  s+=2;
 }
}

// fill one READOUT structure
void UT81B::Decode(const BYTE *src, DWORD f, READOUT&ro) {
 ro.value=strtod((char*)src,NULL);
 char c, *p=ro.digits, dppos=0;
 while (*src==' ') src++;	// skip leading spaces
 for (char i=0;;i++) {		// copy characters
  c=*src++;
  if (c=='.') dppos=i;		// for min/max calculation (only Hz)
  else if (c=='L') ro.value=NaN;
  else if (c=='-');		// copy sign or dash
  else if (c<'0' || '9'<c) break;
  *p++=c;
 }
 while (c==' ') c=*src++;	// skip trailing spaces
 *p=0;
 ro.unitprefix=0;
 ro.unitcode=0;
 if (c) {
  static const char translateprefix[]="num kM";
  p=strchr((char*)translateprefix,c);
  if (p) ro.unitprefix=int(p-translateprefix)*3-9, c=*src; // take next character for unit
  static const char translateunit[]="V?FHAVVV/V";
  p=strchr((char*)translateunit,c);
  if (p) ro.unitcode=char(p-translateunit+1);
 }
 ro.value*=e10(false,1,ro.unitprefix);
 char expo;
 switch (ro.unitcode) {		// recalculate exponent for range
  case 1: expo=rbuf[11]/3-4; break;	// V
  case 2: expo=rbuf[18]-1; break;		// Ohm
  case 5: expo=rbuf[11]/3-7; break;	// A
  default: expo=ro.unitprefix+dppos-3;		// Hz,F (estimated)
   if (ro.digits[0]=='-' && ro.digits[1]<'4' || ro.digits[0]<'4') expo--;
 }
 GenMinMax(&ro,f,counts,expo);
}

void UT81B::Handler() {
 switch (msg) {
  case init:{
   trto=5000;
   counts=(int)lParam;		// 4000
   //baud=9600;
  }break;

  case data:{
// This scope multimeter uses true 8-bit data,
// so packet delimiter detection is done using byte sending routine?
   if (rbuf[0]!=0x5A) goto rep;		// echo
   if (rbuf[1]!=0x00) goto rep;		// terminator for echo
   if (rbuf[30]!=0x00) goto rep;	// terminator for left readout
   if (rbuf[39]!=0x00) goto rep;	// terminator for right readout
   char scopemode=rbuf[6]&0x80;
   if (scopemode) {
    if (rlen<41+320) {			// there are some additional bytes at large amplitudes!
     ok=true;				// collect more data
     return;
    }
   }else{
    if (rlen!=41) goto rep;
   }

// Generate additional information
   char mode=rbuf[6]&0x7F;
   static const BYTE BitTable[]={0377,0377,0377,0240,0377,0377,0070,0230,0377,0377,0377,0377,0377,0120,0377};
   DWORD f=collect(BitTable,mode-5,mode-6);
   f^=1<<F_Hold;
   switch (mode) {
    case 0:
    case 1: if (!(f&1<<F_AC)) f|=1<<F_DC; break;		// add DC attribute
   }
   READOUT ro[2];
   int NumReadout=1;
   Decode(rbuf+21,f,ro[0]);
   if (rbuf[33]!='-') {
    Decode(rbuf+31,f,ro[1]);
    NumReadout++;
   }
   struct{
    SCOPEDATA sd;
    TRACEDATA td;
   }scope={{1,1, 8,8, 160/*320*/,128}};		// most SCOPEDATA values are constants
   if (scopemode) {
    scope.sd.timebase=(char)rbuf[13]-27;
    scope.sd.autorange=(char)rbuf[9];
    scope.sd.tx=(char)rbuf[14]+80;	// ± 76 from UT81B, center to the trace data (left=0)
    scope.sd.ty=(char)rbuf[16];
    scope.sd.tsource=rbuf[15]<<7/*edge*/|rbuf[17]<<4/*mode*/;
    scope.td.devicode=(char)rbuf[11]-(mode?14:5);
    scope.td.autorange=(char)rbuf[8];
    scope.td.unitcode=mode==1?5:1;	// A, else V (never Hz)
    scope.td.ofs=(char)rbuf[12];
    scope.td.data=rbuf+41;
    SmoothTrace();
   }
   SetData(f,ro,NumReadout,scopemode?&scope.sd:NULL);
   rlen=0;
   ok=true;
  }nobreak;

  case tout:
  case open:{
rep:
   SendBytes((const BYTE*)"Z",2);
  }break;

 }
}

/***********************************************************
 * Decoding data for UT108 / UT109 (automotive multimeter) *
 ***********************************************************/

void UT109::Handler() {
 switch (msg) {
  case init:{
   rmax=13;
   counts=(int)lParam;		// 3999
  }break;

  case data:{
   if (!check(0x7FFul)) return;

   char mode=rbuf[0]&0x0F;
   if (mode>8) {ok=false; return;}
   char bsk=rbuf[1]&0x0F;	// blue shift key
   if (bsk>2) {ok=false; return;}
   bool AC=false;
//				    V    mV  Ohm Diode  mA   µA   A    RPM  Hz
   static const char modeunit0[9]={0x01,0x01,0x02,0x01,0x05,0x05,0x05,0x0D,0x04};
   static const char modescale[9]={0x2F,0x2D,0x11,0x2F,0x2D,0x2B,0x2F,0x00,0x10};
   READOUT ro;
   ro.unitcode=modeunit0[mode];
   char scalebase=modescale[mode];
   char scaleadd=rbuf[2]&0x07;
   if (mode==1) scaleadd^=1;	// reverse order
   if (mode==7) scaleadd=0;

   if (bsk) switch (mode) {
    case 1:{
     scaleadd=0;
     scalebase=0;
     ro.unitcode=bsk+5;	// °C or °F
    }break;
    case 2:{
     scalebase=0x0C;	// Beep [Ohm]
     if (bsk==2) {
      ro.unitcode=3;	// Capacity [F]
      scalebase=0x07;
     }
    }break;
    case 7:{
     scalebase=0x11;
     ro.unitcode=9;	// Dwell [%]
    }break;
    default: AC=true;
   }

   char dppos=dp(scalebase,scaleadd,&ro.unitprefix);

// Now generate string (and detect singularities +INF, -INF, NAN)
   char *p=ro.digits;
   if (rbuf[8]&4) *p++='-';
   for (int i=0; i<4; i++) {
    if (i==dppos) *p++='.';
    char z=rbuf[i+3];
    if (rbuf[8]&1) z=" OL "[i];	// OVL? Replace "4020" by " OL " and insert decimal point
    *p++=z;
   }
   if (mode==7 && bsk==0) *p++='0';	// add zero for "RPMx10"
   *p=0;

// Now generate additional information
   static const BYTE BitTable[]={0101,0377,0377,0377,0377,0377,0113,0110,0377,0377,0377,0111,0112,0377,0377};
   DWORD f=collect(BitTable,-1,mode-3);
   if (mode==2 && bsk==1) f|=1<<F_Beep;
   switch (mode) {
    case 0:
    case 1:
    case 4:
    case 5:
    case 6: f |= AC ? 1<<F_AC : 1<<F_DC; break;
   }

// Now generate double value for DDE
   if (scalebase) scalebase=(scalebase&0x1F)-0x12;
   scalebase+=scaleadd;
   rbuf[7]=0;
   char *endp;
   int z=strtoul((char*)rbuf+3,&endp,10);
   if (mode==7 && bsk==1) z*=10;
   if (*endp || rbuf[8]&1) ro.value=NaN;
   else ro.value=e10(rbuf[8]&4,z,scalebase);

   GenMinMax(&ro,f,counts,scalebase);
   SetData(f,&ro);
   rlen=0;
   ok=true;
  }break;
 }
}

/********************************************
 * Decoding data for UT233 (clamp-on meter) *
 ********************************************/

void UT233::Decode(const BYTE *src, char neg, char ovl, char u, READOUT&ro) {
 char *p=ro.digits, dppos=src[4]&7, i=4;
 if (neg) *p++='-';
 for(;;){
  *p++=*src++;
  if (!--i) break;
  if (i==dppos) *p++='.';
  if (i==2 && dppos==4) *p++=':';
 }
 *p=0;
 ro.unitprefix= u==8 || 15<=u && u<20 ? 3 : 0;	// W,VA,var,Wh,VAh,varh -> kW, etc.
 ro.unitcode=u==5?1:u;	// let GenMinMax limit to 1000 A, not 10 A as for other multimeters
 ro.value=strtod(ro.digits,NULL)*e10(0,1,ro.unitprefix);
 GenMinMax(&ro,1<<F_AC,9999,ro.unitprefix-dppos);
 ro.unitcode=u;
}

void UT233::Handler() {
 switch (msg) {
  case init:{
   rmax=26;
   counts=(int)lParam;		// 9999
   baud=2400;
  }break;

  case data:{
   if (!check(0xFFFFFFul)) return;

// The bytes 1,2 (range) and 18,19 (memory position) are not evaluated.
   READOUT ro[3];
   int modus=rbuf[0]&7;
   static const char unitcodes[8]={8,15,16,0,20,4};
   Decode(rbuf+3,rbuf[20]&4,rbuf[21]&2,unitcodes[modus],ro[0]);
   Decode(rbuf+8,0,rbuf[21]&1,1,ro[1]);
   Decode(rbuf+13,0,rbuf[20]&2,5,ro[2]);
   DWORD f;
   static const BYTE BitTable[]={
    0240,0377,0377,0377,0270,0377,0260,0377,0377,0377,0377,0253,0252,0377,0377,0273,0272,0271};
   f=CollectBits(rbuf,BitTable,sizeof(BitTable));
   switch (modus) {
    case 3: f|=1<<F_cosphi; break;
    case 4: f|=1<<F_phi; break;
   }
   SetData(f,ro,elemof(ro));
   rlen=0;
   ok=true;
  }break;
 }
}

/**********************************************
 * Decoding data for UT325 (dual thermometer) *
 **********************************************/

void UT325::Handler() {
 switch (msg) {
  case init:{
   rmax=19;
   counts=(int)lParam;		// 9999 (maybe)
   baud=2400;
  }break;

  case data:{
   int was_rlen=rlen;
   if (rlen>rmax) rlen=rmax;
   if (!check(0x13FFFul)) return;
// static const char couples[]="KJTENRS";	// list of thermocouples, to be shown for FLAGS!
   READOUT ro[4];
   ZeroMemory(ro,sizeof(ro));
// ReadOut[0] = first temperature
   char *p=ro[0].digits;
   int i;
   for (i=1;i<5;i++) {
    char c=rbuf[i];
    switch (c) {
     case 0x3A: c=' '; break;
     case 0x3B: c='-'; break;
    }
    *p++=c;
    if (i==3 && rbuf[0]&2) *p++='.';
   }
   *p=0;
   static const char unitcodes[4]={0,6,7,12};
   ro[0].unitcode=unitcodes[rbuf[5]&3];
   static const char d1[]="T1\0T2\0T1-T2\0T1-T2";
   p=(char*)d1;
   for (i=rbuf[13]&3;i;i--) p+=lstrlenA(p)+1;
   lstrcpyA(ro[0].desc,p);
   ro[0].min=-200;	// TODO: see data sheet, depends on unit and thermocouple
   ro[0].max=1600;
   ro[0].value=strtod(ro[0].digits,&p);
   if (*p) ro[0].value=NaN;
// ReadOut[1] = memory number ("00".."99")
   ro[1].digits[0]=rbuf[6];
   ro[1].digits[1]=rbuf[7];
   ro[1].value=atoi(ro[1].digits);
// ReadOut[2] = time ("00:00".."99:59")
   ro[2].digits[0]=rbuf[11];
   ro[2].digits[1]=rbuf[12];
   ro[2].digits[2]=':';
   ro[2].digits[3]=rbuf[9];
   ro[2].digits[4]=rbuf[10];
   ro[2].value=atoi(ro[2].digits)*60+atoi(ro[2].digits+3);
   ro[2].unitcode=14;
// ReadOut[3] = second temperature (very unprecise)
   if (rbuf[15]) {
    int t=*(short*)(rbuf+14);
    if (t>=0) t-=15697;
    else t=-(t+16942);
    sprintf(ro[3].digits,"%.1f",ro[3].value=t*0.05);
    ro[3].unitcode=6;	// vorläufig °C
   }else{
    ro[3].value=NaN;   
   }
   ro[3].min=-200;
   ro[3].max=1600;
   ro[3].desc[0]='T';
   ro[3].desc[1]='2'-((rbuf[13]^rbuf[13]>>1)&1);
   SetData(0,ro,4);
   rlen=was_rlen-rmax;
   RtlMoveMemory(rbuf,rbuf+rmax,rlen);
   ok=true;
  }break;

//  case tout:
//  case open:{
//   SendBytes((const BYTE*)"A",1);
//  }break;
 }
}

/*******************************
 * Multimeter Info enumeration *
 *******************************/

bool _stdcall EnumUT(int n, ENUMINFO* ei) {
 static const struct MMINFO{	// information structure for a particular multimeter
  short model;		// The model number (negative for Voltcraft)
  char suffix;		// The model suffix ('A'..'G')
  char iconidx;		// An index for type of multimeter,
			// 0=hand, 1=with scope, 2=desk, 3,4=Voltcraft, 5=clamp-on
  unsigned short counts;
  void (_stdcall INDATA::*pHandler)();
 }Builtin[]={
  {60,'A', 0, 3999,(void(_stdcall INDATA::*)())&UT60A::Handler},
  {60,'E', 0, 3999,(void(_stdcall INDATA::*)())&UT60A::Handler},
  {60,'F', 0, 4000,(void(_stdcall INDATA::*)())&UT60G::Handler},
  {60,'G', 0, 6000,(void(_stdcall INDATA::*)())&UT60G::Handler},
  {61,'B', 0, 4000,(void(_stdcall INDATA::*)())&UT61B::Handler},
  {61,'C', 0, 6000,(void(_stdcall INDATA::*)())&UT61B::Handler},
  {61,'D', 0, 6000,(void(_stdcall INDATA::*)())&UT61B::Handler},
  {61,'E', 0,22000,(void(_stdcall INDATA::*)())&UT61E::Handler},
  {70,'B', 0, 4000,(void(_stdcall INDATA::*)())&UT70B::Handler},
  {70,'D', 0, 8000,(void(_stdcall INDATA::*)())&UT70D::Handler},
  {71,'A', 0,20000,(void(_stdcall INDATA::*)())&UT71B::Handler},
  {71,'B', 0,20000,(void(_stdcall INDATA::*)())&UT71B::Handler},
  {71,'C', 0,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},
  {71,'D', 0,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},
  {71,'E', 0,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},
  {81,'A', 1, 4000,(void(_stdcall INDATA::*)())&UT81B::Handler},
  {81,'B', 1, 4000,(void(_stdcall INDATA::*)())&UT81B::Handler},
  {108,0,  0, 3999,(void(_stdcall INDATA::*)())&UT109::Handler},
  {109,0,  0, 3999,(void(_stdcall INDATA::*)())&UT109::Handler},
  {233,0,  5, 9999,(void(_stdcall INDATA::*)())&UT233::Handler},
  {325,0,  0, 9999,(void(_stdcall INDATA::*)())&UT325::Handler},
  {803,0,  2, 6000,(void(_stdcall INDATA::*)())&UT70B::Handler},
  {804,0,  2,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},
  {-820,0, 3, 3999,(void(_stdcall INDATA::*)())&UT60A::Handler},	// UT60A
  {-840,0, 3, 3999,(void(_stdcall INDATA::*)())&UT60A::Handler},	// UT60E
  {-920,0, 3,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},	// UT71C
  {-940,0, 3,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},	// UT71E
  {-960,0, 3,40000,(void(_stdcall INDATA::*)())&UT71B::Handler},	// UT71D
  {-1008,0,4, 4000,(void(_stdcall INDATA::*)())&UT81B::Handler},	// UT81B
 };
 if ((unsigned)n>elemof(Builtin)) return false;
 if (n==elemof(Builtin)) {
  lstrcpyA(ei->ManufName,"Vichy");
  lstrcpyA(ei->ModelName,"VC99");	// it's UT61C
  ei->IconIndex=0;
  ei->ExtraAlloc=0;
  ei->pHandler=(void(_stdcall INDATA::*)())&UT61B::Handler;
  ei->lParam=6000;
 }else{
  const MMINFO *mmi=Builtin+n;
  if (mmi->model>=0) {
   lstrcpyA(ei->ManufName,"UNI-T");
   sprintf(ei->ModelName,"UT%d%c",mmi->model,mmi->suffix);
  }else{
   lstrcpyA(ei->ManufName,"Voltcraft");
   sprintf(ei->ModelName,"VC%d",mmi->model);
  }
  ei->IconIndex=mmi->iconidx;
  ei->ExtraAlloc=0;		// Here, no allocation is needed
  ei->pHandler=mmi->pHandler;
  ei->lParam=mmi->counts;	// For UT61[BCD], UT71[BCDE] multimeters, only the counts are different
 }
 return true;
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded