April 7th, 2020

Perl module for decoding tekpower TP4000ZC

Originally published at Never been one to let the carrier drop. You can comment here or there.

I didn’t see one of these lying around so I banged this together in a few minutes to be able to use the TP4000ZC with a raspberry pi.. the intention is to use this to monitor the main bus voltage of my solar array.

This is provided, obviously, with no warranty, and is based on information found at tek’s web site. It seems to match the display.


package tekpower;

use Device::SerialPort;
use Data::Dumper;
use strict;

sub new {
my $self = {};
my $type = shift;
my $port = shift;

$self->{'debug'} = 0;
$self->{'port'} = $port;
$self->{'dev'} = Device::SerialPort->new($port, 1) || die "Failed to open $port";
$self->{'dev'}->baudrate(2400);
$self->{'dev'}->parity('none');
$self->{'dev'}->databits(8);
$self->{'dev'}->stopbits(1);
$self->{'dev'}->dtr_active(1);

$self->{'dev'}->debug($self->{'debug'}) if($self->{'debug'});
$self->{'dev'}->write_settings;
$self->{'dev'}->read_const_time(3000);
my $r = bless($self, $type);

return $r;
}

sub read {
my $self = shift;
$self->{'dev'}->reset_error;
my ($count,$buf) = $self->{'dev'}->read(64);
print "count: $count\n" if($self->{'debug'});
my @array = split(//,$buf);
my $notOk = 1;
while($notOk && @array) {
my $z = shift(@array);

print "z: " . ord($z) if($self->{'debug'});

my $checksum = (ord($z) & 0xF0) >> 4;
print "checksum: $checksum\n" if($self->{'debug'});
if($checksum == 1) {
unshift(@array,$z);
$notOk = 0;
}
}
my @v_array;
# first check high order bits
my ($i, $checksum);
for($i=0;$i<14;$i++) { $checksum = ord($array[$i]) & 0xF0; $v_array[$i] = ord($array[$i]) & 0x0F; $checksum = $checksum >> 4;
if($checksum != ($i+1)) {
print "Checksum mismatch at $i ($checksum)\n" if($self->{'debug'});
return undef;
}

}

# second decode reading;
my $mode;

$self->{'smode'} = "AC" if($v_array[0] & 8);
$self->{'smode'} = "DC" if($v_array[0] & 4);
# bit 2 is auto ranging, do we care?
# bit 1 is RS232, um, if you don't know that, what are you doing here?
$self->{'sign'} = "+";
$self->{'sign'} = "-" if($v_array[1] & 8);
$self->{'digit1'} = $self->convert_digit($v_array[1], $v_array[2], 0 );
$self->{'digit2'} = $self->convert_digit($v_array[3], $v_array[4], 1 );
$self->{'digit3'} = $self->convert_digit($v_array[5], $v_array[6], 1 );
$self->{'digit4'} = $self->convert_digit($v_array[7], $v_array[8], 1 );

$self->{'number'} = $self->{'sign'} . $self->{'digit1'} . $self->{'digit2'} . $self->{'digit3'} . $self->{'digit4'};

$self->{'range'} = 'u' if($v_array[9] & 8);
$self->{'number'} *= 0.000001 if($v_array[9] & 8);

$self->{'range'} = 'n' if($v_array[9] & 4);
$self->{'number'} *= 0.000000001 if($v_array[9] & 4);
$self->{'range'} = 'k' if($v_array[9] & 2);
$self->{'number'} *= 1000 if($v_array[9] & 2);

# 1 is diode, do we care?
$self->{'range'} = 'm' if($v_array[10] & 8);
$self->{'number'} *= 0.001 if($v_array[10] & 8);

$self->{'range'} = '%' if($v_array[10] & 4);
$self->{'range'} = 'M' if($v_array[10] & 2);
$self->{'number'} *= 1000000 if($v_array[10] & 2);

$self->{'mode'} = "farad" if($v_array[11] & 8);
$self->{'mode'} = "ohm" if($v_array[11] & 4);
$self->{'mode'} = "delta" if($v_array[11] & 2);

# bit 1 is hold, do we care?

$self->{'mode'} = "amps" if($v_array[12] & 8);
$self->{'mode'} = "volts" if($v_array[12] & 4);
$self->{'mode'} = "hz" if($v_array[12] & 2);

return $self->{'smode'} . ' ' . $self->{'mode'} . " " . $self->{'sign'} . " " . $self->{'digit1'} . $self->{'digit2'} . $self->{'digit3'} . $self->{'digit4'} . ' ' . $self->{'range'};

}

sub convert_digit {
my $self = shift;
my $lhs = shift;
my $rhs = shift;
my $include_decimal = shift;
my $decimal;

if($include_decimal) {
if($lhs & 8) {
$decimal = ".";
} else {
$decimal = "";
}
}

$lhs = $lhs & 7;

my $d;

# 000 0101 = 1
if($lhs == 0 && $rhs == 5) {
$d = 1;
# 101 1011 = 2
} elsif($lhs == 5 && $rhs == 11) {
$d = 2;
# 001 1111 = 3
} elsif($lhs == 1 && $rhs == 15) {
$d = 3;
# 010 0111 = 4
} elsif($lhs == 2 && $rhs == 7) {
$d = 4;
# 011 1110 = 5
} elsif($lhs == 3 && $rhs == 14) {
$d = 5;
# 111 1110 = 6
} elsif( $lhs == 7 && $rhs == 14) {
$d = 6;
# 001 0101 = 7
} elsif($lhs == 1 && $rhs == 5) {
$d = 7;
# 111 1111 = 8
} elsif($lhs == 7 && $rhs == 15) {
$d = 8;
# 011 1111 = 9
} elsif($lhs == 3 && $rhs == 15 ) {
$d = 9;
# 111 1101 = 0
} elsif($lhs == 7 && $rhs == 13 ) {
$d = 0;
} elsif($lhs == 6 && $rhs == 8) {
$d = "L";
} else {
return undef;
}

my $v = $decimal . $d;
print "V: $v\n" if($self->{'debug'});
return $v;
}

1;