When to use design patterns
I wonder if it is suitable to use builder pattern and facade pattern here.
The patterns from the book Design Patterns rely exclusively on object oriented features, and are often quite heavy handed if you follow them to the letter. Sometimes that is exactly what you need, but there are also problems where you can use generic programming or other programming paradigms, or where you can use a simpler form of a pattern.
Consider the builder pattern: yes, this can help create an object which otherwise needs lots of parameters to its constructor, but maybe the type of the object you want to create can just have set_*() functions, instead of having those in a separate builder type?
Naming things
The components in each design pattern are given names in the book, but they are rather abstract names, and they don't add much value to real code. In particular, don't use Fcd or Facade in your program, just name it FirmwareUpgrader.
Avoid unnecessary abbreviations, but if you really want to abbreviate, at least be consistent. What does m_fh_ugd mean? Why is there ugd in the name of that variable, but upg in the name of the builder for it?
Return values
It seems like you use the C way of returning a status code, where 0 means success and negative numbers mean an error. But what is the difference between -1, -2 and -3? They mean different things for different functions, and I have to read those functions to understand the meaning.
At the very least, make sure give names to status codes, for example by creating an enum, or create custom std::error_codes.
You can also consider using exceptions, although I know that is frowned upon in embedded code. C++23 introduces std::expected, which is a better way to return either a value or an error code from a function. If you cannot use C++23 yet, it's quite easy to find an implementation of it that works on C++11, or you can create one yourself.
Also note that main() shouldn't return negative numbers; use EXIT_FAILURE instead.
Prefer creating a new struct over using std::pair
While std::pair has its uses, prefer creating a new struct if you need to bundle two values together. The advantage is that you can give the members your own names, instead of being stuck with the rather non-descriptive first and second.
Avoid code duplication
Avoid repeating the same code multiple times; it saves you typing, avoids bugs and makes your code more maintainable. Consider having to add support for two more cameras: now you have to go and find all the places where you had separate lines of code for each camera, and make sure you duplicate and modify them correctly in all those places. Instead, you could do something like this:
class FirmwareUpgrader {
public:
enum CameraPosition {
front,
middle,
back,
count
};
int upgrade(CameraPosition position, const std::vector<std::uint8_t>& firmware, std::uint32_t blk_size) const;
…
private:
std::unique_ptr<CameraUpgrader> m_upgraders[CameraPosition::count];
…
};
int FirmwareUpgrader::upgrade(CameraPosition position, const std::vector<std::uint8_t>& firmware, uint32_t std::block_size) const {
if (!m_upgraders[position]) {
return -1;
}
if (0 != m_upgraders[position]->update(firmware, block_size)) {
return -2;
}
return 0;
}
Of course, you now might need to add some checks to avoid accessing the array out-of-bounds. Still, you can now easily add more cameras without needing to modify upgrade() at all. It is also more efficient than comparing the string type against all possible camera positions.