From 398e5b984dd4947c49ce68ebee616a311a9af168 Mon Sep 17 00:00:00 2001 From: Richard Zetterberg Date: Sat, 18 Jan 2020 12:25:19 +0100 Subject: [PATCH 1/2] Fixes #27 Adds generic PartSupported that can be used to get the support status for any PID part, instead of concrete Part1Supported, Part2Supported, etc. --- .travis.yml | 6 +- CHANGELOG.md | 16 ++- README.org | 4 +- VERSION | 2 +- automation.py | 26 +++-- commands.go | 271 ++++++++++++++++++++++++++--------------------- commands_test.go | 82 ++++++++++++++ device.go | 124 +++++++++++++--------- device_test.go | 248 +++++++++++++++++++++++++++++++++++++++---- shell.nix | 3 + 10 files changed, 574 insertions(+), 208 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1d77eda..73089bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: go go: - 1.9 script: - - go tool vet --all . + - go fmt + - go vet + - golint - go build -v ./... - - go test ./... + - go test -bench . ./... diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd87b8..3c3c0db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,22 @@ # Changelog +## [0.7.0] - 2020-02-23 + +### Changed +- Commands `Part1Supported`, `Part2Supported`, `Part3Supported` are now deprecated by the generic command `PartSupported` + +### Fixed +- Issue #27 - ISSupported function in device.go incorrect for PID's outside part 1 + ## [0.6.0] - 2019-03-20 -### Fixed -- issue #18 - commands expect one byte as response - mocked device returned three bytes -- device identyfication issue - some device identified itself in second line of response +### Fixed +- Issue #18 - commands expect one byte as response - mocked device returned three bytes +- Device identyfication issue - some device identified itself in second line of response ### Added - `NewDistSinceDTCClear` command (0x31) - distance since last DTC clear -- mocked responses for Fuel (0x2F), Speed (0x0D) +- Mocked responses for Fuel (0x2F), Speed (0x0D) ## [0.5.1] - 2018-08-15 diff --git a/README.org b/README.org index 8d2a0c8..df7b494 100644 --- a/README.org +++ b/README.org @@ -15,7 +15,7 @@ #+end_src #+RESULTS: version_output -- Version :: 0.6.0 +- Version :: 0.7.0 Go library for communicating with cars [[https://en.wikipedia.org/wiki/On-board_diagnostics][OBD-II]] system using [[https://www.elmelectronics.com/ic/elm327/][ELM327]] based USB-devices. @@ -378,4 +378,4 @@ The library has been used successfully on the following cars: |---------------------------+-----------------+--------------| | Lexus IS200 Manual 2004 | 0.3.0 | @rzetterberg | | Ford Ka 2011 | 0.5.0 | @Enrico204 | -| Ford Transit Automat 2019 | 0.6.0 | @mikspec | \ No newline at end of file +| Ford Transit Automat 2019 | 0.6.0 | @mikspec | diff --git a/VERSION b/VERSION index a918a2a..faef31a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.7.0 diff --git a/automation.py b/automation.py index c4f9d55..a4dc676 100755 --- a/automation.py +++ b/automation.py @@ -90,13 +90,19 @@ def check(args): logger.info('Checking quality of project') command(['go', 'fmt']) - command(['go', 'tool', 'vet', '--all', '.']) + command(['go', 'vet']) command(['go', 'build', '-v', './...']) command(['golint']) def test(args): logger.info('Running benchmarks, unit tests and examples') - command(['go', 'test', '-bench', '.', './...']) + + cmd = ['go', 'test'] + + if args.bench: + cmd = cmd + ['-bench', '.', './...'] + + command(cmd) example_files = glob.glob('./examples/**/*.go', recursive=True) @@ -117,17 +123,19 @@ def test(args): help='Controls the log level, "info" is default' ) - check_parser = subparsers.add_parser( - 'check', - ) - + check_parser = subparsers.add_parser('check') check_parser.set_defaults(func=check) - test_parser = subparsers.add_parser( - 'test', + test_parser = subparsers.add_parser('test') + + test_parser.add_argument( + '--bench', + dest='bench', + action='store_true', + help='Controls whether to run benchmark or not' ) - test_parser.set_defaults(func=test) + test_parser.set_defaults(bench=False, func=test) args = parser.parse_args() diff --git a/commands.go b/commands.go index fd3cbc2..d6e2e60 100644 --- a/commands.go +++ b/commands.go @@ -27,7 +27,7 @@ type OBDCommand interface { // BaseCommand is a simple struct with the 3 members that all OBDCommands // will have in common. type BaseCommand struct { - parameterID byte + parameterID OBDParameterID dataWidth byte key string } @@ -107,34 +107,175 @@ func (cmd *UIntCommand) ValueAsLit() string { * Specific types */ -// Part1Supported represents a command that checks the supported PIDs 0 to 20 -type Part1Supported struct { +// PartSupported represents a command that checks which 31 PIDs are supported +// of a part. +// +// All PIDs are divided into parts 7 parts with the following PIDs: +// +// - Part 1 (0x00): 0x01 to 0x20 +// - Part 2 (0x20): 0x21 to 0x40 +// - Part 3 (0x40): 0x41 to 0x60 +// - Part 4 (0x60): 0x61 to 0x80 +// - Part 5 (0x80): 0x81 to 0xA0 +// - Part 6 (0xA0): 0xA1 to 0xC0 +// - Part 7 (0xC0): 0xC1 to 0xE0 +// +// PID 0x00 checks which PIDs that are supported of part 1, after that, the +// last PID of each part checks the supportedness of the next part. +// +// So PID 0x20 of Part 1 checks which PIDs in part 2 are supported, PID 0x40 of +// part 2 checks which PIDs in part 3 are supported, etc etc. +type PartSupported struct { BaseCommand UIntCommand + index byte } -// NewPart1Supported creates a new Part1Supported. -func NewPart1Supported() *Part1Supported { - return &Part1Supported{ - BaseCommand{0, 4, "supported_commands_part1"}, +// PartRange represents how many PIDs there are in one part +const PartRange = 0x20 + +// NewPartSupported creates a new PartSupported. +func NewPartSupported(index byte) *PartSupported { + if index < 1 { + index = 1 + } else if index > 7 { + index = 7 + } + + pid := OBDParameterID((index - 1) * PartRange) + + return &PartSupported{ + BaseCommand{pid, 4, fmt.Sprintf("supported_commands_part%d", index)}, UIntCommand{}, + index, } } // SetValue processes the byte array value into the right unsigned // integer value. -func (cmd *Part1Supported) SetValue(result *Result) error { +func (part *PartSupported) SetValue(result *Result) error { payload, err := result.PayloadAsUInt32() if err != nil { return err } - cmd.Value = uint32(payload) + part.Value = uint32(payload) return nil } +// SetRawValue sets the raw value directly without any validation or parsing. +func (part *PartSupported) SetRawValue(val uint32) { + part.Value = val +} + +// PIDInRange checks if the given is in range of the current part. +// +// For example, if the current part is 1 (0x01 to 0x20) and the given command +// has a PID of 0x10, then this function returns true. +// +// If the given command has a PID of 0x31 then this function returns false. +func (part *PartSupported) PIDInRange(comparePID OBDParameterID) bool { + endPID := OBDParameterID(part.index * PartRange) + startPID := OBDParameterID(endPID-PartRange) + 1 + + return startPID <= comparePID && comparePID <= endPID +} + +// CommandInRange checks if the PID of the given command is in range of the +// current part. +// +// For example, if the current part is 1 (0x01 to 0x20) and the given command +// has a PID of 0x10, then this function returns true. +// +// If the given command has a PID of 0x31 then this function returns false. +func (part *PartSupported) CommandInRange(cmd OBDCommand) bool { + return part.PIDInRange(cmd.ParameterID()) +} + +// SupportsPID checks if the given command is supported in the current part. +// +// To figure out if a PID is supported we need to understand what the Value +// of a PartSupported represents. +// +// It represents the supported/not supported state of 32 PIDs. +// +// It does this by encoding this information as 32 bits, where each bit +// represents the state of a PID: +// +// - When the bit is set, it represents the PID being supported +// - When the bit is unset, it represents the PID being unsupported +// +// To make it easier to map PID values to actual bits, Bit-Encoded-Notation +// is used, where each bit has a name. Each name as a letter representing +// the byte and a number representing the bit in the byte. +// +// Here's how Bit-Encoded-Notation maps against the bits: +// +// A7 A0 B7 B0 C7 C0 D7 D0 +// | | | | | | | | +// v v v v v v v v +// 00000000 00000000 00000000 00000000 +// +// The state of the first PID is kept at bit A7, the second PID at A6, all +// the way until we get to PID 0x20 (32) which is kept in bit D0. +// +// In order to check if a bit is active, we can either: +// +// - Shift the bits of the value to the right until the bit we want to check +// has the position D0 and then use a AND bitwise conditional with the mask 0x1 +// - Shift the bits of the mask 0x1 to the left until it has the same position as +// the bit we want to check and then use a AND bitwise conditional with value +// +// This function uses the first method of checking if the bit is active. +// +// In order to figure out which bit position has, we take the position of the +// first PID, which is 32 and subtract the PID number to get the bit position. +// This means that 32 - PID 1 = 31 (A7) and 32 - PID 32 = 0 (D0). +// +// Now we know how to figure out what bit holds the information and how to read +// the information from the bit. +// +// This works well for checking if part 1 supported PIDs between 0x1 and 0x20, +// but it fails for parts 2,3,4,5,6,7 and PIDs about 0x20. +// +// In order to make this work for other parts besides 1, we simply normalize the +// PID number by removing 32 times the part index, instead of hardcoding the value +// to subtract to 32. +func (part *PartSupported) SupportsPID(comparePID OBDParameterID) bool { + if !part.PIDInRange(comparePID) { + return false + } + + offset := uint32(32 * part.index) + bitsToShift := offset - uint32(comparePID) + result := (part.Value >> bitsToShift) & 1 + + return result == 1 +} + +// SupportsNextPart checks if the PID that is used to check the next part is +// supported. This PID is always the last PID of the current part, which means +// we can simply check if the D0 bit is set. +func (part *PartSupported) SupportsNextPart() bool { + result := part.Value & 1 + + return result == 1 +} + +// SupportsCommand checks if the given command is supported in the current part. +// +// Note: returns false if the given PID is not handled in the current part +func (part *PartSupported) SupportsCommand(cmd OBDCommand) bool { + return part.SupportsPID(cmd.ParameterID()) +} + +// Index returns the part index. +func (part *PartSupported) Index() byte { + return part.index +} + // MonitorStatus represents a command that checks the status since DTCs // were cleared last time. This includes the MIL status and the amount of // DTCs. @@ -740,118 +881,6 @@ func (cmd *RuntimeSinceStart) SetValue(result *Result) error { return nil } -// Part2Supported represents a command that checks the supported PIDs 21 to 40. -type Part2Supported struct { - BaseCommand - UIntCommand -} - -// NewPart2Supported creates a new Part2Supported with the right parameters. -func NewPart2Supported() *Part2Supported { - return &Part2Supported{ - BaseCommand{32, 4, "supported_commands_part2"}, - UIntCommand{}, - } -} - -// SetValue processes the byte array value into the right unsigned integer -// value. -func (cmd *Part2Supported) SetValue(result *Result) error { - payload, err := result.PayloadAsUInt32() - - if err != nil { - return err - } - - cmd.Value = uint32(payload) - - return nil -} - -// Part3Supported represents a command that checks the supported PIDs 41 to 60. -type Part3Supported struct { - BaseCommand - UIntCommand -} - -// NewPart3Supported creates a new Part3Supported with the right parameters. -func NewPart3Supported() *Part3Supported { - return &Part3Supported{ - BaseCommand{64, 4, "supported_commands_part3"}, - UIntCommand{}, - } -} - -// SetValue processes the byte array value into the right unsigned integer -// value. -func (cmd *Part3Supported) SetValue(result *Result) error { - payload, err := result.PayloadAsUInt32() - - if err != nil { - return err - } - - cmd.Value = uint32(payload) - - return nil -} - -// Part4Supported represents a command that checks the supported PIDs 61 to 80. -type Part4Supported struct { - BaseCommand - UIntCommand -} - -// NewPart4Supported creates a new Part4Supported with the right parameters. -func NewPart4Supported() *Part4Supported { - return &Part4Supported{ - BaseCommand{96, 4, "supported_commands_part4"}, - UIntCommand{}, - } -} - -// SetValue processes the byte array value into the right unsigned integer -// value. -func (cmd *Part4Supported) SetValue(result *Result) error { - payload, err := result.PayloadAsUInt32() - - if err != nil { - return err - } - - cmd.Value = uint32(payload) - - return nil -} - -// Part5Supported represents a command that checks the supported PIDs 81 to A0. -type Part5Supported struct { - BaseCommand - UIntCommand -} - -// NewPart5Supported creates a new Part5Supported with the right parameters.. -func NewPart5Supported() *Part5Supported { - return &Part5Supported{ - BaseCommand{128, 4, "supported_commands_part5"}, - UIntCommand{}, - } -} - -// SetValue processes the byte array value into the right unsigned integer -// value. -func (cmd *Part5Supported) SetValue(result *Result) error { - payload, err := result.PayloadAsUInt32() - - if err != nil { - return err - } - - cmd.Value = uint32(payload) - - return nil -} - /*============================================================================== * Utilities */ diff --git a/commands_test.go b/commands_test.go index 94db35a..d52409e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1,6 +1,7 @@ package elmobd import ( + "fmt" "testing" ) @@ -16,3 +17,84 @@ func TestMonitorStatusResult(t *testing.T) { assert(t, command.MilActive == true, "MIL was not active") assert(t, command.DtcAmount == 127, "DTCs were not 127") } + +func TestPartSupportedCommandInRange(t *testing.T) { + type scenario struct { + part *PartSupported + insidePIDs []OBDParameterID + outsidePIDs []OBDParameterID + } + + scenarios := []scenario{ + { + NewPartSupported(1), + []OBDParameterID{0x01, 0x10, 0x20}, + []OBDParameterID{0x00, 0x21, 0x40, 0x41}, + }, + { + NewPartSupported(2), + []OBDParameterID{0x21, 0x30, 0x40}, + []OBDParameterID{0x00, 0x01, 0x20, 0x41}, + }, + { + NewPartSupported(3), + []OBDParameterID{0x41, 0x50, 0x60}, + []OBDParameterID{0x00, 0x01, 0x40, 0x61}, + }, + { + NewPartSupported(4), + []OBDParameterID{0x61, 0x70, 0x80}, + []OBDParameterID{0x00, 0x01, 0x60, 0x81}, + }, + } + + for _, scen := range scenarios { + for _, pid := range scen.insidePIDs { + assert( + t, + scen.part.PIDInRange(pid) == true, + fmt.Sprintf( + "PID 0x%X was in range of part %d", + pid, + scen.part.Index(), + ), + ) + } + + for _, pid := range scen.outsidePIDs { + assert( + t, + scen.part.PIDInRange(pid) == false, + fmt.Sprintf( + "PID 0x%X was out of range of part %d", + pid, + scen.part.Index(), + ), + ) + } + } +} + +func TestPartSupportedSupportsCommand(t *testing.T) { + part := NewPartSupported(1) + + part.Value = 0xBE1FA813 + + supported := []OBDParameterID{ + 0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x13, 0x15, 0x1C, 0x1F, 0x20, + } + + unsupported := []OBDParameterID{ + 0x02, 0x08, 0x09, 0x0A, 0x0B, 0x12, 0x14, 0x16, + 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D, 0x1E, + } + + for _, pid := range supported { + assertEqual(t, part.SupportsPID(pid), true) + } + + for _, pid := range unsupported { + assertEqual(t, part.SupportsPID(pid), false) + } +} diff --git a/device.go b/device.go index 561d29b..2a9aec3 100644 --- a/device.go +++ b/device.go @@ -264,55 +264,34 @@ func (dev *Device) GetVersion() (string, error) { return strings.Trim(version, " "), nil } -// CheckSupportedCommands check which commands are supported (PID 1 to PID 160) -// by the car connected to the ELM327 device. -// -// Since a single command can only contain 32-bits of information 5 commands -// are run in series by this function to get the whole 160-bits of information. -// -// Returns error if the first command (PID 1 to 20) fails, the rest of the 5 -// commands are ignored if they fail. +// CheckSupportedCommands check which commands are supported by the car connected +// to the ELM327 device. func (dev *Device) CheckSupportedCommands() (*SupportedCommands, error) { - partRes, err := dev.RunOBDCommand(NewPart1Supported()) - part1 := uint32(0) - - if err != nil { - return nil, err + result := &SupportedCommands{ + []*PartSupported{}, } - part1 = uint32(partRes.(*Part1Supported).Value) + index := byte(1) - partRes, err = dev.RunOBDCommand(NewPart2Supported()) - part2 := uint32(0) + for { + part := NewPartSupported(index) - if err == nil { - part2 = uint32(partRes.(*Part2Supported).Value) - } + partRes, err := dev.RunOBDCommand(part) - partRes, err = dev.RunOBDCommand(NewPart3Supported()) - part3 := uint32(0) - - if err == nil { - part3 = uint32(partRes.(*Part3Supported).Value) - } - - partRes, err = dev.RunOBDCommand(NewPart4Supported()) - part4 := uint32(0) - - if err == nil { - part4 = uint32(partRes.(*Part4Supported).Value) - } + if err != nil { + return nil, err + } - partRes, err = dev.RunOBDCommand(NewPart5Supported()) - part5 := uint32(0) + result.AddPart(partRes.(*PartSupported)) - if err == nil { - part5 = uint32(partRes.(*Part5Supported).Value) + // Check if the car supports the PID that checks if the next part of PIDs + // are supported + if !part.SupportsNextPart() { + break + } } - result := SupportedCommands{part1, part2, part3, part4, part5} - - return &result, nil + return result, nil } // RunOBDCommand runs the given OBDCommand on the connected ELM327 device and @@ -366,11 +345,56 @@ func (dev *Device) RunManyOBDCommands(commands []OBDCommand) ([]OBDCommand, erro // (PID 1 to PID 160) that are supported by the car connected to the ELM327 // device. type SupportedCommands struct { - part1 uint32 - part2 uint32 - part3 uint32 - part4 uint32 - part5 uint32 + parts []*PartSupported +} + +// NewSupportedCommands creates a new PartSupported. +func NewSupportedCommands(partValues []uint32) (*SupportedCommands, error) { + parts := []*PartSupported{} + index := byte(1) + + for _, val := range partValues { + part := NewPartSupported(index) + + part.SetRawValue(val) + + parts = append(parts, part) + + index++ + } + + return &SupportedCommands{parts}, nil +} + +// AddPart adds the given part to the slice of parts checked. +func (sc *SupportedCommands) AddPart(part *PartSupported) { + sc.parts = append(sc.parts, part) +} + +// GetPart gets the part at the given index. +func (sc *SupportedCommands) GetPart(index byte) (*PartSupported, error) { + partsAmount := len(sc.parts) + + if partsAmount == 0 { + return nil, fmt.Errorf("Cannot get part by index %d, as there are no parts", index) + } + + if index >= byte(partsAmount) { + return nil, fmt.Errorf("Cannot get part by index %d, there are only %d parts", index, partsAmount) + } + + return sc.parts[index], nil +} + +// GetPartByPID gets the part at the given index. +func (sc *SupportedCommands) GetPartByPID(pid OBDParameterID) (*PartSupported, error) { + if pid == 0 { + return sc.GetPart(0) + } + + index := byte((pid - 1) / 0x20) + + return sc.GetPart(index) } // IsSupported checks if the given OBDCommand is supported. @@ -382,15 +406,13 @@ func (sc *SupportedCommands) IsSupported(cmd OBDCommand) bool { } pid := cmd.ParameterID() + part, err := sc.GetPartByPID(pid) - inPart1 := (sc.part1 >> uint32(32-pid)) & 1 - inPart2 := (sc.part2 >> uint32(32-pid)) & 1 - inPart3 := (sc.part3 >> uint32(32-pid)) & 1 - inPart4 := (sc.part4 >> uint32(32-pid)) & 1 - inPart5 := (sc.part5 >> uint32(32-pid)) & 1 + if err != nil { + return false + } - return (inPart1 == 1) || (inPart2 == 1) || - (inPart3 == 1) || (inPart4 == 1) || (inPart5 == 1) + return part.SupportsPID(pid) } // FilterSupported filters out the OBDCommands that are supported. diff --git a/device_test.go b/device_test.go index 17bbd91..57104f6 100644 --- a/device_test.go +++ b/device_test.go @@ -22,7 +22,7 @@ func benchParseOBDResponse(cmd OBDCommand, input []string, b *testing.B) { func BenchmarkParseOBDResponse4(b *testing.B) { benchParseOBDResponse( - NewPart1Supported(), + NewPartSupported(1), []string{"41 00 02 03 04 05"}, b, ) @@ -41,27 +41,246 @@ func BenchmarkParseOBDResponse2(b *testing.B) { */ func TestToCommand(t *testing.T) { - assertEqual(t, NewPart1Supported().ToCommand(), "01001") assertEqual(t, NewEngineLoad().ToCommand(), "01041") assertEqual(t, NewVehicleSpeed().ToCommand(), "010D1") assertEqual(t, NewDistSinceDTCClear().ToCommand(), "01311") } func TestIsSupported(t *testing.T) { - sc := SupportedCommands{0x0, 0x0, 0x0, 0x0, 0x0} + sc, err := NewSupportedCommands([]uint32{0x0, 0x0, 0x0, 0x0, 0x0}) - cmd1 := NewPart1Supported() + assert(t, err == nil, "Supported commands was created successfully") - if !sc.IsSupported(cmd1) { - t.Error("Expected supported sensors to always be supported") - } + cmd1 := NewPartSupported(1) + + assertEqual(t, sc.IsSupported(cmd1), true) cmd2 := NewEngineLoad() - sc.part1 = 0x10000000 + part, err := sc.GetPart(0) + + assert(t, err == nil, "New part supported was created successfully") + + part.SetRawValue(0x10000000) + + assertEqual(t, sc.IsSupported(cmd2), true) +} + +func TestGetPart(t *testing.T) { + sc, err := NewSupportedCommands([]uint32{0x0, 0x0, 0x0, 0x0}) + + assert(t, err == nil, "Supported commands was created successfully") + + part, err := sc.GetPart(0) + + assert(t, part != nil && err == nil, "Getting first part is successful") + + part, err = sc.GetPart(3) + + assert(t, part != nil && err == nil, "Getting last part is successful") + + part, err = sc.GetPart(4) + + assert(t, part == nil && err != nil, "Getting out of bounds path fails") +} + +func TestGetPartByPID(t *testing.T) { + sc, err := NewSupportedCommands([]uint32{0x0, 0x0, 0x0}) + + assert(t, err == nil, "Supported commands was created successfully") + + part, err := sc.GetPartByPID(0x0) - if !sc.IsSupported(cmd2) { - t.Errorf("Expected command %v to be supported", cmd2) + assert(t, part != nil && err == nil, "Getting PID 0x0 is successful") + assert(t, part.Index() == 1, "Part 1 is return for PID 0") + + part, err = sc.GetPartByPID(0x20) + + assert(t, part != nil && err == nil, "Getting PID 0x20 is successful") + assert(t, part.Index() == 1, "Part 1 is return for PID 0x20") + + part, err = sc.GetPartByPID(0x21) + + assert(t, part != nil && err == nil, "Getting PID 0x21 is successful") + assert(t, part.Index() == 2, "Part 2 is return for PID 0x21") + + part, err = sc.GetPartByPID(0x40) + + assert(t, part != nil && err == nil, "Getting PID 0x30 is successful") + assert(t, part.Index() == 2, "Part 2 is return for PID 0x40") + + part, err = sc.GetPartByPID(0x41) + + assert(t, part != nil && err == nil, "Getting PID 0x41 is successful") + assert(t, part.Index() == 3, "Part 3 is return for PID 0x41") + + part, err = sc.GetPartByPID(0x60) + + assert(t, part != nil && err == nil, "Getting PID 0x60 is successful") + assert(t, part.Index() == 3, "Part 3 is return for PID 0x60") + + part, err = sc.GetPartByPID(0x61) + + assert(t, part == nil && err != nil, "Getting PID 0x61 fails, since part 4 doesn't exist") +} + +type DummyCommand struct { + BaseCommand +} + +func (cmd *DummyCommand) ValueAsLit() string { + return "0x0" +} + +func (cmd *DummyCommand) SetValue(result *Result) error { + return nil +} + +// TestIsSupportedWikipediaExample checks that the IsSupported function returns +// the correct results when using the example on the wikipedia-page about +// decoding Service 01 PID 00: +// +// > For example, if the car response is BE1FA813, it can be decoded like this: +// > So, supported PIDs are: 01, 03, 04, 05, 06, 07, 0C, 0D, 0E, 0F, 10, 11, 13, +// > 15, 1C, 1F and 20 +// +// Source: https://en.wikipedia.org/wiki/OBD-II_PIDs#Service_01_PID_00 +func TestIsSupportedWikipediaExample(t *testing.T) { + sc, err := NewSupportedCommands([]uint32{0xBE1FA813, 0x0, 0x0, 0x0, 0x0}) + + assert(t, err == nil, "Supported commands was created successfully") + + supported := []OBDParameterID{ + 0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x13, 0x15, 0x1C, 0x1F, 0x20, + } + + unsupported := []OBDParameterID{ + 0x02, 0x08, 0x09, 0x0A, 0x0B, 0x12, 0x14, 0x16, + 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D, 0x1E, + } + + for _, pid := range supported { + cmd := &DummyCommand{ + BaseCommand{pid, 1, "dummy"}, + } + + assertEqual(t, sc.IsSupported(cmd), true) + } + + for _, pid := range unsupported { + cmd := &DummyCommand{ + BaseCommand{pid, 1, "dummy"}, + } + + assertEqual(t, sc.IsSupported(cmd), false) + } +} + +// TestIssue27Regression verifies that the IsSupported function returns the correct +// results when checking PIDs outside of part 1. +// +// The test simulates that we have received the result of 5 parts, where +// each part has the first and last bit active, and all other bits inactive. +// +// This means that the first PID of the part should be supported, as well as +// the last PID, which is the one you use to check the supportedness of PIDs of +// the next part. +// +// Source: https://github.com/rzetterberg/elmobd/issues/27 +func TestIssue27Regression(t *testing.T) { + sc, err := NewSupportedCommands([]uint32{ + // Result from calling: + // 0x00 "PIDs supported [01 - 20]" + // ---------------------------------- + // > Bit encoded [A7..D0] == [PID $01..PID $20] + // + // A7 = 0x01 "Monitor status since DTCs cleared" + // D0 = 0x20 "PIDs supported [21 - 40]" + // + // A7 B7 C7 D7 D0 + // | | | | | + // 10000000 00000000 00000000 00000001, + 0x80000001, + + // Result from calling: + // 0x20 "PIDs supported [21 - 40]" + // ---------------------------------- + // > Bit encoded [A7..D0] == [PID $21..PID $40] + // + // A7 = 0x21 "Distance traveled with malfunction indicator lamp (MIL) on" + // D0 = 0x40 "PIDs supported [41 - 60]" + // + // A7 B7 C7 D7 D0 + // | | | | | + // 10000000 00000000 00000000 00000001, + 0x80000001, + + // Result from calling: + // 0x40 "PIDs supported [41 - 60]" + // ---------------------------------- + // > Bit encoded [A7..D0] == [PID $41..PID $60] + // + // A7 = 0x41 "Monitor status this drive cycle" + // D0 = 0x60 "PIDs supported [61 - 80]" + // + // A7 B7 C7 D7 D0 + // | | | | | + // 10000000 00000000 00000000 00000001, + 0x80000001, + + // Result from calling: + // 0x60 "PIDs supported [61 - 80]" + // ---------------------------------- + // > Bit encoded [A7..D0] == [PID $61..PID $80] + // + // A7 = 0x61 "Driver's demand engine - percent torque" + // D0 = 0x80 "PIDs supported [81 - A0]" + // + // A7 B7 C7 D7 D0 + // | | | | | + // 10000000 00000000 00000000 00000001, + 0x80000001, + + // Result from calling: + // 0x80 "PIDs supported [81 - A0]" + // ---------------------------------- + // > Bit encoded [A7..D0] == [PID $81..PID $A0] + // + // A7 = 0x81 "Engine run time for Auxiliary Emissions Control Device(AECD)" + // D0 = 0xA0 "PIDs supported [A1 - C0]" + // + // A7 B7 C7 D7 D0 + // | | | | | + // 10000000 00000000 00000000 00000001, + 0x80000001, + }) + + assert(t, err == nil, "Supported commands was created successfully") + + supported := []OBDParameterID{ + // Part 1 PIDs + 0x01, 0x20, + + // Part 2 PIDs + 0x21, 0x40, + + // Part 3 PIDs + 0x41, 0x60, + + // Part 4 PIDs + 0x61, 0x80, + + // Part 5 PIDs + 0x81, 0xA0, + } + + for _, pid := range supported { + cmd := &DummyCommand{ + BaseCommand{pid, 1, "dummy"}, + } + + assertEqual(t, sc.IsSupported(cmd), true) } } @@ -73,7 +292,7 @@ func TestParseOBDResponse(t *testing.T) { scenarios := []scenario{ { - NewPart1Supported(), + NewPartSupported(1), []string{"41 00 02 03 04 05"}, }, { @@ -164,13 +383,6 @@ func TestParseOBDResponse(t *testing.T) { "41 11 FF", }, }, - { - NewPart1Supported(), - []string{ - "SEARCHING...", - "41 00 01 02 03 04", - }, - }, { NewDistSinceDTCClear(), []string{"41 31 02 33"}, diff --git a/shell.nix b/shell.nix index 96270f0..a9cd649 100644 --- a/shell.nix +++ b/shell.nix @@ -7,8 +7,11 @@ in name = "elmobd-${version}"; version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./VERSION); + # go get golang.org/x/tools/cmd/cover + buildInputs = [ go + golint python3 ]; } From b7a52d63b4dbd10f41f28d64873ba1081e9e3e7a Mon Sep 17 00:00:00 2001 From: Richard Zetterberg Date: Sun, 8 Mar 2020 14:57:21 +0100 Subject: [PATCH 2/2] Removes golint from travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 73089bb..fc2d819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,5 @@ go: script: - go fmt - go vet - - golint - go build -v ./... - go test -bench . ./...