OVMS3/OVMS.V3/components/vehicle_bmwi3/tools/generate_ecu_code.pl

329 lines
12 KiB
Perl

#! /usr/bin/perl -w
no warnings 'experimental::smartmatch';
use File::Slurp;
use Data::Dumper;
use JSON::PP;
our $DEBUG = 0;
# Optionally you can pass a PID to match in which case you get that one only
our @MATCH_PIDS = ();
if (scalar(@ARGV) > 3) {
@MATCH_PIDS = @ARGV[3..scalar(@ARGV)-1];
@MATCH_PIDS = map { uc($_) } @MATCH_PIDS;
}
our $types = {
'signed char' => 'SCHAR',
'unsigned char' => 'UCHAR',
'char' => 'UCHAR',
'signed int' => 'SINT',
'unsigned int' => 'UINT',
'int' => 'UINT',
'long' => 'SINT32',
'signed long' => 'SINT32',
'unsigned long' => 'UINT32'
};
our $sizes = {
'signed char' => 1,
'unsigned char' => 1,
'char' => 1,
'signed int' => 2,
'unsigned int' => 2,
'int' => 2,
'long' => 4,
'signed long' => 4,
'unsigned long' => 4
};
our $ctypes = {
'signed char' => 'char',
'unsigned char' => 'unsigned char',
'char' => 'char',
'signed int' => 'short',
'unsigned int' => 'unsigned short',
'int' => 'unsigned short',
'signed long' => 'long',
'long' => 'long',
'unsigned long' => 'unsigned long',
'BITFIELD' => 'HANDLED SEPARATELY'
};
# These map from the C++ types!
our $formats = {
'char' => '%x',
'unsigned char' => '%x',
'short' => '%d',
'unsigned short' => '%u',
'long' => '%ld',
'unsigned long' => '%lu',
'float' => "%.4f",
'string?' => "%s"
};
our $blacklist = {
# 'KUEHLDAUER_HVB' => 1 # Obsolete and dupe
};
# We sometimes get duplicate result names - in that case we dedupe them by added the ID that they are returned from
our $usedfunctionnames = {};
our $usedresultnames = {};
open (CODE, ">&=3") || die "Can't fdopen 3";
open (POLLLIST, ">&=4") || die "Can't fdopen 4";
binmode(STDOUT, ":utf8");
binmode(STDERR, ":utf8");
binmode(CODE, ":utf8");
binmode(POLLLIST, ":utf8");
# Read ECU definition json as created by parseecu.pl
my $tables;
{
my $jsonpp = JSON::PP->new->utf8;
my $json = read_file($ARGV[0]);
$tables = $jsonpp->decode($json);
}
our $ECU = $ARGV[1] || $tables->{'ovmscode'} || "???";
our $ECUID = $ARGV[2] || $tables->{'ovmsextaddr'} || "??";
our $ECUDESC = $tables->{'ovmsdesc'} || "ECU: $ECU";
print STDERR "Generating OVMS config for ECU $ECU on id $ECUID: $ECUDESC\n";
# Dump header
foreach my $FD (*STDOUT, *CODE, *POLLLIST) {
print $FD "
//
// Warning: don't edit - generated by generate_ecu_code.pl processing $ARGV[0]: $ECU $ECUID: $ECUDESC
// This generated code makes it easier to process CANBUS messages from the $ECU ecu in a BMW i3
//";
}
print "
#define I3_ECU_${ECU}_TX 0x06F1${ECUID}
#define I3_ECU_${ECU}_RX 0x06${ECUID}F1";
$tables = $tables->{'tables'};
my $functions = $tables->{'SG_FUNKTIONEN'};
my $need_nl = 0;
# Read through the functions
foreach my $function (@$functions) {
my $functionname = $function->{ARG};
# Filter
if (scalar(@MATCH_PIDS)) {
print STDERR "checking " .
next unless (uc($function->{ID}) ~~ @MATCH_PIDS);
}
# For now skip those that take arguments (which are actually some of the most interesting)
$need_nl = 1;
if ($function->{ARG_TABELLE} && $function->{ARG_TABELLE} ne '-') {
if ($need_nl) { print "\n"; $need_nl = 0; }
print "\n// Skipping " . $functionname . " on " . $function->{ID} . " which takes arguments";
next;
}
# And skip those marked as obsolete
if ($function->{INFO} =~ /^Dieser Job ist nicht mehr angefordert und wird nicht mehr/) {
if ($need_nl) { print "\n"; $need_nl = 0; }
print "\n// Skipping " . $functionname . " on " . $function->{ID} . " INFO says it's obsolete";
next;
}
# And skip those blacklisted
if ($blacklist->{$functionname}) {
if ($need_nl) { print "\n"; $need_nl = 0; }
print "\n// Skipping " . $functionname . " on " . $function->{ID} . ": Blacklisted";
next;
}
if ($need_nl) { print "\n"; $need_nl = 0; }
# Uniqueify function name (there are dupes, though I'm sure there shouldn't be)
if ($usedfunctionnames->{$functionname}) {
$functionname = $functionname . '_'. uc($function->{ID});
print STDERR "De-duped function " . $function->{ARG} . " as $functionname\n";
}
$usedfunctionnames->{$functionname} = 1;
my $desc = format_desc($function->{INFO_EN}, $function->{INFO});
if ($DEBUG) {
print Dumper($function);
if ($function->{ARG_TABELLE} && $function->{ARG_TABELLE} ne '-') {
print Dumper({ arg_table => $tables->{uc($function->{ARG_TABELLE})} });
}
if ($function->{RES_TABELLE} && $function->{RES_TABELLE} ne '-') {
print Dumper({ res_table => $tables->{uc($function->{RES_TABELLE})} });
}
}
printf "\n#define I3_PID_%s_%-49s %6s", $ECU, $functionname, $function->{ID};
print "\n // $desc" if ($desc ne '');
# write the code
printf(CODE "%-80s // %s", "\n\n case I3_PID_${ECU}_" . $functionname . ": {", $function->{ID});
# Now deal with the results
my $results = $function->{RES_TABELLE};
$results = $tables->{uc($results)};
if (! $results) {
# Might just be a single result
if ($function->{DATENTYP} && $function->{DATENTYP} ne '-') {
$results = [ $function ];
Dumper($results);
}
}
if ($results) {
my $offset = 0;
my @codelines;
foreach my $result (@$results) {
print "\n";
print STDERR Dumper({result => $result}) if ($DEBUG);
my $desc = format_desc($result->{INFO_EN}, $result->{INFO});
my $datatype = $result->{DATENTYP};
my $mul = $result->{MUL};
my $div = $result->{DIV};
my $add = $result->{ADD};
my $unit = $result->{EINHEIT};
my $resultname = $result->{RESULTNAME};
$resultname = $result->{NAME} unless ($resultname && $resultname ne "-");
# De-dupe it
if ($usedresultnames->{$resultname}) {
print STDERR "De-duped result $resultname as " . uc($function->{ID}) . "_" . $resultname . "\n";
$resultname = $resultname . '_' . uc($function->{ID});
}
$usedresultnames->{$resultname} = 1;
# Build the macro expansion including any manipulations.
my $expr;
my $ctype;
if ($datatype =~ /^data\[(\d+)\]$/) {
print "\n // Can't yet generate code for $resultname of type $datatype at offset $offset. But we account for the $1 bytes";
print "\n // $desc" if ($desc ne '');
$offset += $1;
}
elsif ($datatype =~ /^string\[(\d+)\]$/) {
print "\n // Can't yet generate code for $resultname of type $datatype, at offset $offset. But we account for the $1 bytes";
print "\n // $desc" if ($desc ne '');
$offset += $1;
}
elsif (! $ctypes->{$datatype} ) {
print "\n // Can't process $resultname - don't know type $datatype (*** this will mean all the following offsets are wrong!!! ****)";
} else {
# For a bitfield we can look up the bit definitions.
my $isbitfield = 0;
my $bits;
if ($datatype eq "BITFIELD") {
$isbitfield = 1;
$bits = $tables->{$resultname};
# print Dumper({ "resultname" => $resultname, "datatype" => $datatype, "bitsdesc" => $bits});
if ($bits && $bits->[0]) {
$datatype = $bits->[0]->{DATENTYP};
print "\n // $resultname is a BITFIELD of size $datatype. We don't yet generate definitions for each bit, we treat as the host data type";
print "\n // $desc" if ($desc ne '');
foreach my $bit (@$bits) {
print "\n // " . $bit->{RESULTNAME} . ": Mask: " . $bit->{MASKE} . " - " . $bit->{INFO_EN};
}
} else {
$datatype = "unsigned char";
print "\n // $resultname is a BITFIELD of unknown size. We don't have definitions for each bit, and we GUESSED it is one byte ***";
print "\n // $desc" if ($desc ne '');
}
}
$ctype = $ctypes->{$datatype};
$expr = '(RXBUF_' . $types->{$datatype} . '(' . $offset . ')';
if ($mul ne '-' && $mul != 1) {
$expr .= '*' . $mul . 'f';
$ctype = 'float';
}
if ($div ne '-' && $div != 1) {
$expr .= '/' . $div . 'f';
$ctype = 'float';
}
$expr .= (($add > 0) ? '+' : '') . $add if ($add ne '-' && $add != 0);
$expr .= ')';
$offset += $sizes->{$datatype};
printf "\n #define I3_RES_%s_%-45s %s", $ECU, $resultname, $expr;
printf "\n #define I3_RES_%s_%-45s '%s'", $ECU, $resultname . '_UNIT', $unit if ($unit && $unit ne '-');
printf "\n #define I3_RES_%s_%-45s %s", $ECU, $resultname . '_TYPE', $ctype if ($ctype);
print "\n // $desc" if ($desc ne '');
# code
push @codelines, "\n $ctype $resultname = $expr;\n";
push @codelines, " // $desc\n";
if ($isbitfield) {
if ($bits && $bits->[0]) {
push @codelines, " // $resultname is a BITFIELD of size $datatype. We don't yet generate definitions for each bit, we treat as the host data type\n";
foreach my $bit (@$bits) {
push @codelines, " // " . $bit->{RESULTNAME} . ": Mask: " . $bit->{MASKE} . " - " . $bit->{INFO_EN} . "\n";
}
} else {
push @codelines, " // $resultname is a BITFIELD of unknown size. We don't have definitions for each bit, and we GUESSED it is one byte ***\n";
}
}
print STDERR "Don't have format for $ctype\n" unless ($formats->{$ctype});
push @codelines, " ESP_LOGD(TAG, \"From ECU %s, pid %s: got %s=" . ($isbitfield ? "%lx" : $formats->{$ctype}) . "%s\\n\", \"$ECU\", \"" . $functionname . "\", \"$resultname\", " . ($isbitfield ? "(unsigned long)" : "") . "$resultname, " . (($unit && $unit ne "-") ? "\"\\\"$unit\\\"\"" : "\"\"") . ");\n";
}
}
# code
print CODE "\n if (datalen < $offset) {\n ESP_LOGW(TAG, \"Received %d bytes for %s, expected %d\", datalen, \"I3_PID_${ECU}_" . $functionname . "\", $offset);\n break;\n }";
print CODE "\n" . join('', @codelines);
}
# code
print CODE "\n // ========== Add your processing here ==========\n";
print CODE " hexdump(rxbuf, type, pid);\n";
print CODE "\n break;\n }";
# polllist
printf POLLLIST "\n%-120s %s", " //{ I3_ECU_${ECU}_TX, I3_ECU_${ECU}_RX, VEHICLE_POLL_TYPE_OBDIIEXTENDED, I3_PID_${ECU}_" . $functionname . ",", " { 0, 0, 0, 0 }, 0, ISOTP_EXTADR }, // " . $function->{ID};
}
print "\n";
print POLLLIST "\n";
print CODE "\n";
sub format_desc {
my ($en, $de) = @_;
my $desc = $en || '';
if ($desc eq '') {
$desc = $de || '';
} else {
$desc .= " / " . $de if ($de);
}
if ($desc && $desc ne '') {
$desc =~ s/(.{1,110}|\S{111,})(?:\s[^\S\r\n]*|\Z)/$1\n \/\/ /g if (length $desc >= 110);
$desc =~ s/(\n \/\/ )?$//;
}
return $desc;
}
# 'result' => {
# 'MASKE' => '-',
# 'INFO' => "\x{c3}\x{9c}berlastz\x{c3}\x{a4}hler Sch\x{c3}\x{bc}tz K1",
# 'DATENTYP' => 'unsigned int',
# 'MUL' => '1.0',
# 'INFO_EN' => 'Overload counter contactor K1',
# 'ADD' => '0.0',
# 'RESULTNAME' => 'STAT_OVERLOAD_K1_WERT',
# 'NAME' => '-',
# 'L/H' => 'high',
# 'DIV' => '1.0',
# 'EINHEIT' => '-'
# }