You are on page 1of 17

HonOBDapt 1.

Intro, Summary
Prototype

HonOBDapt translates requests for on-board automotive computer sensor data from an
ISO standard OBD II format to a Honda proprietary format used in 92-95 Honda Civics.

HonOBDapt uses a general purpose microcontroller module equipped with external


digital I/O ports, the Arduino Uno Rev. 3. HonOBDapt uses a Freescale MC33660
ISOLink logic level converter integrated circuit to electrically connect the OBD II signal
line to the Arduino. HonOBDapt software is written in C++ and uses the
SoftwareSerialWithHalfDuplex library.

HonOBDapt supports core interactive diagnostic functionality of the OBD II standard,


comprised of essential sensor data including: air and coolant temperature, spark advance,
fuel injector pulse width, exhaust gas oxygen content, engine speed (RPM), short and
long term fuel trim, fuel system status (open or closed loop), manifold barometric
pressure, atmospheric barometric pressure, throttle position, vehicle speed, etc. There is
also a basic capability of translating check engine diagnostic trouble codes (DTCs).
There is no support for obtaining OBD II freeze frame data, nor any analog to the concept
of continuous fuel status, EVAP system or misfire monitoring.

These limitations notwithstanding this adapter serves a crucial purpose in allowing 92-95
Honda Civics to be diagnosed with modern, readily available OBD II diagnostic
scanners.

A prototype was constructed with the following: several trips to the junkyard to buy a
Honda on board computer (ECU) and OBD II standard connectors, lots of hot glue and
some mild eyestrain from trying to solder the MC33660, which is manufactured only in a
package intended for automated assembly. The prototype was tested with an ELM
compatible Bluetooth OBD II scanner. The prototype was tested with the OBD II PC
software packages Scantool and OBDAutoDoctor.

Sections I, II, III: C++ Software Specification


Sections IV, V, VI, VII: Electrical and Hardware specification
Section VIII: TODO
I. Control Flow
Asynchronous Processing Unit Design
Main Event Loop
State controller design, considerations
II. Class Reference
BusCoordinator
BusInit
TryReady
GenericBusInitAttempt
GenericBusInitComplete
CommCoordinator
GenericCommMessageCollector
GenericCommMessageProcessor
HondaCommCoordinator
HondaCommMessageCollector
HondaCommMessageProcessor
IHandler
IGenericHandler
IISOReply
GenericISOReply
GenericHandler
HondaHandler
HondaPort, ISOPort
HondaMap, ISOPID

III. Patterns and Principles


SubSections a, b, c, d, e, f: Object Oriented Characteristics
SubSections g, h, i, j, k, l, m: Gang of Four Patterns

Polymorphism
Loose Coupling,
Separation of Concerns
Information Hiding and Encapsulation
Reuse and Inheritance
Generalization
Template
Command Pattern
Chain of Responsibility, Responsibility Driven Design
State, Automaton, finite state machine
Strategy
Mediator
Adapter

IV. Serial Ports


V. MC33660
VI. Arduino
VII. OBD II
VIII. TODO
CONTROL FLOW

Asynchronous processing unit design

Generally, classes involved in handshaking and serial communications support Work(), DoneWorking()
and Reset().
These functions serve as a basic cooperative multitasking system. Work() is called when a higher level
controller has a time slice to give to the unit. The Work() function of each controller is expected to do its'
work, and exit quickly. If the conditions are met that allow the handshaking or communications to proceed
to the next higher level, DoneWorking() should return true. Reset() is used to reset the internal state of the
unit, typically at the end of a logical session controlled by a higher level controller.

Generally, the units are one-shot triggers. If, for example, a unit is falsely triggered to initiate a particular
process ( by noise or an invalid packet ), it will continue to attempt completion of the process and cannot
properly proceed with a valid sequence until it is Reset() by a higher level controller.

Main Event Loop

The main event loop cycles through the upper lever coordinators, BusCoordinator, CommCoordinator and
HondaCommCoordinator in a round robin fashion, calling .Work() and checking .DoneWorking() on each
one.

The OBD II port requires a bus initialization handshaking sequences. No bus initialization is necessary on
the HondaPort.

The main event loop strives to move the system into higher and higher levels of functioning. The macro
levels of functioning are:
Level 1. OBD II bus not initialized
Level 2. OBD II bus initialized, waiting for incoming request from the OBD II port.
Level 3. Message received from the OBD II port and forwarded to the HondaPort, waiting for message
from HondaPort
Level 4. Message received from the HondaPort and forwarded to the OBD II port.

Once the functioning level reaches 4, the process restarts generally at level 2, but possibly level 1.

Level 1.

The main event loop maintains a state variable that is either NO_COMM or COMM_ESTAB. Each time
through the main event loop, if the state is NO_COMM (bus not initialized), then BusCoordinator.Work()
is called.
The BusCoordinator will work to initialize the OBD II bus and achieve a higher state, COMM_ESTAB, by
using TryReady, BusInit, GenericBusInitAttempt and GenericBusInitComplete. After the call to
BusCoordinator.Work(), BusCoordinator.DoneWorking() is checked. If this function returns true, then the
bus is ready for communications and the state is set to COMM_ESTAB. if DoneWorking() is not true, the
main event loop restarts, again attempting to raise the functioning level by successfully initializating the
OBD II bus.

Level 2.
Once the bus is initialized, CommCoordinator.Work() will be called to try to continue to raise the
processing level.
CommCoordinator will use GenericCommMessageCollector to get any incoming data on the OBD II port.
If there is no data available immediately on the serial port, the GenericCommMessageCollector.Work() will
exit immediately; this allows the CommCoordinator to exit and yield control to the main event loop.

If there is waiting data, the data will be retrieved and a state of COLLECT will be marked internally to
GenericCommMessageCollector.

During the next iteration through the main event loop, CommCoordinator.Work() will be called and the
Work() function will call GenericCommMessageCollector.Work(). If there is more data available on the
OBD II port, that data will be collected by the GenericCommMessageCollector. Since the internal state of
GenericCommMessageCollector is COLLECT, the new data will be appended to the existing message
buffer.

Level 3
A true return value from GenericCommMessageCollector.DoneWorking() signifies that a complete and
valid OBD II message has been received and the internal state of the coordinator, CommCoordinator, will
be marked as MESSAGE_RECEIVED. Then the GenericCommMessageProcessor will become active,
processing the request by calling HondaHandler.IncomingPID(). The HondaHandler will translate the
OBD II PID to an appropriate request for Honda data and will call SendHondaRequest() to send a
command sequence to the HondaPort. HondaHandler will then assign the request to the
HondaCommCoordinator, which queues the request, using a function pointer to establish a non-blocking
asynchronous callback to be executed when the HondaPort data is ready.
CommMessageProcessor and CommCoordinator then yield control to the main event loop.

During the next iteration of the main event loop, HondaCommCoordinator.Work() will be called and the
HondaCommCoordinator will pick up the queued request and set an internal state of WORKING. The
HondaCommCoordinator will use the HondaCommMessageCollector to get any incoming data on the
HondaPort. If there is waiting data, the character data will be retrieved and a state of COLLECT will be
marked internally in the HondaCommMessageCollector

DUring the next iteration through the main event loop, HondaCommCoordinator.Work() will be called. If
there is more data, that data will be collected by the HondaCommMessageCollector. Since the internal
state of HondaCommMessageCollector is COLLECT the new data will be appended to the existing
message buffer.

Level 4

When the message from the HondaPort is complete and valid, signaled by the
HondaCommMessageCollector returning true to DoneWorking(), the HondaCommMessageProcessor will
become active and process the message by executing HondaHandler.Callback(). HondaHandler.Callback()
will execute the appropriate return function matching the original asynchronous request and effect the
translation of the Honda sensor data into ISO measurements, using a member of the group
ISOMeasurementsTOISOReply, and the reply to the OBD II port, using a GenericHandler.IISOReply
family member.

At this point the overall state has reached its highest point of productivity. The various
MessageCollector(s) and MessageProcessor(s) are reset, and the main event loop restarts at level 2, waiting
for more serial messages from the OBD II port.

-- Advantages of state controller design

---Extensibility

This structure provides a clean, clear implementation that lends itself to further modifications and
extension, such as hooks to process other data or timeout watchdogs, because there are clear places to hook
into the overall flow of handshaking or messaging without the risk of undesirable interaction or needing to
understand the lower level operation of each controller.

This structure also provides a clean way to implement potentially deep nested logic in a clear, easy to
debug and trace fashion.

Each asynchronous unit is coded with the overall structure in mind. For example, TryReady.Work() will
not block but will do a small amount of work, polling the OBD II K-Line bus status and checking the time-
of-day ( millis() ) to maintain a running total of idle time and then Work() will exit. TryReady can
maintain internal state such as the running total but encapsulates that state and signifies that internal
conditions have been satisfied by returning true to DoneWorking(). Also, when logical conditions require,
Reset() can be used to reset the running total.

In this way serial communications or handshaking can be carefully controlled from the upper level.
For instance, this structure makes it easy and clear to implement an *OVERALL* handshake attempt
timeout, without modifying functionally distinct units such as TryReady.

---Error Handling

When dealing with serial port messaging, subject to noise, message fragments being received, etc,
it is quite possible for individual functional units to become stuck or hung due to: waiting for message
termination or validation that will never succeed because of mid-message corruption, invalid or
nonstandard messages, etc.

In this type of messaging environment it is important to maintain supervisory control with clear top-down
control logic. The asynchronous, non-blocking Work(), DoneWorking() pattern lends itself to
implementing supervisory controls.

Of paramount importance is that the supervisory code and its' interface to lower level code be clear, distinct
and concise, so that unforseen situations; i.e. dealing with slightly altered or new packets or protocol details
and their concomitant issues; can be dealt with effectively.

---Complexity and correctness

The factoring of complicated nested communications logic into a body of state machines
is an effective way of managing complexity and makes logical defects more apparent.
This factoring methodology allows the program to be treated as the steps of an algorithm,
facilitating the application of mathematically rigorous techniques to prove or disprove the
correctness of the program logic.

-- Possible ramifications of state controller design

Although the event loop design of the serial communications code has additional overhead compared to a
more direct low-level approach, and can cause an entire event loop to be executed between individual serial
character data retrievals, the overhead is mostly syntactic exposition, and is not expected to be
computationally intensive.

As well, 9600 baud is not particularly fast relative to Arduino's microprocessor speed and the
SoftwareSerial library is interrupt driven ( interrupts take priority over all other execution context ) and
SoftwareSerial maintains its' own input buffers. Also, the direct serial controllers, CommMessageCollector
and HondaCommMessageCollector are enhanced with a burst mode that increases their priority under
certain circumstances. Due to these factors it is considered extremely unlikely for the overhead of the state
machine control system to cause a loss of serial data on any port.
CLASS REFERENCE

BusCoordinator
The BusCoordinator is responsible for coordinating the bus initialization process including: bus idle time
prerequisite, wakeup-packet handshaking and confirmation handshaking.
The Buscoordinator uses TryReady, BusInit, GenericBusInitAttempt, and GenericBusInitComplete.

TryReady
TryReady polls the OBD II K-Line bus pin and establishes that the necessary bus idle condition has been
met before handshaking begins.

BusInit
Tasked with the job of attempting and completing an OBD II bus initialization handshake sequence. Uses
GenericBusInitAttempt and GenericBusInitComplete.

GenericBusInitAttempt –
establishes generic requirements for a bus initialization attempt, which begins the handshaking sequence of
an OBD II session. Uses WAKE_PACK array, a storage area for K-Line bus pin logic level transition
times ( 10ms, 100ms, etc ). This routine uses Arduino's digitalRead to check the logic level of the OBD II
K-Line bus pin. When the pin alters state ( High to Low or Low to High ) the time-of-day ( millis() ) is
recorded. This recorded value is compared to the time-of-day when the next level transition occurs, and in
this way the length of the handshaking pulse or pulses is established.

If the transition pattern is a valid OBD II K-Line bus wake-up pattern, DoneWorking() will return true.

The protocol specific details of the GenericBusInitAttempt are left to the more derived classes
BusInitAttempt_ISO14230_slow, BusInitAttempt_ISO14230_fast, BusInitAttempt_ISO9141

GenericBusInitComplete
establishes basic requirements of bus initialization completion which finishes the handshaking sequences
for an OBD II session. The completion of the handshaking is accomplished by sending a confirmation
message, mathematically derived from the messages received during a bus initialization attempt, to the
OBD II port.

The protocol specific details of the BusInitComplete are left to the more derived classes:
BusInitComplete_ISO9141, BusInitComplete_14230_slow, BusInitComplete_ISO14230_fast

CommCoordinator
The CommCoordinator is responsible for collecting, verifying and processing messages on the OBD II K-
line serial port. Processing generally concludes with a call to HondaHandler.IncomingPID(), which accepts
responsibility for replying to the OBD II K-line serial port with the requested Honda on board computer
(ECU) data. Uses GenericCommMessageCollector and GenericCommMessageProcessor.

GenericCommMessageCollector
Collects the serial data from the OBD II port and establishes completeness (appropriate headers) and basic
error-checking (checksum) of incoming serial data.
This message collector supports a serial burst mode, assuming that an incoming serial character is usually
followed by the remaining characters without delay. This routine is authorized to busy-wait (block)
waiting for serial data for a maximum time interval defined by SERIAL_BURST_MAX_TIME_SLICE,
before relinquishing control to its' coordinator.

Protocol specific details are left to the more derived classes: ISOCommMessageCollector_ISO9141,
ISOCommMessageCollector_ISO14230

GenericCommMessageProcessor -
Processes a valid message containing an OBD II PID by calling HondaHandler.IncomingPID() to transfer
responsibility for retrieving the requested data and sending a reply to the OBD II port. IncomingPID
function does not block and GenericCommMessageProcessor and its' coordinator CommCoordinator can
exit immediately after the call to IncomingPID.

Protocol specific details are left to the more derived classes: CommMessageProcessor_ISO9141,
CommMessageProcessor_ISO14230

HondaCommCoordinator
manages Honda communications. Uses HondaCommMessageCollector and
HondaCommMessageProcessor.

Works in conjunction with the HondaHandler. Provides AssignWork function which, when called by
HondaHandler, queues a request for Honda data.

HondaCommMessageCollector
Collects the serial data from the HondaPort. Establishes completeness, appropriate headers, error-checking
(checksum) of incoming serial data.

This message collector supports a serial burst mode, assuming that an incoming serial character is usually
followed by the remaining characters without delay. This routine is authorized to busy-wait (block)
waiting for serial data for a maximum time interval defined by SERIAL_BURST_MAX_TIME_SLICE,
before relinquishing control to its' coordinator.

HondaCommMessageProcessor
Processes valid messages from the HondaPort. Processing concludes with a call to
HondaHandler.Callback(), which effects translation of the Honda data and the reply to the OBD II port
using a member of the GenericHandler ISOMeasurementsToISOReply group and an IISOReply family
member.

IHandler
establishes what a handler, used by the CommCoordinator.CommMessageProcessor, must do
A class implementing IHandler must support IncomingPID. At a fundamental level, to implement the
IHandler interface the class must be able to do something with IncomingPID, perhaps ignoring unsupported
operations. This class would be a good jumping off point for diverse purposes, for example, a class that
only logs the incoming messages.

IGenericHandler
supports VIN and Capabilities.
A slightly richer implementation would be required to, at a minimum, reply with the Vehicle Identification
Number and the Capabilities, which is a list of supported sensors or other data that can be retrieved.
IISOReply
is an interface defining the generic requirements of a reply to the OBD II port.

GenericISOReply
is class containing generic OBD II port reply code.
The protocol specific details are left to the more derived classes: ProtocolReply_ISO9141 and
ProtocolReply_ISO14230.

GenericHandler
Adds an interface of virtual function prototypes that describe the expected input and output for a sensor
data retrieval implementation and a baseline map function, ISOPID_TO_GENERIC_HANDLER_MAP,
that links OBD II PIDs to functions in the sensor interface. GenericHandler builds directly upon its direct
ancestor abstractions IGenericHandler and IHandler, but becomes richer and more concrete by extending
outside of that family to become a composition with an IISOReply family member.

GenericHandler also defines functions that convert data to an OBD II format. These functions follow the
naming convention ISOMeasurementsToISOReply..XXXXX. These functions accept input data in the
scale dictated by the OBD II standard; i.e. temperature is measured in Celcius degress, with the input data
to the function represented by the formula "Actual Temp - 40". E.G., if the temperature of the coolant is
determined to be 55 deg. C., you would call ISOMeasureToISOReply_Scalar_byte( 15 );

The inclusion of an IISOReply family member which includes functions that reply to the OBD II port and
the definitions of the ISOMeasurementsToISOReply functions establishes that at this level of class
abstraction that the concept of handling will be to send a reply message to the OBD II port. The IISOReply
member also provides functions toward this end. At this level, the implementation of the sensor data
virtual functions is left abstract.

HondaHandler
becomes the most derived, richest member of this family tree. It is a complete, fully functional handler. It
adds concrete implementations of the remaining abstractions in the GenericHandler family hierarchy, the
abstract sensor data retrieval interface and IncomingPID().

The HondaHandler implements IncomingPID by using the generic mapping of OBD II PIDS onto the
abstract sensor data retrieval interface, ISOPID_TO_GENERIC_HANDLER_MAP. The HondaHandler
implementation of the sensor data retrieval functions is asynchronous, and delegated, relying on the
placement of its' functional colleague, HondaCommCoordinator, in the main event loop, to retrieve the data
from the HondaPort.

HondaCommCoordinator.AssignWork() is used to communicate with the HondaCommCoordinator,


requesting that during the next main event loop cycle, the retrieval of a particular sensor value from the
Honda on board computer (ECU).
AssignWork establishes a callback function pointer for the HondaCommCoordinator to call when the
requested data has been retrieved.

When the HondaCommCoordinator responds with the data, HondaHandler converts, maps or otherwise
translates the data values from the Honda scaling or other representations, into values compatible with the
OBD II measurement scale or representation. Then a member of the
GenericHandler.ISOMeasurementsTOISOReply...XXXX group is called to generically convert the data
into an OBD II format and then use an IISOReply family member to effect the reply to the OBD II port
using the proper protocol.
HondaHandler implements the IGeneralAsyncHandler interface, marking it as an asynchronous class.

HondaHandler defines GeneralAsyncRequest, a function that factors out the common code for setting up
the function pointer.

IGenericAsyncHandler
IGenericAsyncHandler is a marker interface. Implementing it marks a class as asynchronous in nature.

ISOPort, HondaPort
ISOPort and HondaPort provide character based access to the serial ports, defined using a SoftwareSerial
instance configured to communicate on the digital I/O pins connected to the OBD II connector and the
Honda Diagnostic Link Connector.

ISOPort and HondaPort provide safety features by providing a consistent interface to the serial ports, which
can often become unmanageable within nested serial communications logic.

All reads and writes are done through these interfaces, so the checksums are calculated through these
routines, reducing the risk of multiple conflicting direct access.

HondaMap
HondaMap objects are used to refer to specific sensors in the Honda on-board computer (ECU). The
definition includes an address and length. Some sensors return two bytes, for example, RPM. If the two-
byte return data is a scalar value, the data is big-endian, that is, the most significant byte, the one that
counts for 256 for each 1 of its' value, is the first of the two-bytes transmitted by the ECU.

ISOPID
ISOPID objects are used to represent OBD II requests. Each request contains a mode and an identifier.
Mode 1 is sensors. Mode 3 is Diagnostic Trouble Codes. E.G. Mode 1, identifier 0x0C is engine speed
(RPM).

PATTERNS

Object Oriented characteristics:

Polymorphism
GenericBusInitAttempt, ISO14230_BusInitAttempt, ISO9141_BusInitAttempt

When the variable oBusInitAttempt takes the form of a ISO14230_BusInitAttempt, the BusCoordinator
doesn't know or care about this. All the BusCoordinator needs to know is that a GenericBusInitAttempt is
being utilized. Thus the principle of polymorphism, or many forms, is being leveraged to achieve more
generalized code. From one perspective the form of the instance variable oBusInitAttempt is
GenericBusInitAttempt and from another perspective the form is ISO14230_BusInitAttempt, thus
polymorphic.

IHandler, GenericHandler, HondaHandler


The IHandler abstract interface is used in the CommMessageProcessor. The form of the instance variable
in CommMessageProcessor is an abstract IHandler at compile time. At run time, however the IHandler
object takes the form of a concrete HondaHandler. From the perspective of the CommMessageProcessor
the instance variable is an IHandler, but from a macro, system level view, the instance variable is a
HondaHandler. Thus this variable takes many forms and is polymorphic. GenericHandler is another form
of an IHandler, albeit a kind of abstract 'unexpressed' form, as instantiating it would result in an incomplete
handler implementation.

Loose Coupling

Even though they work closely together to accomplish the overall goal in this adapter implementation,
there is a loose-coupling between the CommMessageProcessor, receiving a PID from the OBDII port and
the HondaHandler, effecting a reply to the OBD II port. This coupling is loose because the Handler could
be implemented in different ways and this loose coupling leaves the door open for more diverse
interpretations of the term 'handling'.

In this adapter implementation the loose coupling make it easier to inject an asynchronous handler into the
design. The CommMessageProcessor is coupled only to an IHandler, an interface definition ( aka, contract
specification ), not tied directly to the asynchronous implementation of HondaHandler, reducing the chance
of cascading changes rippling through multiple functional units.

One of the advantages of the loose coupling in this adapter implementation is its' ability to mitigate the
situation where one subsystem is blocked by another. It would be undesirable to make the
CommMessageProcessor block(wait) for the Honda data to be retrieved. The data may never come but we
still want the CommMessageProcessor to be able to degrade gracefully and respond to its' immediate
supervisor. The loose coupling supports this because the HondaHandler doesn't take control when it is
tasked with getting Honda sensor data. It is delegated to do so and this is a pattern of Chain of
Responsibility, but the CommMessageProcessor maintains control and can exit cleanly. This clean exit
facilitates the placement of supervisory control logic where it belongs; at a high logical level in the code,
not buried deep in the implementation details of a particular low level unit.

Loose coupling has its benefits and downfalls. Loose coupling allows flexibility in implementing the
Handler without changing the CommMessageProcessor, and ultimately aids in the implementation of an
asynchronous handler, but this does not come without issues. For example, there is no way to easily
enforce transactional integrity across this coupling, or pass error or control messages such as 'retry' across
this coupling because by the time a failure is detected on one side of the coupling, the execution context
that initiated the request has exited. A similar problem occurs with non-asynchronous loose coupling,
where, for example, it is impossible to pass an error message because the loose coupling only supports a
limited interface.

Separation of Concerns
See _Patterns and Principles_ "Loose Coupling", "Information Hiding and
Encapsulation", and "Chain of Responsibility"

Generalization
-- Reuse, Inheritance and Templates
--- Benefits
The sharing of common functionality reduces the overall code load that must be inspected. It also increase
the chances that changes made to the system will propagate in the proper way.
--- Ramifications
The factoring out of common functionality must be done thoughtfully and carefully, as the benefits of reuse
are quickly outweighed by the complexity of managing entanglement between sharing
classes.

See Also, Generalization, composition, flexibility of modularity


see Also, Generalization, composition, ramifications
See Also, Generalization, overgeneralization
See Also, Template for examples; Generally whereever there is a Template there is reuse.

Generalization, composition
-- Reuse, factoring, flexibility of modularity
--- Benefits
Factoring out distinct conceptual groups of functionality like sensor data virtual function interface,
ISOMeasurementsTOISOReply grouping, IISOReply and GenericISOReply provides the
flexibility of modularity. Individual pieces can be swapped in and out, e.g. substituting a newer
Controller Area Network diagnostic subsystem implementation for the OBD II system, as long as
the new piece adheres to the established convention, defined by interfaces.

Flexibilty means less risk. Generalizing may take more front-end design time, but reduces the amplitude of
wild-schedule swings on the back end.
--- Ramifications
Compositional reuse can lead a design down a dead end. If a class inherits everything from a composed
class and implements a new subsystem, without a solid set of abstract interfaces ( or contracts ) to define
exactly where in the tree the new functionality branches off from ( or differs from the previous
incarnation ), it is likely that new functionality will simply rewrite existing functionality.

So there is a place on the spectrum between strict inheritance hierarchy-to-god class compositional
multipurposing that must be chosen carefully. Too tight interface adherence limits the diversity that future
implementations can create. Too little interface adherance and excessive use of ad-hoc compositional reuse
encourages rewrites and big-ball-of-mud designs.

Overgeneralization

The attempt to use generic structures is a noble goal, but there is a limit to how far to take it. The architect
of class families must focus on the real purpose of the design effort. Is it to solve a particular problem, with
a flexible building block pattern, that reduces the risk that changing requirements will render the code
unusable?
Or is the design effort moving dangerously toward overgeneralization. For example, a good home builder
might carry a variety of tools, saws, hammer, etc, some of which may even be used to build other tools. e.g.
scaffolding for painting. But the home builder doesn't carry a smelting forge in the truck. He doesn't need
that much generality and to attempt such would actually be counterproductive - for building a house - so
the principle is to keep some perspective about the goal you are trying to achieve.

Too much factoring and generalizing can cause unnecessary complexity. As the class family becomes
more fragmented and modular it becomes harder for the consumer to understand how to use it.
So the place on the spectrum of generalism-to-specialization is important. There is always a design trade-
off. As Spolsky says, the trash can must be open on top so people can put trash in. But it needs to be
closed on top so trash doesn't blow away.

Generalization, practical implications and best practices

The structure of the IHandler family, beginning with the IHandler interface and ending with the
HondaHandler.
is constructed with inheritance and composition.

The HondaHandler inherits functionality from the trunk, GenericHandler. But GenericHandler is actually
formed of inheritance from IGenericHandler and IHandler, but also of composition with another family
tree, IISOReply. So the trunk HondaHandler inherits is already a compositional mixture and not a strict
inheritance hierarchy of 'is a' relationships. The mixing of the ISOMeasurementsTOISOReply functions
and an IISOReply family member into the IHandler family hierarchy effectively constrains handlers
derived from GenericHandler to being OBD II-based handlers.

HondaHandler is also formed with a mix of inheritance and composition; inheriting from GenericHandler
but also formed by being composed with IGenericAsyncHandler.

In principle, if the classes form a definite tight hierarchy and really could not be split into separate pieces
that are independently functional, use inheritance. Another rule of thumb is if the relationship is a direct
building *upon* , use inheritance. If the relationship is a kind of association with, or equal level
partnership, use composition. If the classes could be split, use a composition approach to achieve the right
mix of 'lateral' building blocks.

The GenericHandler is made concrete on the OBD II side and left abstract on the Honda side because this is
the most likely usage pattern. The next logical step for the IHandler family tree would be to branch off at
the GenericHandler level to create a concrete implementation of another proprietary standard that needed to
adapt to the now ubiquitious OBD II standard. This is a likely re-use pattern, but not the only one afforded
by the generalization topology described here. It is hard to conceptualize exactly what could be constructed
with the existing modules and interfaces ( or contracts ) but that is the beauty of infrastructure in the
designer's opinion.

Generalization principles serve as design heuristics when building re-usable infrastructure ( or tooling ) to
build Mediators like GenericHandler where one or more sides may be unknown at the time of construction
of the tooling.

Mixins
The IHandler family may benefit from a more formal analysis and re-design using mixins, which are a
combination of methods from separate classes.

Traits
The IHandler family may benefit from a more formal analysis and re-design using traits, a set of methods
that implement a behaviour and a set of methods that parameterize that behaviour.

See Also, _Class References_ "GenericHandler", "HondaHandler"


Template:

IHandler provides a template for any class that may be used as a handler, by CommMessageProcessor.
HondaHandler is the most derived class from this template.

IISOReply provides a template for any class that replies to the ISOPort. ISOReply_9141 and
ISOReply14320 build on this template.

GenericBusInitAttempt provides a template for classes that attempt OBD II protocol bus initialization.
Since there are three similar but nontheless unique possible methods of initialization (ISO9141,
ISO14230_slow, and ISO14230_fast) the shared code between these three is factored into the template and
only the unique characteristics remain in the most derived classes (BusInitAttempt_ISO9141,
BusInitAttempt_ISO14230_slow, BusInitAttempt_ISO14230_fast).

Information Hiding and Encapsulation


ISOPort and HondaPort encapsulate the checksum running total variable, improving code safety.

Each coordinator (BusCoordinator, CommCoordinator, HondaCommCoordinator) or controller (TryReady,


BusInitAttempt, CommMessageCollector, etc) hides its internal state, providing a standard interface. This
improves safety and reduces the need to understand the internal details to make changes to the logic using
these functional units.

HondaMap and ISOPID classes hide the low-level details of these data structures, providing a safer ( e.g. a
Honda address can never be inadvertently associated with wrong length because they are stored together ),
more convenient way of manipulating these logical units.

Command Pattern
GenericHandler contains a function ISOPID_TO_GENERIC_HANDLER_MAP(), that is built in a
command pattern that lends itself to further ad-hoc modification by adding executable code within the
respective command hooks.
Each ISOPID is treated as a command object, directing execution into the appropriate sensor data retrieval
function. This pattern is extensible, for example, easily allowing for two or more separate retrieval steps or
other ad-hoc functionality in the commanded subroutine.

Chain of Responsibility:
The functional colleagues CommMessageProcessor and HondaHandler exhibit characteristics of a Chain of
Responsibility pattern. The CommMessageProcessor does not directly handle the gathering of the data that
is required for the successful conversions of the PID it receives into Honda data. Rather, it relies on an
indirect Chain of Responsibility. When a PID is received by CommMessageProcessor, a delegating call to
IncomingPID is made. This shifts responsibility of actually completing this task onto another subsystem.

HondaHandler, AssignWork, GeneralAsyncRequest


The HondaHandler sends a message to HondaCommCoordinator using AssignWork to retrieve the
appropriate data from the HondaPort. The AssignWork function shifts the responsibility to the
HondaCommCoordinator to retrieve the data. The HondaCommCoordinator is given the responsibility for
retrieving the data, and is also given the tools, in the form of a Callback function pointer, to
*ASYNCHRONOUSLY* satisfy it's obligation of returning the data.

The BusCoordinator, BusInit and GenericBusInitAttempt (or GenericBusInitComplete) form a Chain of


Responsibility pattern. The BusCoordinator is responsible for the overall bus initialization sequences, but
it delegates responsibilty for this task to two associated classes, TryReady and BusInit. BusInit then
transfers responsibility to its' delegates GenericBusInitAttempt and GenericBusInitComplete. Thus the
responsibility for certain actions is transferred in a chain.

The main event loop and select parts of the class design could be considered an example of Responsibility
Driven Design, using a Clustered/Delegated control Structure.

State:

Many examples:
The main event loop stores state about the overall OBD II connection state with two macro level states,
NO_COMM and COMM_ESTAB.
BusCoordinator stores state about the state of bus handshaking.
BusInitAttempt stores state about the state of the bus initialization attempt.
Each of these units is called repeatedly to accomplish a single logical task and they keep track of where
they are by a State pattern; they are guided and alter their behaviour based on their transient state.

DoneWorking() and Reset() provide consistent, safe, encapsulated access to the state.

The main event loop and all the subsidiary loops contained within could all be considered an example of
one complex Automaton. These structures could also be described as finite state machines.

Strategy:
The implementation of IHandler.IncomingPID() by HondaHandler could be considered one Strategy for
dealing with the incoming OBD II request. This larger unit of work seems to qualify as a strategy and
diverges from the Template definition of mere alteration of subtle details by a class deriving from a
Template.
Compare Template.

Mediator:

GenericHandler is a mediator because it is the only class that knows the internal details of two systems: the
OBD II system and the Honda system.

From the perspective of the CommMessageProcessor, the IHandler base interface presents a simplified
interface to the Honda side that consists of only one function call, IncomingPID.

From the perspective of the Honda side GenericHandler presents a simplified interface to the OBD II side
that consists of a group of ISOMeasurementsToISOReply...XXXX reply functions. The input and
operation of these functions is well defined and simple.

Thus the GenericHandler serves as a mediator, providing a simplified interface to a complex task of
obtaining Honda sensor data as well a providing a simplified interface to a complex task of sending data to
the OBD II port using the proper protocol, mediating these two sides of the system.

Adapter:
At a macro level, the entire hardware / software system is an Adapter, converting and translating the
requests and data from one system into a format compatible with another system.
SERIAL PORTS

Software Serial
The SoftwareSerial library was chosen for two reasons. The Arduino only has one hardware serial port,
and the best use for this port is as a debugging aid to see the trace messages from the monitor.

The other reason is that there is a need to operate the serial port at a non-standard Honda baud rate. The
serial timing in SoftwareSerial.h was adjusted, though minimally, for 9470 baud. The original timing for
9600 baud worked before these adjustments were made. This baud rate is relatively low and the difference
between the Honda rate and the standard rate is small, so it is possible these timing adjustments are
superfluous.

Due to the nature of the SoftwareSerial interrupt driven design, only one serial port can be active at any
time. Therefore, at precise junctions in the control structure, each port is activated with a .listen()
statement.

Half Duplex. This section is unconfirmed and based on the best knowledge of the designer.

SoftwareSerialWithHalfDuplex is used because the communication line on the Honda Diagnostic Link
Connector is half duplex, that is, only one member, the client (HonOBDapt) or the server (on board
computer, ECU) can transmit data at one time. SoftwareSerialWithHalfDuplex supports this configuration
by specifying the same pin for Transmit and Receive. It is the responibillty of the
HondaCommCoordinator to enforce this constraint, as the behaviour of SoftwareSerialWithHalfDuplex if
an attempt is made to transmit and receive at the same time is unknown.

The OBD II ISOPort is also half duplex, but is connected to the MC33660 which has a full duplex style of
connection with both Receive and Transmit pin connections. This provides a physical, electrical way of
enforcing half duplex on the OBD II K-Line, as a communications participant attempting to bring the line
low or high while the other participant is still transmitting will simply be ignored by the circuitry in the
MC33660.

MC33660

MC33660 is a level conversion integrated circuit that converts 5 volt logic signals to 12 volt logic signals.
This is necessary because the main hardware component, the Arduino, uses 5 volt signals on its' digital I/O
pins and OBD II specifies 12 volt signals. MC33660 is an integrated circuit specifically designed for logic
level conversion in an OBD II automotive application and should therefore be particularly suited to this
application in terms of susceptibility to noise, overload protection, etc. MC33660 is designed to be
connected to the K-Line bus and therefore provides an appropriate electrical interface for serial
communications and bus-style level transitions.

Arduino

Arduino provides 5 volt logic level digital I/O pins that are suited to this application. The application of a
software library that allows serial communications on any pin provides significant flexibility. Arduino has
flexible power supply requirements and can be operated on a 12 volt system. Nominal current draw
through the onboard voltage regulator in this application is 200mA. Heat created by the voltage regulator
dropping 12 volts to 5 volts to power the on board processor is dissipated by a small on-board heatsink, so
the current limits and supply voltage combination must be carefully selected. The current configuration is
within limits to the best knowledge of the designer and has been anecdotally confirmed by feeling for
excess heat from the voltage regulator.

OBD II
The OBD II electrical and logical communications standard is actually a conglomeration of many existing
standards at the time of creation of the OBD II 'standard' by ISO. This adapter supports the K-Line
standard(s) of ISO-9141, ISO-14230_slow, and ISO-14230_fast. Only one is technically required as any
OBD II compliant scanner must be able to work with all three, but the implementations vary only
minimally and provide a good exercise and example of certain software design techniques.

TODO

----Asynchronous Multitasking:

There should be an ITask interface to constrain and define the exact nature of the
controller(s) that have the Work(), DoneWorking(), and Reset() functions.

-----Handler class family construction:

The sensor data virtual function prototypes in GenericHandler should be split into a
separate interface definition, then implemented virtually by GenericHandler and
concretely by more derived classes like HondaHandler.

The ISOMeasurementToISOReply...XXXX family of functions should be extracted out


of GenericHandler and put in a separate mix-in.

The IGeneralAsyncHander should be expanded and more clearly defined to include the
GeneralAsyncRequest function defined in an ad-hoc manner in HondaHandler.

----Other

The HondaCommCoordinator should implement an IWorkQueue interface, clarifying the


role of the Assignwork function.

The magic number(s) of the digital I/O pins used for serial communications in
SoftwareSerial should be extracted out into a #define.

You might also like