|
| 1 | +# DR004 Direct Usage of Hardware Abstraction Layer |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +Currently, we are in violation of the requirement to only use libraries and frameworks through adapters, as mandated by the objectives of the software architecture (see |
| 6 | +[relevant section in Software Architecture](<\ref flexible_structure> "relevant section in Software Architecture")). |
| 7 | +Currently, we utilize an implementation of the [ArduinoCore-API][ACA] (ACA) as a hardware abstraction layer (HAL) from outside the adapters layer. |
| 8 | +More specifically, we employ an implementation of the ACA from Espressif, tailored for ESP32s. |
| 9 | + |
| 10 | +It is generally understood that using HALs is only necessary from the |
| 11 | +[board adapters package](<\ref board_adapters> "board adapters package"). |
| 12 | + |
| 13 | +[ACA]: https://github.com/arduino/ArduinoCore-API |
| 14 | + |
| 15 | +## Options |
| 16 | + |
| 17 | +There are several options for resolving this violation of our own rules. |
| 18 | + |
| 19 | +### Option 1 |
| 20 | + |
| 21 | +Adhere to the current guidelines. |
| 22 | + |
| 23 | +According to the current state of the software architecture, we should use the 3rd party HAL indirectly through adapters. |
| 24 | +These adapters would reside in the |
| 25 | +[3rd party adapters package](<\ref third_party_adapters> "3rd party adapters package"). |
| 26 | + |
| 27 | +#### Option 1a) |
| 28 | + |
| 29 | +For an object-oriented (OO) variant, defining an interface and inheriting from it would require: |
| 30 | + |
| 31 | +- Defining one or several interfaces inside the |
| 32 | + [board adapters package](<\ref board_adapters> "board adapters package") |
| 33 | + (header files). |
| 34 | +- Implementing those as adapters inside the |
| 35 | + [3rd party adapters package](<\ref third_party_adapters> "3rd party adapters package") |
| 36 | + (source files). |
| 37 | +- Defining interfaces for the factories (see |
| 38 | + [dependency injection](<\ref dependency_injection> "dependency injection")) |
| 39 | + inside the |
| 40 | + [board adapters package](<\ref board_adapters> "board adapters package") |
| 41 | + (header files). |
| 42 | +- Implementing those factories inside the |
| 43 | + [3rd party adapters package](<\ref third_party_adapters> "3rd party adapters package") |
| 44 | + (source files). |
| 45 | + |
| 46 | +This is the typical way dependency injection, and therefore the |
| 47 | +[dependency rule](<\ref interpretation_dependency_rule> "dependency rule"), |
| 48 | +is implemented. |
| 49 | + |
| 50 | +#### Option 1b) |
| 51 | + |
| 52 | +Instead of using an object-oriented approach using inheritance, one could create an interface to the HAL based on free functions. |
| 53 | + |
| 54 | +This option would require: |
| 55 | + |
| 56 | +- Defining one or several interfaces (free function declarations) inside the |
| 57 | + [board adapters package](<\ref board_adapters> "board adapters package") |
| 58 | + (header files). |
| 59 | +- Implementing those as adapters inside the |
| 60 | + [3rd party adapters package](<\ref third_party_adapters> "3rd party adapters package") |
| 61 | + (source files). |
| 62 | +- If the build configuration (see [`lib_ldf_mode`][1]) requires it, one may have to define |
| 63 | + [proxy headers](<\ref proxy_header> "proxy headers") |
| 64 | + (header files). |
| 65 | + |
| 66 | +Factories are not necessary because this approach does not rely on objects. |
| 67 | + |
| 68 | +[1]: https://docs.platformio.org/en/latest/projectconf/sections/env/options/library/lib_ldf_mode.html "PlatformIO documentation of lib_ldf_mode" |
| 69 | + |
| 70 | +#### Option 1c) |
| 71 | + |
| 72 | +A hybrid approach (OO and free functions) is also possible. |
| 73 | +This would fit well with the ACA as it also consists of classes and free functions. |
| 74 | + |
| 75 | +The required steps would be a mixture of those of options 1a) and 1b). |
| 76 | + |
| 77 | +### Option 2 |
| 78 | + |
| 79 | +Change the current guidelines so that direct dependency on 3rd party HALs is allowed within hardware-related packages (currently only the |
| 80 | +[board adapters package](<\ref board_adapters> "board adapters package")). |
| 81 | + |
| 82 | +This approach eliminates the requirement to define 3rd party adapters for the HAL. |
| 83 | + |
| 84 | +In order to test code depending on the ACA, the 3rd party interface needs to be stubbed. |
| 85 | +*For vanilla ACA, stubbing the proprietary interface can be greatly simplified by using [ArduinoFake][].* |
| 86 | +We utilize an [implementation of the Arduino Core API (ACA) specific for ESP32s by Espressif](https://github.com/espressif/arduino-esp32/) which extends the ACA. |
| 87 | +Following this option's approach, to directly use ACA and ArduinoFake for testing, we would need to manage the extensions separately. |
| 88 | +The following variants of this option specify different methods to handle the extensions. |
| 89 | + |
| 90 | +[ArduinoFake]: https://platformio.org/lib/show/1689/ArduinoFake "ArduinoFake in PlaftormIO's registry" |
| 91 | + |
| 92 | +#### Option 2a) |
| 93 | + |
| 94 | +Instead of directly using a modified Arduino interface provided by the extensions (for example for a specific `class`), we use an adapter. |
| 95 | +The adapters would be integrated just as for option 1. |
| 96 | +But compared to option 1, this only needs to be done for those smallest indivisible interfaces which are not compatible with the vanilla ACA. |
| 97 | + |
| 98 | +#### Option 2b) |
| 99 | + |
| 100 | +At those places in the source code where extensions are used, we introduce conditionally compiled sections. |
| 101 | +One section uses the modified version of the ACA, necessary for the productive code. |
| 102 | +The alternative section is used when compiling for native tests. |
| 103 | +It uses the vanilla ACA which can be simply stubbed using ArduinoFake. |
| 104 | + |
| 105 | +The selection of conditionally compiled sections is done using `constexpr if`, where possible. |
| 106 | +Else, the C preprocessor (CPP) is used for conditional inclusion (`#if`, ...). |
| 107 | + |
| 108 | +## Comparison of Options |
| 109 | + |
| 110 | +### Comparing variants Option 1a), b), and c) |
| 111 | + |
| 112 | +Assuming that we do define interfaces for one or several HALs, we should not follow the structure of the currently used one just for convenience (as option 1c suggests). |
| 113 | +This could complicate writing adapters for different HAL implementations. |
| 114 | +Allowing variants is one of the purposes of the adapters. |
| 115 | + |
| 116 | +Instead, we should design the interfaces to fit the usage. |
| 117 | +In general, an object-oriented approach is sensible to represent the different peripheral components available through HALs. |
| 118 | +Also, an object-oriented approach is the most straightforward for implementing dependency inversion. |
| 119 | + |
| 120 | +From these variants, **option 1a)** would be the best. |
| 121 | + |
| 122 | +### Comparing Options 1a), 2a), and 2b) |
| 123 | + |
| 124 | +- **Option 1a)**: Implement and use adapters for the complete 3rd party HAL(s) |
| 125 | + - Pros: |
| 126 | + - Clean separation of concerns, dependency inversion, and injection. |
| 127 | + - Fits the overall design to use dependency inversion to fulfill the |
| 128 | + [dependency rule](<\ref interpretation_dependency_rule> "dependency rule"). |
| 129 | + - Using a different implementation of our - to be defined - HAL interface would not require modifying the users of our HAL interface. |
| 130 | + Instead, only a different adapter - implementing our HAL interface - would be required.[¹](<\ref dr004_1 "¹") |
| 131 | + - Cons: |
| 132 | + - This approach requires writing adapters for all used HAL interfaces to 3rd party libraries. |
| 133 | + Designing a reusable interface is challenging, and the adapters may result in boilerplate code. |
| 134 | + Note that this approach would mean writing adapters to a HAL implementation, which is an adapter (to proprietary peripheral drivers) in itself. |
| 135 | +- **Option 2a)**: Use adapters only for extensions to vanilla ACA |
| 136 | + - Pros: |
| 137 | + - Compared to option 1a), only a fraction of the adapters would need to be written. |
| 138 | + - Cons: |
| 139 | + - Our board adapters would have a high degree of dependency on the ACA. |
| 140 | + The *Single Responsibility Principle* is violated[²](<\ref dr004_2 "²") as the board adapters would not only depend on the board and its hardware but also on an interface of a 3rd party HAL. |
| 141 | + - Dealing with the extensions made compared to vanilla ACA requires writing some adapters. |
| 142 | + - Adapting a variation from the ACA in one member function of a class requires creating an interface containing all used member functions of that class, even if those other member functions are used without alterations. |
| 143 | + This introduces *boilerplate code*. |
| 144 | +- **Option 2b)**: Use alternative sections within source files |
| 145 | + - Pros: |
| 146 | + - Reduces the need for additional code to a minimum. |
| 147 | + No adapters need to be written to use the HAL. |
| 148 | + - Cons: |
| 149 | + - Our board adapters would have a high degree of dependency on the ACA. |
| 150 | + The *Single Responsibility Principle* is violated[²](<\ref dr004_2 "²") even further (compared to option 2a)) by depending on the vanilla ACA required for testing in the native build environment only. |
| 151 | + Changes to the test infrastructure should not necessitate changes in the source file containing productive code. |
| 152 | + - In general, conditionally compiled sections of code reduces its readability. |
| 153 | + |
| 154 | +\note \anchor dr004_1 |
| 155 | +¹: *In practice*, our HAL interface may not be suitable to accommodate the new HAL implementation (see [Rule of Three](https://en.wikipedia.org/w/index.php%3Ftitle%3DRule_of_three_%28computer_programming%29%26oldid%3D1173684754)). |
| 156 | +Thus, we would have to adjust our HAL interface and the code using it accordingly. |
| 157 | +In other words, it is difficult to design a HAL interface such that we would actually benefit when changing the HAL implementation to a different 3rd party library. |
| 158 | +Thus, this advantage may not be relevant for that case. |
| 159 | +Also, in case the HAL implementation is changed, it may be less effort to adjust all the uses of the new HAL interface than to write adapters for the changed 3rd party library. |
| 160 | + |
| 161 | +\note \anchor dr004_2 |
| 162 | +²: The source file (of a non-adapter) would depend on a 3rd party interface. |
| 163 | +Changes to that 3rd party interface may necessitate changes to the source file even though the requirements to that source file remain the same. |
| 164 | + |
| 165 | +### Summary & Decision |
| 166 | + |
| 167 | +Writing adapters adds additional effort, potentially resulting in boilerplate code, and the advantage is dubious. |
| 168 | + |
| 169 | +After careful consideration, we've decided on: |
| 170 | + |
| 171 | +> **Option 2:** Allow direct dependencies from hardware-related code to 3rd party HAL interfaces. |
| 172 | +
|
| 173 | +And more specifically for testing on native environments, use: |
| 174 | + |
| 175 | +> **Option 2b):** Where the ACA is used, it is allowed to add small conditionally compiled sections for tests to cope with deviations of the ACA used in productive code from the vanilla ACA. |
| 176 | +
|
| 177 | +The latter relaxation is to facilitate the use of ArduinoFake to stub the HAL's interface. |
| 178 | + |
0 commit comments