Initial version.
authorLadislav Laska <laska@kam.mff.cuni.cz>
Tue, 23 Jul 2013 19:55:52 +0000 (21:55 +0200)
committerLadislav Laska <laska@kam.mff.cuni.cz>
Tue, 23 Jul 2013 19:58:58 +0000 (21:58 +0200)
The first working version. Tested with Accucell 6 battery charger, in
LiPo mode, 3S with parallel. Seems to work fine, but:

 - some stuff may be off by decimal point (I guessed)
 - some stuff may be completely wrong, but the basics (voltage, current,
   mah charge and balancer readings) looks good

Accucell.pm [new file with mode: 0644]
PROTOCOL [new file with mode: 0644]
accutool [new file with mode: 0755]

diff --git a/Accucell.pm b/Accucell.pm
new file mode 100644 (file)
index 0000000..df4bc9d
--- /dev/null
@@ -0,0 +1,177 @@
+#!/usr/bin/env perl
+
+package Accucell;
+
+use Device::SerialPort;
+use Data::Dumper;
+use common::sense;
+
+#   00 .. {  star marker
+#      01... zz Bit field 0x01 Temp cutoff, 0x04 Capacity cutoff, 0x08 key beep on, 0x10 buzzer on
+#      02 .. nc NiMH sensitivity Delta V milliVolts change per cell  {Byte 02 & 03 discription}
+#      03 .. NS NiCD sensitivity Delta V Millivolts change per cell  {were reversed in the orig }
+#      04 .. TC Temp Cutoff                                          {and in all previous posted}
+#      05 .. WW Waste time chg->dcg                                  {discriptions & code       }
+#      06 .. ff backlight (units of 5%)
+#      07 .. kk input Cutoff Voltage
+#      8 .. GG 0 disc, 1 charge, 0x10 cycle(DtoC) 0x11 cycle(CtoD)
+#      9 .. qq NiCd charge current
+#      10 .. rr NiCd discharge current
+#      11 .. 11 Nicd CtoD or DtoC
+#      12 .. vv num of cycles NiCd
+#      13 .. ss NiMh charge current
+#      14 .. uu NiMh discharge current
+#      15 .. 22 NiMh CtoD or DtoC
+#      16 .. yy num of cycles Nimh
+#      17 .. jj Lipo charge current (also storage current)
+#      18 .. LL Lipo # cells
+#      19 20 hh Lipo discharge current
+#      21 .. 33 Pb charge current   21
+#      22 .. pp number of Pb cells  22
+#      23 .. bb Battery type 4=Pb, 3=Nc, 2=Nm, 1=Li, 0=None
+#      24 .. aa Active
+#      25 26 dddd Discharge cutoff voltage NiMh
+#      27 28 nnnn Discharge cutoff voltage NiCd
+#      29 .. ??? don't know
+#      30 .. SS Safety Timer (120)
+#      31 32 cccc Capacity cutoff
+#      33 34 AAAA Current
+#      35 36 BBBB Battery voltage
+#      37 38 eeee Temp External
+#      39 40 tttt Temp Internal
+#      41 42 IIII Input Voltage
+#      43 44 mmmm mAh
+#      45 46 L1L1 lipo cell 1
+#      47 48 L2L2 lipo cell 2
+#      49 50 L3L3 lipo cell 3
+#      51 52 L4L4 lipo cell 4
+#      53 54 L5L5 lipo cell 5
+#      55 56 L6L6 lipo cell 6
+#      57 58 L7L7 lipo cell 7
+#      59 60 L8L8 lipo cell 8
+#      60 - 68.................Bytes 60 through 68 could be for extra cell voltages in a charger upgrade
+#      69 .. Unknown suspect that is MSB overflow of timer
+#      70 .. TT tracks the time displayed on the charger in minutes
+#      71 .. Byte does not seem to change???
+#      72    CB Check byte from charger
+#      73 74 CKCK Checksum
+
+our @fields = ( "config", "nicd_dv", "nimh_dv", "temp_cutoff", "waste_time",
+       "backlight", "input_cutoff", "mode", "nicd_charge_current",
+       "nicd_discharge_current", "nicd_cycle_mode", "nicd_cycles",
+       "nimh_charge_current", "nimh_discharge_current", "nimh_cycle_mode",
+       "nimh_cycles", "lipo_charge_current", "lipo_cells", "lipo_discharge_current",
+       "pb_charge_current", "pb_cells", "battery_type", "active",
+       "nimh_discharge_cutoff", "nicd_discharge_cutoff", "safety_timer",
+       "capacity_cutoff", "current", "voltage", "temp_ext", "temp_int",
+       "input_voltage", "capacity", "cell_voltage_1", "cell_voltage_2",
+       "cell_voltage_3", "cell_voltage_4", "cell_voltage_5", "cell_voltage_6",
+       "cell_voltage_7", "cell_voltage_8", "time", "checksum", "check_byte");
+
+sub new {
+       my ($class, $file, $baud) = @_;
+       my $port = Device::SerialPort->new($file) or die("Could not open serial port: $!\n");
+       $port->baudrate($baud);
+       $port->read_char_time(0);     # don't wait for each character
+       $port->read_const_time(5000); # read timeout
+       
+       my $self = {
+               "_port_name" => $file,
+               "_baud" => $baud,
+               "_port" => $port
+       };
+       bless $self, $class;
+       return $self;
+}
+
+sub make_float {
+       my $array = shift;
+       my $first = shift;
+       return @$array[$first].".".(sprintf "%02i", @$array[$first+1]);
+}
+
+sub make_int {
+       my $array = shift;
+       my $first = shift;
+       return @$array[$first].(sprintf "%02i", @$array[$first+1]);
+}
+
+
+sub parse_message {
+       my @array = map { ord($_) & 0x7f } split //, shift;
+
+       # Check the checksum
+       my $sum;
+       $sum += $_ for @array[1..72];
+       $sum &= 0xff;
+       $sum = (($sum & 0xf0) << 4) | ($sum & 0xf) | 0x3030 ;
+       return undef if ($sum != (($array[73] << 8) | $array[74])); # Invalid checksum, return undef
+
+       my %msg = ( 
+               "config" => $array[1],
+               "nicd_dv" => $array[2],
+               "nimh_dv" => $array[3],
+               "temp_cutoff" => $array[4],
+               "waste_time" => $array[5],
+               "backlight" => $array[6],
+               "input_cutoff" => $array[7] / 10,
+               "mode" => $array[8], # 0 discharge, 1 charge, 2 cycle(dtc), 3 cycle(ctd)
+               "nicd_charge_current" => $array[9]/10,
+               "nicd_discharge_current" => $array[10]/10,
+               "nicd_cycle_mode" => $array[11],
+               "nicd_cycles" => $array[12],
+               "nimh_charge_current" => $array[13]/10,
+               "nimh_discharge_current" => $array[14]/10,
+               "nimh_cycle_mode" => $array[15],
+               "nimh_cycles" => $array[16],
+               "lipo_charge_current" => $array[17],
+               "lipo_cells" => $array[18],
+               "lipo_discharge_current" => make_float(\@array, 19),
+               "pb_charge_current" => $array[21],
+               "pb_cells" => $array[22],
+               "battery_type" => $array[23], # 0 = None, 1 = Li, 2 = nimh, 3 = nicd, 4 = pb
+               "active" => $array[24],
+               "nimh_discharge_cutoff" => make_float(\@array, 25)*10,
+               "nicd_discharge_cutoff" => make_float(\@array, 27)*10,
+               "safety_timer" => $array[29],
+               "capacity_cutoff" => make_int(\@array, 30),
+               "current" => make_float(\@array, 33),
+               "voltage" => make_float(\@array, 35),
+               "temp_ext" => make_int(\@array, 37),
+               "temp_int" => make_int(\@array, 39),
+               "input_voltage" => make_float(\@array, 41),
+               "capacity" => make_int(\@array, 43),
+               "cell_voltage_1" => make_float(\@array, 45),
+               "cell_voltage_2" =>     make_float(\@array, 47),
+               "cell_voltage_3" =>     make_float(\@array, 49),
+               "cell_voltage_4" =>     make_float(\@array, 51),
+               "cell_voltage_5" =>     make_float(\@array, 53),
+               "cell_voltage_6" =>     make_float(\@array, 55),
+               "cell_voltage_7" =>     make_float(\@array, 57),
+               "cell_voltage_8" =>     make_float(\@array, 59),
+               "time" => $array[70],
+               "checksum" => ($array[73] << 8) | ($array[74]),
+               "check_byte" => $array[72]
+       );
+       return \%msg;
+}
+
+
+sub read_message {
+       my ($self) = @_;
+       my $buffer="";
+       while (1) {
+               my $c = $self->{"_port"}->read(1);
+               # Reset the work
+               if ($c eq "{") {
+                       if ($buffer =~ /^{/) {
+                               my $msg = parse_message($buffer);
+                               return $msg if (defined $msg); # Message is undef if it is not valid, for example the checksum failed
+                       }
+                       $buffer = "";
+               }
+               $buffer .= $c;
+       }
+}
+
+1;
diff --git a/PROTOCOL b/PROTOCOL
new file mode 100644 (file)
index 0000000..feaa634
--- /dev/null
+++ b/PROTOCOL
@@ -0,0 +1,78 @@
+# There is some info on how the format is structured. The following is from
+# http://www.rcgroups.com/forums/showthread.php?t=1295917&page=2
+
+ reformated /LMH
+ formated as displayed in the hex data window
+01  03  05  07  09  11  13  15  17  19  21  23  25
+zzncNSTCWWffkkGGqqrr11vvssuu22yyjjLLhhhh33ppbbaadddd
+878485B78193E481878180818281808280808282828680808093
+01  03  05  07  09  11  13  15  17  19  21  23  25
+zzncNSTCWWffkkGGqqrr11vvssuu22yyjjLLhh__33ppbbaadddd
+
+27  29  31  33  35  37  39  41  43  45  47  49  51
+nnnn__SSccccAAAABBBBeeeettttIIIImmmmL0L0L1L1L2L2L3L3
+8095808CB28080808080808080808C9380808080808080808080
+
+53  55  57  59  61  63  65  67  69  71  73
+L4L4L5L5L6L6L7L7__________________TT__CKCKCK}}
+808080808080808080808080808080808194BC3B307D
+
+I have used a 2 letter code to show what each byte (as 2 hex digits) represents, and
+n underscore (_) to show a space. Some bytes are still unknown.
+Byte  Key
+01... zz Bit field 0x01 Temp cutoff, 0x04 Capacity cutoff, 0x08 key beep on, 0x10 buzzer on
+02 .. nc NiMH sensitivity Delta V milliVolts change per cell  {Byte 02 & 03 discription}
+03 .. NS NiCD sensitivity Delta V Millivolts change per cell  {were reversed in the orig }
+04 .. TC Temp Cutoff                                          {and in all previous posted}
+05 .. WW Waste time chg->dcg                                  {discriptions & code       }
+06 .. ff backlight (units of 5%)
+07 .. kk input Cutoff Voltage
+08 .. GG 0 disc, 1 charge, 0x10 cycle(DtoC) 0x11 cycle(CtoD)
+09 .. qq NiCd charge current
+10 .. rr NiCd discharge current
+11 .. 11 Nicd CtoD or DtoC
+12 .. vv num of cycles NiCd
+13 .. ss NiMh charge current
+14 .. uu NiMh discharge current
+15 .. 22 NiMh CtoD or DtoC
+16 .. yy num of cycles Nimh
+17 .. jj Lipo charge current (also storage current)
+18 .. LL Lipo # cells
+19 20 hh Lipo discharge current
+21 .. 33 Pb charge current   21
+22 .. pp number of Pb cells  22
+23 .. bb Battery type 4=Pb, 3=Nc, 2=Nm, 1=Li, 0=None
+24 .. aa Active
+25 26 dddd Discharge cutoff voltage NiMh
+27 28 nnnn Discharge cutoff voltage NiCd
+29 .. ??? don't know
+30 .. SS Safety Timer (120)
+31 32 cccc Capacity cutoff
+33 34 AAAA Current
+35 36 BBBB Battery voltage
+37 38 eeee Temp External
+39 40 tttt Temp Internal
+41 42 IIII Input Voltage
+43 44 mmmm mAh
+45 46 L1L1 lipo cell 1
+47 48 L2L2 lipo cell 2
+49 50 L3L3 lipo cell 3
+51 52 L4L4 lipo cell 4
+53 54 L5L5 lipo cell 5
+55 56 L6L6 lipo cell 6
+57 58 L7L7 lipo cell 7
+59 60 L8L8 lipo cell 8
+60 - 68.................Bytes 60 through 68 could be for extra cell voltages in a charger upgrade
+69 .. Unknown suspect that is MSB overflow of timer
+70 .. TT tracks the time displayed on the charger in minutes
+71 .. Byte does not seem to change???
+72    CB Check byte from charger
+73 74 CKCK Checksum
+
+
+72 73 74 CKCKCK  How to decode the checksum and check it
+Start after the 7B start marker.
+Add up bytes 1 through 72, just keep the least significant byte.
+Convert this byte value to hex, suppose this is 5B. split it as 05 and 0B hex
+Create two bytes 35 and 3B, i.e. put a 3 in front of each hex digit.
+These two bytes 73 and 74 are the checksum, sent before the 7D, 35 then 3B.
diff --git a/accutool b/accutool
new file mode 100755 (executable)
index 0000000..454c0ff
--- /dev/null
+++ b/accutool
@@ -0,0 +1,62 @@
+#!/usr/bin/env perl
+
+use Accucell;
+use common::sense;
+use Data::Dumper;
+use Getopt::Long qw(:config bundling no_ignore_case no_auto_abbrev);
+
+my $opt_source = "/dev/ttyUSB0";
+my $opt_baud = 9600;
+my $opt_log = "accucell.log";
+my $opt_output = "accucell.plot";
+my $opt_gnuplot = "VT,AT";
+
+GetOptions( 
+       'source|s=s' => \$opt_source, 
+       'baud|b=i' => \$opt_baud,
+       'log|l=s' => \$opt_log,
+       'output|o=s' => \$opt_output,
+       'gnutplot|g=s' => \$opt_gnuplot
+       );
+
+
+# The gnuplot stuff can be: VT, AT, or both
+
+
+if (-c $opt_source) {
+       # The input file is character device, open serial port and start reading
+       my $acu = Accucell->new($opt_source, $opt_baud) or die("Could not connect to charger: $!");
+       my $msg;
+
+       print STDERR "Waiting for process start.";
+       while (1) {
+               $msg = $acu->read_message();
+               last if ($msg->{"active"} != 0);
+               print ".";
+       }
+       print " got it\n";
+       open( FH, ">$opt_log" );
+       print FH "time,".join(",", @Accucell::fields)."\n";
+
+       my $basetime = time;
+       while (1) {
+               my $t = time - $basetime;
+               my $line = "$t,";
+               $line .= $msg->{$_}."," for @Accucell::fields;
+               $line =~ s/,$/\n/g;
+               print FH $line;
+               print "> ".$line;
+               flush FH;
+               last if ($msg->{"active"} == 0);
+               $msg = $acu->read_message();
+       }
+
+       print "Charger gone inactive, finishing...\n" if ($msg->{"active"} == 0);
+
+       close FH;
+} elsif (-f $opt_source) {
+
+} else {
+       print "Invalid input file.\n";
+}
+