Restore /etc/dev-mapping.conf support 70/25870/2 10.92.0 10.93.0 11.91.0 11.92.0 12.90.0 12.90.1 12.91.0 12.92.0 12.93.0 13.93.0 koi/10.92.0 koi/10.93.0 koi_10.92.0 koi_10.93.0 lamprey/11.91.0 lamprey/11.92.0 lamprey_11.91.0 lamprey_11.92.0 marlin/12.90.0 marlin/12.90.1 marlin/12.91.0 marlin/12.92.0 marlin/12.93.0 marlin_12.90.0 marlin_12.90.1 marlin_12.91.0 marlin_12.92.0 marlin_12.93.0 needlefish/13.93.0 needlefish_13.93.0
authorScott Murray <scott.murray@konsulko.com>
Wed, 6 Jan 2021 23:30:03 +0000 (18:30 -0500)
committerScott Murray <scott.murray@konsulko.com>
Mon, 11 Jan 2021 22:57:06 +0000 (17:57 -0500)
Add back the ini-config and config-parser code that existed
previously, and use it in binding init to over-ride the device
mapping from the controller JSON if /etc/dev-mapping.conf exists.
This restores the documented behavior, and is needed for the
existing AGL demo platform support and soon CI.

Additionally:
- Add code to validate the active_message_set, diagnostic_bus, and
  bus device mapping configuration values.
- The above required moving plugin loading before the configuration
  callback in the controller configuration, but this change seems
  rational in that everything required by the generated plugin code
  is already initialized before then, and it makes validating the
  configuration possible without adding an extra callback.
- Add logging of the used CAN bus to device mappings at info level
  to ease debugging any future issues.
- Tweak the log level of the missing configuration file message to
  info from error, since it is a legitimate mode of operation if
  relying on the default bus values in the controller JSON.

Bug-AGL: SPEC-3755

Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Change-Id: I440f5e0fc85be41f7c4c1f47d824a403525a18f9

libs/ini-config/CMakeLists.txt [new file with mode: 0644]
libs/ini-config/ini-config.cpp [new file with mode: 0644]
libs/ini-config/ini-config.hpp [new file with mode: 0644]
low-can-binding/CMakeLists.txt
low-can-binding/binding/low-can-cb.cpp
low-can-binding/can/can-bus.cpp
low-can-binding/can/can-bus.hpp
low-can-binding/can/message-definition.cpp
low-can-binding/utils/config-parser.cpp [new file with mode: 0644]
low-can-binding/utils/config-parser.hpp [new file with mode: 0644]

diff --git a/libs/ini-config/CMakeLists.txt b/libs/ini-config/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ceb4825
--- /dev/null
@@ -0,0 +1,38 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: Romain Forlot <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(ini-config)
+
+       # Define project Target
+       add_library(${TARGET_NAME} STATIC ${TARGET_NAME}.cpp)
+
+       # Binder exposes a unique public entry point
+       SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+               OUTPUT_NAME ${TARGET_NAME}
+       )
+
+       # Define target includes
+       TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME}
+               PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
+       )
+
+       # Library dependencies (include updates automatically)
+       TARGET_LINK_LIBRARIES(${TARGET_NAME}
+               ${link_libraries})
diff --git a/libs/ini-config/ini-config.cpp b/libs/ini-config/ini-config.cpp
new file mode 100644 (file)
index 0000000..e429b95
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015, 2016 ,2017 "IoT.bzh"
+ * Author "Loïc Collignon" <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ini-config.hpp"
+
+bool starts_with(const std::string& text, const std::string& token)
+{
+       if(text.length() < token.length()) return false;
+       return (text.compare(0, token.length(), token) == 0);
+}
+
+void ini_config::read_file(const std::string& filename)
+{
+       std::ifstream f(filename);
+       if (f)
+       {
+               std::regex r_section("^\\s*\\[([^\\]]+)\\]\\s*(#.*)?$");
+               std::regex r_key("^\\s*([^\\s]+)\\s*=\\s*\"([^\"]+)\"\\s*(#.*)?$");
+               std::string current_section;
+               std::string line;
+               while (std::getline(f, line))
+               {
+                       std::smatch mr;
+
+                       switch (qualify(line))
+                       {
+                       case line_type::section:
+                               if (std::regex_match(line, mr, r_section) && mr.size() >= 2 && mr[1].matched)
+                               {
+                                       current_section = mr[1].str();
+                               }
+                               break;
+                       case line_type::key:
+                               if(std::regex_match(line, mr, r_key) && mr.size() >= 2 && mr[1].matched)
+                               {
+                                       std::string key = current_section + '/' + mr[1].str();
+                                       config_[key] = (mr.size() >= 3 && mr[2].matched) ? mr[2].str() : "";
+                               }
+                               break;
+                       case line_type::ignore:
+                               break;
+                       }
+               }
+       }
+}
+
+ini_config::map ini_config::get_keys(const std::string& section, bool wo_prefix)
+{
+       map ret;
+       std::string key;
+
+       std::string prefix = section + '/';
+       for(auto i = config_.begin();
+               i != config_.end();
+               ++i)
+       {
+               if (starts_with(i->first, prefix))
+               {
+                       if(wo_prefix)
+                               key = i->first.substr(section.size()+1);
+                       else
+                               key = i->first;
+                       ret[key] = i->second;
+               }
+       }
+       return ret;
+}
+
+std::string ini_config::get_value(const std::string& section, const std::string& key)
+{
+       return config_[section + '/' + key];
+}
+
+ini_config::line_type ini_config::qualify(std::string& line)
+{
+       if (line.size())
+       {
+               for (std::string::value_type c : line)
+               {
+                       if (c == '#') return line_type::ignore;
+                       if (c == '[') return line_type::section;
+                       if (!std::isspace(c, std::locale("C"))) return line_type::key;
+               }
+       }
+       return line_type::ignore;
+}
diff --git a/libs/ini-config/ini-config.hpp b/libs/ini-config/ini-config.hpp
new file mode 100644 (file)
index 0000000..4c8cc11
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015, 2016 ,2017 "IoT.bzh"
+ * Author "Loïc Collignon" <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INI_CONFIG_HPP
+#define INI_CONFIG_HPP
+
+#include <string>
+#include <fstream>
+#include <map>
+#include <regex>
+#include <algorithm>
+
+// Représente un fichier de configuration.
+class ini_config
+{
+public:
+       using map = std::map<std::string, std::string>;
+
+       void read_file(const std::string& filename);
+
+       map get_keys(const std::string& section, bool wo_prefix=true);
+       std::string get_value(const std::string& section, const std::string& key);
+
+       typename map::size_type size() const { return config_.size(); }
+       typename map::iterator begin() { return config_.begin(); }
+       typename map::iterator end() { return config_.end(); }
+       typename map::const_iterator cbegin() const { return config_.cbegin(); }
+       typename map::const_iterator cend() const { return config_.cend(); }
+       typename map::reverse_iterator rbegin() { return config_.rbegin(); }
+       typename map::reverse_iterator rend() { return config_.rend(); }
+       typename map::const_reverse_iterator crbegin() const { return config_.crbegin(); }
+       typename map::const_reverse_iterator crend() const { return config_.crend(); }
+
+private:
+       map config_;
+
+       enum class line_type
+       {
+               ignore = 0,
+               section = 1,
+               key = 2
+       };
+
+       line_type qualify(std::string& line);
+};
+
+#endif // INI_CONFIG_HPP
index 38ac4b3..7f73339 100644 (file)
@@ -59,6 +59,7 @@ PROJECT_TARGET_ADD(low-can)
                #utils/socketcan-raw.cpp
                utils/socketcan-bcm.cpp
                utils/converter.cpp
+               utils/config-parser.cpp
        )
 
        add_library(${TARGET_NAME} SHARED ${SOURCES_LIB} ${SOURCES_J1939} ${SOURCES_ISOTP})
@@ -70,6 +71,7 @@ PROJECT_TARGET_ADD(low-can)
        )
 
        TARGET_LINK_LIBRARIES(${TARGET_NAME}
+               ini-config
                openxc-message-format
                uds-c
                isotp-c
index d994dd2..32adac8 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015, 2018 "IoT.bzh"
+ * Copyright (C) 2021 Konsulko Group
  * Author "Romain Forlot" <romain.forlot@iot.bzh>
  * Author "Loic Collignon" <loic.collignon@iot.bzh>
  *
@@ -23,6 +24,7 @@
 #include <queue>
 #include <mutex>
 #include <vector>
+#include <set>
 #include <thread>
 #include <algorithm>
 #include <wrap-json.h>
@@ -37,7 +39,7 @@
 #include "../utils/signals.hpp"
 #include "../diagnostic/diagnostic-message.hpp"
 #include "../utils/openxc-utils.hpp"
-#include "../utils/signals.hpp"
+#include "../utils/config-parser.hpp"
 
 #ifdef USE_FEATURE_J1939
        #include "../can/message/j1939-message.hpp"
@@ -56,7 +58,7 @@ int config_low_can(afb_api_t apiHandle, CtlSectionT *section, json_object *json_
        CtlConfigT *ctrlConfig = (CtlConfigT *) afb_api_get_userdata(apiHandle);
        int active_message_set = 0;
        json_object *dev_mapping = nullptr;
-       const char *diagnotic_bus = nullptr;
+       const char *diagnostic_bus = nullptr;
 
        if(! ctrlConfig || ! ctrlConfig->external)
                return -1;
@@ -64,35 +66,86 @@ int config_low_can(afb_api_t apiHandle, CtlSectionT *section, json_object *json_
        application_t *application = (application_t*) ctrlConfig->external;
 
        if(wrap_json_unpack(json_obj, "{si, s?s}",
-                             "active_message_set", &active_message_set,
-                             "diagnostic_bus", &diagnotic_bus))
+                           "active_message_set", &active_message_set,
+                           "diagnostic_bus", &diagnostic_bus)) {
+               AFB_ERROR("active_message_set and/or diagnostic_bus missing in controller JSON");
+               return -1;
+       }
+
+       if(active_message_set < 0 ||
+          active_message_set > (application->get_messages_definition().size() - 1)) {
+               AFB_ERROR("Invalid active message set %d", active_message_set);
                return -1;
+       }
+       application->set_active_message_set((uint8_t) active_message_set);
+
+       utils::config_parser_t conf_file("/etc/dev-mapping.conf");
+       if(conf_file.check_conf())
+       {
+               // If a mapping file in /etc exists, use it
+               AFB_INFO("Using /etc/dev-mapping.conf");
+               application->get_can_bus_manager().set_can_devices(conf_file.get_devices_name());
+       } else {
+               // Use whatever configuration is in the controller JSON
+               if(wrap_json_unpack(json_obj, "{so}",
+                                   "dev-mapping", &dev_mapping)) {
+                       AFB_ERROR("No device mapping in controller JSON");
+                       return -1;
+               }
+
+               application->get_can_bus_manager().set_can_devices(dev_mapping);
+       }
+
+       // Find all required buses
+       std::set<std::string> buses;
+       std::set<std::string> j1939_buses;
+       vect_ptr_msg_def_t msg_defs = application->get_messages_definition();
+       for(std::shared_ptr<message_definition_t> &msg_def : msg_defs) {
+               if(!msg_def->is_j1939())
+                       buses.insert(msg_def->get_bus_name());
+               else
+                       j1939_buses.insert(msg_def->get_bus_name());
+       }
+#ifdef USE_FEATURE_J1939
+       // NOTE: With C++17 just: buses.merge(j1939_buses)
+       for(auto it = begin(j1939_buses); it != end(j1939_buses); ++it)
+               buses.insert(*it);
+#endif
 
-       application->set_active_message_set((uint8_t)active_message_set);
+       // Check that required buses have device mappings
+       for(auto it = begin(buses); it != end(buses); ++it) {
+               std::string dev = application->get_can_bus_manager().get_can_device_name(*it);
+               if(dev == "") {
+                       AFB_ERROR("No CAN device defined for bus \"%s\"", it->c_str());
+                       return -1;
+               }
+               AFB_INFO("Using CAN device %s for bus \"%s\"", dev.c_str(), it->c_str());
+       }
 
-       if(wrap_json_unpack(json_obj, "{so}",
-                           "dev-mapping", &dev_mapping))
+       // Check that diagnostic bus is one of the configured buses
+       if(diagnostic_bus && buses.count(diagnostic_bus) == 0) {
+               AFB_ERROR("No CAN device mapping defined for diagnostic bus \"%s\"", diagnostic_bus);
                return -1;
 
-       application->get_can_bus_manager().set_can_devices(dev_mapping);
+       }
 
        /// Initialize Diagnostic manager that will handle obd2 requests.
        /// We pass by default the first CAN bus device to its Initialization.
-       if(! diagnotic_bus || application_t::instance().get_diagnostic_manager().initialize(diagnotic_bus))
+       if(! diagnostic_bus || application_t::instance().get_diagnostic_manager().initialize(diagnostic_bus))
                AFB_WARNING("Diagnostic Manager: not initialized. No diagnostic messages will be processed.");
 
        return 0;
 }
 
 CtlSectionT ctlSections_[] = {
-       [0]={.key="config" , .uid="config", .info=nullptr,
-                .loadCB=config_low_can,
-                .handle=nullptr,
-                .actions=nullptr},
-       [1]={.key="plugins" , .uid="plugins", .info=nullptr,
+       [0]={.key="plugins" , .uid="plugins", .info=nullptr,
                .loadCB=PluginConfig,
                .handle=nullptr,
                .actions=nullptr},
+       [1]={.key="config" , .uid="config", .info=nullptr,
+                .loadCB=config_low_can,
+                .handle=nullptr,
+                .actions=nullptr},
        [2]={.key=nullptr , .uid=nullptr, .info=nullptr,
                .loadCB=nullptr,
                .handle=nullptr,
index 40e080f..11b7770 100644 (file)
@@ -69,6 +69,14 @@ void can_bus_t::set_can_devices(json_object *mapping)
        }
 }
 
+/// @brief Fills the CAN device map member with given values
+///
+/// @param[in] mapping configuration section.
+void can_bus_t::set_can_devices(const std::vector<std::pair<std::string, std::string> >& mapping)
+{
+       can_devices_mapping_ = mapping;
+}
+
 /// @brief Take a decoded message to determine if its value complies with the desired
 /// filters.
 ///
index 5aa7a1d..88b1dd4 100644 (file)
@@ -70,6 +70,7 @@ public:
        ~can_bus_t();
 
        void set_can_devices(json_object *mapping);
+       void set_can_devices(const std::vector<std::pair<std::string, std::string> >& mapping);
 
        int get_can_device_index(const std::string& bus_name) const;
        const std::string get_can_device_name(const std::string& id_name) const;
index dab502a..051c2d3 100644 (file)
@@ -56,6 +56,10 @@ message_definition_t::message_definition_t(const std::string bus,
        signals_{signals}
 {}
 
+const std::string message_definition_t::get_bus_name() const{
+       return bus_;
+}
+
 const std::string message_definition_t::get_bus_device_name() const
 {
        return application_t::instance().get_can_bus_manager()
diff --git a/low-can-binding/utils/config-parser.cpp b/low-can-binding/utils/config-parser.cpp
new file mode 100644 (file)
index 0000000..589f034
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015, 2016 ,2017 "IoT.bzh"
+ * Copyright (C) 2021 Konsulko Group
+ * Author "Romain Forlot" <romain.forlot@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config-parser.hpp"
+
+#include "../binding/low-can-hat.hpp"
+
+namespace utils
+{
+       /// @brief constructor using path to file
+       config_parser_t::config_parser_t(std::string conf_file)
+               : filepath_{conf_file}, config_content_{}
+       {
+               config_content_.read_file(conf_file);
+       }
+
+       const std::string& config_parser_t::filepath() const
+       {
+               return filepath_;
+       }
+
+       /// @brief read the conf_file_ and parse it into an INIReader object
+       /// to search into later.
+       bool config_parser_t::check_conf()
+       {
+               if (config_content_.size() <= 0)
+               {
+                       AFB_INFO("Cannot load INI config file: %s.", filepath_.c_str());
+                       return false;
+               }
+               AFB_DEBUG("Configuration file parsed");
+               return true;
+       }
+
+       /// @brief Public method to access devices_name_ vector. If vector size equal 0
+       /// then it will parses the configuration file content to fill it. It could be empty even
+       /// after parsing if content file just don't have a correct "canbus" directive so you
+       /// have to test the returned value.
+       ///
+       /// @return A const vector with string of linux CAN devices.
+       const std::vector<std::pair<std::string, std::string> > config_parser_t::get_devices_name()
+       {
+               std::vector<std::pair<std::string, std::string> > devices_name;
+
+               std::map<std::string, std::string> bus_mapping = config_content_.get_keys("CANbus-mapping");
+               for(const auto& busIt : bus_mapping )
+               {
+                       devices_name.push_back(std::make_pair(busIt.first, busIt.second));
+               }
+
+               return devices_name;
+       }
+}
diff --git a/low-can-binding/utils/config-parser.hpp b/low-can-binding/utils/config-parser.hpp
new file mode 100644 (file)
index 0000000..3115e9b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015, 2016 ,2017 "IoT.bzh"
+ * Author "Romain Forlot" <romain.forlot@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <utility>
+#include "ini-config.hpp"
+
+namespace utils
+{
+       /// @brief A configuration file parser that handle INI configuration
+       /// file format.
+       class config_parser_t
+       {
+       private:
+               const std::string filepath_; /*!< filepath_ - Path to the config file*/
+               ini_config config_content_; /*!< config_content_ - Parsed content of INI file.*/
+
+       public:
+               config_parser_t(config_parser_t&&) = default;
+               config_parser_t(const config_parser_t&) = default;
+               explicit config_parser_t(std::string conf_file);
+
+               const std::string& filepath() const;
+               bool check_conf();
+               const std::vector<std::pair<std::string, std::string> > get_devices_name();
+       };
+}