#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;
}
Vorgefundene Kodierung: UTF-8 | 0
|