added cansim plugin
authorKevron Rees <kevron.m.rees@intel.com>
Fri, 21 Mar 2014 21:29:45 +0000 (14:29 -0700)
committerKevron Rees <kevron.m.rees@intel.com>
Fri, 21 Mar 2014 21:29:45 +0000 (14:29 -0700)
plugins/cansimplugin/CMakeLists.txt [new file with mode: 0644]
plugins/cansimplugin/cansimplugin.cpp [new file with mode: 0644]
plugins/cansimplugin/cansimplugin.h [new file with mode: 0644]
plugins/cansimplugin/test/CMakeLists.txt [new file with mode: 0644]
plugins/cansimplugin/test/main.cpp [new file with mode: 0644]
plugins/cansimplugin/test/ut_cansimplugin.cpp [new file with mode: 0644]

diff --git a/plugins/cansimplugin/CMakeLists.txt b/plugins/cansimplugin/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b827061
--- /dev/null
@@ -0,0 +1,18 @@
+if(cansim_plugin)
+############################################################################################################################################
+
+include_directories(${CMAKE_SOURCE_DIR}/lib ${include_dirs} ${gio_INCLUDE_DIRS} ${gio-unix_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/plugins/common ${Boost_INCLUDE_DIRS} ${json_INCLUDE_DIRS})
+
+set(cansimplugin_headers cansimplugin.h)
+set(cansimplugin_sources cansimplugin.cpp)
+
+add_library(cansimplugin MODULE ${cansimplugin_sources} ${cansimplugin_headers})
+set_target_properties(cansimplugin PROPERTIES PREFIX "")
+target_link_libraries(cansimplugin amb amb-plugins-common -L${CMAKE_CURRENT_BINARY_DIR}/plugins/common ${link_libraries} -lrt)
+
+install(TARGETS cansimplugin LIBRARY DESTINATION lib/automotive-message-broker)
+
+############################################################################################################################################
+
+endif(cansim_plugin)
+
diff --git a/plugins/cansimplugin/cansimplugin.cpp b/plugins/cansimplugin/cansimplugin.cpp
new file mode 100644 (file)
index 0000000..ad466a2
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+Copyright (C) 2012 Intel Corporation
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include <boost/assert.hpp>
+#include <glib.h>
+#include <deque>
+
+#include <vehicleproperty.h>
+#include <listplusplus.h>
+
+#include <logger.h>
+
+#include "cansimplugin.h"
+
+static const char* DEFAULT_CAN_IF_NAME = "vcan0";
+
+//----------------------------------------------------------------------------
+// CANSimPlugin
+//----------------------------------------------------------------------------
+
+// library exported function for plugin loader
+extern "C" AbstractSource* create(AbstractRoutingEngine* routingengine, std::map<std::string, std::string> config)
+{
+#ifndef UNIT_TESTS
+    DEBUG_CONF("cansimplugin",
+        CUtil::Logger::file_off|CUtil::Logger::screen_on,
+        CUtil::Logger::EInfo, CUtil::Logger::EInfo
+    );
+#endif
+    std::unique_ptr< AmbPlugin<CANSimPlugin> > plugin(new AmbPlugin<CANSimPlugin>(routingengine, config));
+    plugin->init();
+    return plugin.release();
+}
+
+//----------------------------------------------------------------------------
+// CANSimPlugin
+//----------------------------------------------------------------------------
+
+const VehicleProperty::Property MappingTable = "MappingTable";
+
+//
+// IVIPOC signals
+//
+const VehicleProperty::Property BatteryStatus = "BatteryStatus";
+PROPERTYTYPEBASIC(BatteryStatus, uint16_t)
+const VehicleProperty::Property FullBatteryRange = "FullBatteryRange";
+PROPERTYTYPEBASIC(FullBatteryRange, uint16_t)
+const VehicleProperty::Property Weather = "Weather";
+PROPERTYTYPEBASIC(Weather, uint16_t)
+const VehicleProperty::Property AvgKW = "AvgKW";
+PROPERTYTYPEBASIC(AvgKW, double)
+
+#define ADDPROPERTY(property, default_value, zone_value) \
+            addPropertySupport(zone_value, [](){ \
+                return new property ## Type(default_value); \
+            })
+
+CANSimPlugin::CANSimPlugin(AbstractRoutingEngine* re, const map<string, string>& config, AbstractSource& parent) :
+    AmbPluginImpl(re, config, parent)
+{
+    auto it = config.find("interfaces");
+    if(it != config.end() && it->second.length()){
+        std::unique_ptr<json_object, decltype(&json_object_put)> rootobject(json_tokener_parse(it->second.c_str()), &json_object_put);
+        if(rootobject){
+            g_assert(json_object_get_type(rootobject.get())==json_type_array);
+            array_list *ifacelist = json_object_get_array(rootobject.get());
+            if (ifacelist) {
+                for(int i=0; i < array_list_length(ifacelist); ++i)
+                {
+                    json_object* obj = (json_object*)array_list_get_idx(ifacelist,i);
+                    const char* str = obj ? json_object_get_string(obj) : nullptr;
+                    if(str){
+                        interfaces[str] = std::shared_ptr<CANBus>(new CANBus(*static_cast<CANObserver*>(this)));
+                    }
+                }
+            }
+        }
+    }
+    // Default interface if none has been configured.
+    if(interfaces.empty()){
+        interfaces[DEFAULT_CAN_IF_NAME] = std::shared_ptr<CANBus>(new CANBus(*static_cast<CANObserver*>(this)));
+    }
+
+    addPropertySupport(
+        Zone::None,
+        [](){
+            StringPropertyType *s = new StringPropertyType(MappingTable, "");
+            s->zone = Zone::None;
+            return s;
+        }
+    );
+
+    //
+    // IVIPOC signals
+    //
+    ADDPROPERTY(VehicleProperty::ChildLockStatus, false, Zone::None);
+    ADDPROPERTY(VehicleProperty::LightHead, false, Zone::None);
+    ADDPROPERTY(VehicleProperty::LightParking, false, Zone::None);
+    ADDPROPERTY(VehicleProperty::AirConditioning, false, Zone::None);
+    ADDPROPERTY(BatteryStatus, 58, Zone::None);
+    ADDPROPERTY(FullBatteryRange, 350, Zone::None);
+    ADDPROPERTY(VehicleProperty::ExteriorTemperature, 74, Zone::None);
+    ADDPROPERTY(VehicleProperty::InteriorTemperature, 68, Zone::None);
+    ADDPROPERTY(VehicleProperty::FrontWheelRadius, 0, Zone::None);
+    ADDPROPERTY(Weather, 1, Zone::None);
+    ADDPROPERTY(AvgKW, 28, Zone::None);
+    ADDPROPERTY(VehicleProperty::VehicleSpeed, 65, Zone::None);
+    ADDPROPERTY(VehicleProperty::Odometer, 75126, Zone::None);
+    ADDPROPERTY(VehicleProperty::TransmissionShiftPosition, Transmission::Drive, Zone::None);
+    ADDPROPERTY(VehicleProperty::NightMode, false, Zone::None);
+    ADDPROPERTY(VehicleProperty::ExteriorBrightness, 1000, Zone::None);
+    // HVAC
+    ADDPROPERTY(VehicleProperty::LightHazard, false, Zone::None);
+    ADDPROPERTY(VehicleProperty::SeatHeater, 0, Zone::FrontLeft);
+    ADDPROPERTY(VehicleProperty::SeatHeater, 0, Zone::FrontRight);
+    ADDPROPERTY(VehicleProperty::AirRecirculation, false, Zone::None);
+    ADDPROPERTY(VehicleProperty::AirflowDirection, HVAC::Front, Zone::None);
+    ADDPROPERTY(VehicleProperty::FanSpeed, 200, Zone::None);
+    ADDPROPERTY(VehicleProperty::TargetTemperature, 68, Zone::Left);
+    ADDPROPERTY(VehicleProperty::TargetTemperature, 68, Zone::Right);
+
+    ADDPROPERTY(VehicleProperty::Defrost, false, Zone::Front);
+    ADDPROPERTY(VehicleProperty::Defrost, false, Zone::Rear);
+
+    ADDPROPERTY(VehicleProperty::VehiclePowerMode, Power::Run, Zone::None);
+    // TirePresure
+       ADDPROPERTY(VehicleProperty::TirePressure, 2.3, Zone::FrontLeft);
+       ADDPROPERTY(VehicleProperty::TirePressure, 2.3, Zone::FrontRight);
+       ADDPROPERTY(VehicleProperty::TirePressure, 2.4, Zone::RearLeft);
+       ADDPROPERTY(VehicleProperty::TirePressure, 2.4, Zone::RearRight);
+}
+
+CANSimPlugin::~CANSimPlugin()
+{
+    for(auto it = interfaces.begin(); it != interfaces.end(); ++it){
+        it->second->stop();
+    }
+}
+
+void CANSimPlugin::init()
+{
+    AmbPluginImpl::init();
+    for(auto it = interfaces.begin(); it != interfaces.end(); ++it){
+        it->second->start(it->first.c_str());
+    }
+}
+
+void CANSimPlugin::supportedChanged(const PropertyList& supportedProperties)
+{
+    const_cast<PropertyList&>(supportedProperties).remove(MappingTable);// CANSimPlugin has own copy of the PropertyList from AmbPlugin
+    createMappingTable(supportedProperties);
+}
+
+int CANSimPlugin::supportedOperations() const
+{
+    return AbstractSource::Get | AbstractSource::Set;
+}
+
+void CANSimPlugin::createMappingTable(const PropertyList& /*supported*/)
+{
+    //
+    // Local helper classes
+    //
+    class JsonObject : public std::unique_ptr<json_object, decltype(&json_object_put)>
+    {
+    public:
+        JsonObject(json_object* object) : std::unique_ptr<json_object, decltype(&json_object_put)>(object, &json_object_put) {}
+    };
+
+    class PROPERTY{
+    public:
+        PROPERTY(const VehicleProperty::Property& propertyName, const Zone::Type& z, int canId) :
+            name(propertyName),
+            zone(z),
+            can_id(canId)
+        {
+        }
+        PROPERTY(const PROPERTY& other) = delete;
+        PROPERTY& operator=(const PROPERTY& other) = delete;
+        PROPERTY(PROPERTY&& other) = default;
+        PROPERTY& operator=(PROPERTY&& other) = default;
+        JsonObject toJson()
+        {
+            JsonObject prop(json_object_new_object());
+            json_object_object_add(prop.get(), "can_id", json_object_new_int(can_id));
+            json_object_object_add(prop.get(), "name", json_object_new_string(name.c_str()));
+            json_object_object_add(prop.get(), "zone", json_object_new_int(static_cast<int>(zone)));
+            return prop;
+        }
+    private:
+        std::string name;
+        Zone::Type zone;
+        int can_id;
+    };
+
+    //
+    PropertyList allProperties(VehicleProperty::capabilities());
+    allProperties.remove(MappingTable);
+
+    //
+    // Create mapping table in JSON format
+    //
+    map< std::string, std::deque<PROPERTY> > table;
+    PropertyList addedProperties;
+    PropertyList removedProperties;
+    std::map< canid_t, std::tuple< std::string, VehicleProperty::Property, Zone::Type> > newMappingTable;
+    int can_id = 10; // Let's have a space for a special messages. Just in case .... in the future.
+    for(PropertyList::const_iterator propIt = allProperties.begin(); propIt != allProperties.end(); ++propIt)
+    {
+        VehicleProperty::Property propertyName(*propIt);
+
+        std::list<std::string> sources(routingEngine->sourcesForProperty(propertyName));
+        size_t size = sources.size();
+
+        bool IAmTheSource = ListPlusPlus<std::string>(&sources).contains(uuid());
+
+        if(size == 0 || size == 1 && IAmTheSource) {
+            if( size == 0 ){
+                // I'm the source from now
+                ZonePropertyType& zonePropType = properties[propertyName];
+                std::shared_ptr<AbstractPropertyType> value(VehicleProperty::getPropertyTypeForPropertyNameValue(propertyName));
+                if(value){
+                    value->zone = Zone::None;
+                    zonePropType.insert(make_pair(Zone::None, value));
+                    addedProperties.push_back(propertyName);
+                }
+                else{
+                    properties.erase(propertyName);
+                }
+            }
+            std::string source(uuid());
+            //PropertyInfo info(routingEngine->getPropertyInfo(propertyName,source));
+            //Zone::ZoneList zones(info.zones());
+            //if(zones.empty())
+            Zone::ZoneList zones;
+            {
+                for(int i = 0; i< 10; ++i){
+                    Zone::Type zone(Zone::None);
+                    if(i)
+                        zone = static_cast<Zone::Type>(1 << i);
+                    zones.push_back(zone);
+                }
+                zones.push_back(Zone::FrontRight);
+                zones.push_back(Zone::FrontLeft);
+                zones.push_back(Zone::MiddleRight);
+                zones.push_back(Zone::MiddleLeft);
+                zones.push_back(Zone::RearRight);
+                zones.push_back(Zone::RearLeft);
+            }
+            for( auto z=zones.begin(); z != zones.end(); ++z ){
+                table[source].push_back(PROPERTY(propertyName, *z, can_id));
+                newMappingTable[can_id++] = make_tuple(source, propertyName, *z);
+            }
+        }
+        else if(IAmTheSource){
+            // I'm the source, and there is another source
+            properties.erase(propertyName);// I don't need to simulate it anymore
+            removedProperties.push_back(propertyName);
+        }
+    }
+
+    if(addedProperties.size() || removedProperties.size()) {
+        JsonObject sources(json_object_new_array());
+        for(auto it = table.begin(); it != table.end(); ++it) {
+            // one source object:
+            JsonObject source(json_object_new_object());
+            JsonObject description(json_object_new_object());
+            json_object_object_add(description.get(), "guid", json_object_new_string(it->first.c_str()));
+            json_object_object_add(source.get(), "source", description.release());
+            // signals:
+                       JsonObject sigs(json_object_new_array());
+            for(auto signalIt = it->second.begin(); signalIt != it->second.end(); ++signalIt) {
+                               json_object_array_add(sigs.get(), signalIt->toJson().release());
+            }
+            // add signals into source
+                       json_object_object_add(source.get(), "signals", sigs.release());
+            // add one source into sources array
+            json_object_array_add(sources.get(), source.release());
+        }
+        // result json:
+        JsonObject result(json_object_new_object());
+        json_object_object_add(result.get(), "sources", sources.release());
+
+        std::string mappingTableValue(json_object_to_json_string(result.get()));
+
+        std::replace(mappingTableValue.begin(), mappingTableValue.end(), '"', '\'');// replace all " to '
+        auto tableProperty = properties[MappingTable][Zone::None];
+        if(tableProperty){
+            // we have a new MappingTable
+            mappingTable.swap(newMappingTable);
+            tableProperty->setValue(mappingTableValue);
+            routingEngine->updateProperty(tableProperty.get(), uuid());
+        }
+
+               routingEngine->updateSupported(addedProperties, removedProperties, &parent);
+    }
+}
+
+// from CANObserver
+void CANSimPlugin::errorOccured(CANObserver::CANError error)
+{
+       (void) error;
+    LOG_INFO( "CANSimPlugin::errorOccured() not implemented "<< std::endl );
+}
+
+void CANSimPlugin::standardFrameReceived(const can_frame& frame)
+{
+       (void) frame;
+    LOG_INFO( "CANSimPlugin::standardFrameReceived() not implemented "<< std::endl );
+}
+
+void CANSimPlugin::extendedFrameReceived(const can_frame& frame)
+{
+    LOG_INFO("CANSimPlugin::extendedFrameReceived()");
+    printFrame(frame);
+
+    auto it = mappingTable.find(frame.can_id);
+    if( it == mappingTable.end()){
+        LOG_WARNING("can_id not found");
+        return;
+    }
+
+    std::string source(std::get<0>(it->second));
+    VehicleProperty::Property name(std::get<1>(it->second));
+    Zone::Type zone(std::get<2>(it->second));
+    AbstractPropertyType* value = findPropertyType(name, zone);
+    if(!value)
+        return;
+
+    std::unique_ptr<GVariant, decltype(&g_variant_unref)> v(value->toVariant(), &g_variant_unref);
+    std::unique_ptr<GVariant, decltype(&g_variant_unref)> v_untrusted(
+        g_variant_new_from_data( g_variant_get_type(v.release()), frame.data, frame.can_dlc, FALSE, nullptr, nullptr),
+        &g_variant_unref
+    );
+    std::unique_ptr<GVariant, decltype(&g_variant_unref)> v_normal(g_variant_get_normal_form(v_untrusted.release()), &g_variant_unref);
+    if(g_variant_is_normal_form(v_normal.get())) {
+        value->fromVariant(v_normal.get());
+        routingEngine->updateProperty(value, source);
+    }
+    else{
+        LOG_ERROR("Can't convert value from CAN to GVariant");
+    }
+}
+
+void CANSimPlugin::errorFrameReceived(const can_frame& frame)
+{
+    LOG_INFO("CANSimPlugin::errorFrameReceived()");
+    printFrame(frame);
+}
+
+void CANSimPlugin::remoteTransmissionRequest(const can_frame& frame)
+{
+       (void) frame;
+    LOG_INFO( "CANSimPlugin::remoteTransmissionRequest() not implemented "<< std::endl );
+}
+
+void CANSimPlugin::printFrame(const can_frame& frame) const
+{
+    LOG_INFO( "CANSimPlugin::printFrame can_id: " << std::hex << frame.can_id << std::dec << endl );
+    LOG_INFO( "CANSimPlugin::printFrame can_dlc: " << int(frame.can_dlc) << endl );
+
+    std::stringstream ss;
+    for(int i=0; i<frame.can_dlc; ++i){
+        ss << " " << std::hex << (int)(frame.data[i]);
+    }
+    ss << std::dec;
+
+    LOG_INFO( "CANSimPlugin::printFrame can data" << ss.str() << endl );
+}
+
+
diff --git a/plugins/cansimplugin/cansimplugin.h b/plugins/cansimplugin/cansimplugin.h
new file mode 100644 (file)
index 0000000..42b3427
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+Copyright (C) 2012 Intel Corporation
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _CANSIMPLUGINIMPL_H_
+#define _CANSIMPLUGINIMPL_H_
+
+#include <map>
+#include <memory>
+#include <tgmath.h>
+
+#include <canbus.h>
+#include <canobserver.h>
+
+#include "ambpluginimpl.h"
+
+/*!
+ *  \defgroup cansimplugin cansimplugin shared library
+ *  \brief CANSimPlugin listens on configured CAN interfaces(e.g. vcan0, vcan1, ...) for CAN frames from CANGenPlugin and translates them into AMB property changes.
+ *
+ *  CANSimPlugin is a source plug-in for Automotive message broker(AMB). \n
+ *  For the AMB library API please visit <a href="https://github.com/otcshare/automotive-message-broker">Automotive message broker web page</a>.\n
+ *
+ *  To load this plugin at AMB starup, insert following rows into AMB configuration file:
+ *  \code
+ *      {
+ *          "name" : "CANSimPlugin",
+ *          "path":"/usr/lib/automotive-message-broker/cansimplugin.so",
+ *          "interfaces" : ["vcan0", "vcan1"]
+ *      }
+ *  \endcode
+ *
+ * \note CANSimPlugin has to be the last source plug-in listed in AMB configuration file. Otherwise it can accidentally unregister or try to simulate some AMB properties supported from other sources.
+ *
+ *  @{
+ */
+
+/*!
+ * \brief CAN frames listener plug-in for the AMB CAN Simulator.
+ *
+ *  Listens on AMB notification for changes in supported properties using supportedChanged() callback. \n
+ *  On startup it registers property called 'MappingTable' and all properties needed for simulation in intelPoc12.Dashboard and intelPoc16.HVAC applications. \n
+ *  Immediately after it registers own properties it will get first notification supportedChanged().\n
+ *  On every notification from supportedChanged() CANSimPlugin goes through all supported properties in AMB, exclude those properties for which there are known sources in the AMB,\n
+ *  assigns to each single property and zone some unique CAN Id(builds 'mapping table') and this 'mapping table' stores as JSON string and updates it as a 'MappingTable' property in AMB.\n
+ *  When 'MappingTable' is built and ready it starts to listen on all required CAN interfaces in AMB configuration file.\n
+ *  On any received CAN frame it goes through the mappingTable, finds property name and zone based on received CAN Id, reads new value from CAN frame and updates it in the AMB.
+ *
+ * \see \ref libcanbus
+ *
+ * \class CANSimPlugin
+ */
+class CANSimPlugin : public AmbPluginImpl, public CANObserver {
+
+public:
+    /*!
+     * \param re AbstractRoutingEngine
+     * \param config Map of the configuration string values loaded on startup from AMB configuration file
+     * \param parent AmbPlugin instance
+     */
+       CANSimPlugin(AbstractRoutingEngine* re, const std::map<std::string, std::string>& config, AbstractSource& parent);
+    virtual ~CANSimPlugin(); // has to be virtual because of unit tests
+
+    // from AbstractSink
+public:
+
+    /*! uuid() is a unique identifier
+      * \return a guid-style unique identifier
+      */
+    const std::string uuid() const { return "3f43e231-11ec-4782-9b5a-3dbcc5221eeb"; }
+
+    /*! Called when the supported properties changes
+     * \param supportedProperties the new list of supported properties.
+     */
+    void supportedChanged(const PropertyList& supportedProperties);
+
+    /*!
+     * \brief supportedOperations
+     * \return returns the supported operations.
+     */
+    int supportedOperations() const;
+
+    // from CANObserver
+public:
+    /*!
+     * Called when error occurred on the bus.
+     * \fn errorOccured
+     * \param error @link CANObserver#CANError Bus error code @endlink
+     */
+    virtual void errorOccured(CANObserver::CANError error);/* socket error */
+    /*!
+     * Called when standard frame was is received from the bus.
+     * \fn standardFrameReceived
+     * \param frame Received frame
+     */
+    virtual void standardFrameReceived(const can_frame& frame);/* SFF was present */
+    /*!
+     * Called when extended frame was is received from the bus.
+     * \fn extendedFrameReceived
+     * \param frame Received frame
+     */
+    virtual void extendedFrameReceived(const can_frame& frame);/* EFF was present */
+    /*!
+     * Called when error frame was received from the bus.
+     * \fn errorFrameReceived
+     * \param frame Error frame
+     */
+    virtual void errorFrameReceived(const can_frame& frame);/* error frame */
+    /*!
+     * Called when remote transmission frame was received from the bus.
+     * \fn remoteTransmissionRequest
+     * \param frame RTR frame
+     */
+    virtual void remoteTransmissionRequest(const can_frame& frame);/* remote transmission request (SFF/EFF is still present)*/
+
+    /*!
+     * Second phase of the plugin initialization.
+     * \fn init
+     */
+    virtual void init();
+
+private:
+
+    /*!
+     * \brief Prints received CAN frame
+     * \param frame Received CAN frame.
+     * \internal
+     * \private
+     */
+    void printFrame(const can_frame& frame) const;
+
+    /*!
+     * \brief Creates string in JSON format which contains all supported AMB properties that can be used for simulation.
+     * \param supported All supported AMB properties.
+     * \see Automotive message broker's \b AsyncRangePropertyReply
+     * \private
+     */
+   void createMappingTable(const PropertyList& supported);
+
+//
+// data:
+//
+
+   /*!
+    * Map of the can_id and corresponding AMB properties in tuple<property_source_id, property_name, zone>
+    * \property mappingTable
+    * \private
+    */
+    std::map< canid_t, std::tuple<std::string, VehicleProperty::Property, Zone::Type> > mappingTable;
+
+    /**
+    * Map of the CAN interfaces used in simulator. Contains libcanbus#CANBus class instances.
+    * \property interfaces
+    * \private
+    */
+    std::map<std::string, std::shared_ptr<CANBus> > interfaces;
+};
+#endif // _CANSIMPLUGINIMPL_H_
+
+/** @} */
diff --git a/plugins/cansimplugin/test/CMakeLists.txt b/plugins/cansimplugin/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ecd348b
--- /dev/null
@@ -0,0 +1,20 @@
+
+set(include_dirs ${CMAKE_CURRENT_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/lib ../ ../../../tests)
+
+find_library(libtool_LIBRARY ltdl DOC "Libtool libraries")
+find_path(libtool_INCLUDE_DIR ltdl.h DOC "Libtool headers")
+
+pkg_check_modules(glib REQUIRED glib-2.0)
+pkg_check_modules(json REQUIRED json)
+
+set(ut_cansimplugin_headers cansimplugin.h cansimpluginimpl.h mockabstractroutingengine.h)
+set(ut_cansimplugin_sources main.cpp ut_cansimplugin.cpp ../cansimplugin.cpp)
+
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
+add_definitions(-fPIC -g)
+
+add_executable(ut_cansimplugin ${ut_cansimplugin_sources})
+
+include_directories(${include_dirs} ${libtool_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/plugins/common ${glib_INCLUDE_DIRS} ${gio_INCLUDE_DIRS})
+target_link_libraries(ut_cansimplugin ${link_libraries} amb-plugins-common -L${CMAKE_CURRENT_BINARY_DIR}/plugins/common ${libtool_LIBRARY} ${glib_LIBRARIES} ${json_LIBRARIES} amb -lrt -lgobject-2.0 -lpthread)
+
diff --git a/plugins/cansimplugin/test/main.cpp b/plugins/cansimplugin/test/main.cpp
new file mode 100644 (file)
index 0000000..40fa967
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+Copyright (C) 2012 Intel Corporation
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include <glib-object.h>
+#include <CppUTest/CommandLineTestRunner.h>
+#include "logger.h"
+
+struct ThreadData{
+    int argc;
+    char** argv;
+    int testResult;
+    GMainLoop* mainLoop;
+};
+
+static void* tests_thread(void *data)
+{
+    ThreadData* td = static_cast<ThreadData*>(data);
+    if(td){
+        td->testResult = CommandLineTestRunner::RunAllTests(td->argc, td->argv);
+        g_main_loop_quit(td->mainLoop);
+    }
+    return td;
+}
+
+int main(int ac, char **av)
+{
+    ThreadData td;
+    td.argc = ac;
+    td.argv = av;
+    td.testResult = 0;
+
+    DEBUG_CONF("ut_cansimplugin",
+#ifdef _DEBUG
+          CUtil::Logger::file_on|CUtil::Logger::screen_on,
+#else
+          CUtil::Logger::file_on|CUtil::Logger::screen_off,
+#endif
+          CUtil::Logger::EInfo, CUtil::Logger::EWarning);
+
+    td.mainLoop = g_main_loop_new(NULL, FALSE);
+
+    // Run the mainloop and the tests thread
+    GThread* testThread = g_thread_new("tests thread", &tests_thread, &td);
+
+    g_main_loop_run(td.mainLoop);
+    g_thread_join(testThread);
+
+    g_main_loop_unref(td.mainLoop);
+
+
+    return td.testResult;
+}
diff --git a/plugins/cansimplugin/test/ut_cansimplugin.cpp b/plugins/cansimplugin/test/ut_cansimplugin.cpp
new file mode 100644 (file)
index 0000000..6da5182
--- /dev/null
@@ -0,0 +1,475 @@
+/*
+Copyright (C) 2012 Intel Corporation
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "logger.h"
+
+#include <ltdl.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+#include <listplusplus.h>
+#define private protected
+#include "cansimplugin.h"
+#undef private
+#include "mockabstractroutingengine.h"
+#include "mockcanbus.h"
+
+const char* mappingTable = "MappingTable";
+
+typedef void* create_t(AbstractRoutingEngine*, std::map<std::string, std::string>);
+
+class Loader{
+public:
+    Loader()
+    {
+        if(lt_dlinit())
+        {
+            cerr<<"error initializing libtool: "<<__FILE__<<" - "<<__FUNCTION__<<":"<<__LINE__<<" "<<lt_dlerror()<<endl;
+            throw std::runtime_error("Error initializing libtool. aborting");
+        }
+    }
+
+    ~Loader()
+    {
+        auto handle = openHandles.begin();
+        while(handle != openHandles.end())
+            lt_dlclose(*handle++);
+        lt_dlexit();
+    }
+
+    template<class T>
+    T loadPlugin(std::string pluginName, AbstractRoutingEngine* routingEngine, std::map<std::string, std::string> config)
+    {
+        DebugOut()<<"Loading plugin: "<<pluginName<<endl;
+
+        lt_dlhandle handle = lt_dlopenext(pluginName.c_str());
+
+        if(!handle)
+        {
+            cerr<<"error opening plugin: "<<pluginName<<" in "<<__FILE__<<" - "<<__FUNCTION__<<":"<<__LINE__<<" "<<lt_dlerror()<<endl;
+            return nullptr;
+        }
+
+        openHandles.push_back(handle);
+        create_t* f_create = (create_t *)lt_dlsym(handle, "create");
+
+        //mErrorString = lt_dlerror();
+
+        if(f_create)
+        {
+            void* obj = f_create(routingEngine, config);
+            return static_cast<T>( obj );
+        }
+
+        return nullptr;
+    }
+private:
+    std::vector<lt_dlhandle> openHandles;
+};
+
+class TestCANSimPluginImpl : public CANSimPlugin
+{
+public:
+    TestCANSimPluginImpl(AbstractRoutingEngine* re, map<string, string> config, AbstractSource& parent) :
+        CANSimPlugin(re, config, parent)
+    {
+        mock().actualCall("CANSimPlugin::CANSimPlugin");
+
+        for(auto it=interfaces.begin(); it != interfaces.end(); ++it){
+            interfaces[it->first] = std::shared_ptr<CANBus>(new MockCANBus(*this));
+        }
+    }
+
+    virtual ~TestCANSimPluginImpl()
+    {
+        mock().actualCall("CANSimPlugin::~CANSimPlugin");
+    }
+
+    std::string TestCreateMappingTable(const PropertyList& supported) {
+        supportedChanged(supported);
+        AbstractPropertyType* value = findPropertyType("MappingTable", Zone::None);
+        if(!value)
+            return "";
+        return  value->toString();
+    }
+
+    inline void TestAddPropertyToMappingTable(const int id, const char* source, const char* propertyName, const Zone::Type& z)
+    {
+        mappingTable[id] = make_tuple(source, propertyName, z);
+        ZonePropertyType& zonePropType = properties[propertyName];
+        std::shared_ptr<AbstractPropertyType> value(VehicleProperty::getPropertyTypeForPropertyNameValue(propertyName));
+        if(value){
+            value->zone = z;
+            zonePropType.insert(make_pair(z, value));
+        }
+        else{
+            properties.erase(propertyName);
+        }
+    }
+};
+
+class TestCANSimPlugin : public AmbPlugin<TestCANSimPluginImpl>
+{
+public:
+    TestCANSimPlugin(AbstractRoutingEngine* re, map<string, string> config) :
+        AmbPlugin<TestCANSimPluginImpl>(re, config)
+    {
+        mock().actualCall("AmbPlugin::AmbPlugin");
+    }
+
+    virtual ~TestCANSimPlugin()
+    {
+        mock().actualCall("AmbPlugin::~AmbPlugin");
+    }
+
+    inline TestCANSimPluginImpl* getImplementation() { return dynamic_cast<TestCANSimPluginImpl*>(d.get()); }
+};
+
+TEST_GROUP(CANSimPlugin)
+{
+    MockAbstractRoutingEngine *routingEngine;
+    std::map<std::string, std::string> config;
+    TestCANSimPlugin *plugin;
+
+    void setup()
+    {
+        DebugOut::setDebugThreshhold(0);
+        VehicleProperty::factory();
+        routingEngine = new MockAbstractRoutingEngine();
+        plugin = nullptr;
+        mock().expectOneCall("AmbPlugin::AmbPlugin");
+        mock().expectOneCall("CANSimPlugin::CANSimPlugin");
+        mock().expectOneCall("CANBus::stop"); // because of the interfaces[it->first] = std::shared_ptr<CANBus>(new MockCANBus(*this)); in TestCANSimPluginImpl
+        mock().expectOneCall("AbstractRoutingEngine::setSupported");
+        mock().expectNCalls(27, "AbstractRoutingEngine::supported"); // 27 well-known properties in AMB API
+        config["interfaces"] = "[\"vcan999\"]";
+        plugin = new TestCANSimPlugin(routingEngine, config);
+    }
+    void teardown()
+    {
+        //mock().expectOneCall("CANBus::stop");
+        mock().expectOneCall("AmbPlugin::~AmbPlugin");
+        mock().expectOneCall("CANSimPlugin::~CANSimPlugin");
+        if(plugin != nullptr) {
+            delete plugin;
+            plugin = nullptr;
+        }
+        VehicleProperty::shutdown();
+
+        delete routingEngine;
+        routingEngine = nullptr;
+
+        mock().checkExpectations();
+        mock().clear();
+    }
+};
+
+TEST(CANSimPlugin, create)
+{
+
+    //mock().expectOneCall("CANSimPlugin::CANSimPlugin");
+    Loader ldr;
+    config.clear();// create plugin with emtpy interfaces list at least once.
+    mock().expectOneCall("AbstractRoutingEngine::setSupported");
+    mock().expectNCalls(32, "AbstractRoutingEngine::supported"); // we are calling 32 times addPropertySupport in AMB API
+    AbstractSource* myplugin = ldr.loadPlugin<AbstractSource*>("./build/cansimplugin.so", routingEngine, config);
+    CHECK( myplugin != nullptr);
+    delete myplugin;
+}
+
+TEST(CANSimPlugin, getPropertyAsync)
+{
+    bool replySuccess(false);
+    int replyError(-1);
+    int replyZone(-1);
+    std::string replySignalName("");
+
+    // NULL pointer test
+    plugin->getPropertyAsync(nullptr);
+
+    // Invalid request test
+    AsyncPropertyRequest requestInvalid;
+    requestInvalid.timeout = 0;
+    AsyncPropertyReply* reply = new AsyncPropertyReply(requestInvalid);
+    plugin->getPropertyAsync(reply);
+    delete reply; reply = nullptr;
+
+    requestInvalid.property = "NotExists";
+    requestInvalid.completed = [&](AsyncPropertyReply* reply)
+        {
+            replySuccess = reply->success;
+            replyError = reply->error;
+            if(reply->value) {
+                LOG_TRACE("getPropertyAsync completed, value is: " << reply->value->toString());
+                replyZone = reply->value->zone;
+                replySignalName = reply->value->name;
+            }
+            delete reply;
+        };
+
+    reply = new AsyncPropertyReply(requestInvalid);
+    plugin->getPropertyAsync(reply);
+
+    CHECK(replySuccess == false);
+    CHECK(replyError == AsyncPropertyReply::InvalidOperation);
+
+    plugin->getImplementation()->TestAddPropertyToMappingTable(0xa, plugin->uuid().c_str(), VehicleProperty::SeatHeater.c_str(), Zone::FrontLeft);
+    AsyncPropertyRequest request;
+    request.timeout = 0;
+    request.property = VehicleProperty::SeatHeater;
+    request.zoneFilter = Zone::FrontLeft;
+    request.completed = requestInvalid.completed;
+
+    reply = new AsyncPropertyReply(request);
+    plugin->getPropertyAsync(reply);
+
+    CHECK(replySuccess == true);
+    CHECK_EQUAL_C_STRING(request.property.c_str(), replySignalName.c_str());
+    CHECK_EQUAL_C_INT(request.zoneFilter, replyZone);
+
+}
+
+TEST(CANSimPlugin, getRangePropertyAsync)
+{
+    bool replySuccess(false);
+    int replyError(-1);
+
+    plugin->getRangePropertyAsync(nullptr);
+
+    AsyncRangePropertyRequest requestInvalid;
+    AsyncRangePropertyReply* reply = new AsyncRangePropertyReply(requestInvalid);
+    plugin->getRangePropertyAsync(reply);
+    delete reply; reply = nullptr;
+
+    AsyncRangePropertyRequest request;
+    request.properties.push_back(VehicleProperty::SeatHeater);
+    request.completed = [&](AsyncRangePropertyReply* reply)
+    {
+        replySuccess = reply->success;
+        replyError = reply->error;
+        delete reply;
+    };
+
+    reply = new AsyncRangePropertyReply(request);
+    plugin->getRangePropertyAsync(reply);
+
+    CHECK(replySuccess == false);
+    CHECK(replyError == AsyncPropertyReply::InvalidOperation);
+}
+
+TEST(CANSimPlugin, setProperty)
+{
+    bool replySuccess(false);
+    int replyError(-1);
+    std::string replySignalName("");
+
+    // Invalid request test
+    AsyncSetPropertyRequest requestInvalid;
+    requestInvalid.timeout = 0;
+    AsyncPropertyReply* reply = plugin->setProperty(requestInvalid);
+    delete reply; reply = nullptr;
+
+    requestInvalid.property = "NotExists";
+    requestInvalid.completed = [&](AsyncPropertyReply* reply)
+        {
+            replySuccess = reply->success;
+            replyError = reply->error;
+            delete reply;
+        };
+
+    reply = plugin->setProperty(requestInvalid);
+
+    CHECK(replySuccess == false);
+    CHECK(replyError == AsyncPropertyReply::InvalidOperation);
+
+    plugin->getImplementation()->TestAddPropertyToMappingTable(0xa, plugin->uuid().c_str(), VehicleProperty::SeatHeater.c_str(), Zone::FrontLeft);
+    mock().expectOneCall("AbstractRoutingEngine::updateProperty");
+    AsyncSetPropertyRequest request;
+    request.timeout = 0;
+    request.property = VehicleProperty::SeatHeater;
+    request.zoneFilter = Zone::FrontLeft;
+    request.value = VehicleProperty::getPropertyTypeForPropertyNameValue(request.property, "1");
+    request.completed = requestInvalid.completed;
+
+    reply = plugin->setProperty(request);
+    delete request.value;
+    request.value = nullptr;
+
+    CHECK(replySuccess == true);
+    CHECK(replyError == AsyncPropertyReply::NoError);
+}
+
+TEST(CANSimPlugin, subscribeUnsubscribeToPropertyChanges)
+{
+    plugin->subscribeToPropertyChanges("");
+
+    // AbstractRoutingEngine::updateProperty is called so many times as many zones signal has
+    mock().expectNCalls(1, "AbstractRoutingEngine::updateProperty");
+    plugin->subscribeToPropertyChanges(VehicleProperty::VehicleSpeed);// only Zone::None
+
+    // empty implemnetation, does nothing
+    plugin->unsubscribeToPropertyChanges("");
+    plugin->unsubscribeToPropertyChanges(VehicleProperty::VehicleSpeed);
+
+}
+
+TEST(CANSimPlugin, supported)
+{
+    PropertyList supported = plugin->supported();
+    CHECK_EQUAL_C_INT(29, supported.size());
+}
+
+TEST(CANSimPlugin, supportedOperations)
+{
+    int supported = plugin->supportedOperations();
+    CHECK_EQUAL_C_INT(AbstractSource::Get | AbstractSource::Set, supported);
+}
+
+TEST(CANSimPlugin, getPropertyInfo)
+{
+    PropertyInfo pi = plugin->getPropertyInfo(VehicleProperty::EngineSpeed);
+    CHECK(!pi.isValid());
+    std::list<Zone::Type> zones = pi.zones();
+    CHECK(zones.empty());
+    pi = plugin->getPropertyInfo(mappingTable);
+    CHECK(pi.isValid());
+    zones = pi.zones();
+    CHECK(ListPlusPlus<Zone::Type>(&zones).contains(Zone::None));
+}
+
+TEST(CANSimPlugin, uuid)
+{
+    std::string uuid = plugin->uuid();
+    //CHECK_EQUAL_C_STRING("3f43e231-11ec-4782-9b5a-3dbcc5221eeb", uuid.c_str());
+    CHECK(uuid.length() > 0);
+}
+
+TEST(CANSimPlugin, BasicPropertyType_fromString_and_toString)
+{
+
+    //VehicleProperty::DefrostType *defrost = new VehicleProperty::DefrostType();
+    //defrost->append(Window::SideMirrorLeft, true);
+    //defrost->append(Window::SideMirrorRight, false);
+    //defrost->fromString(defrost->toString());
+    //CHECK_EQUAL_C_STRING("{'6':'1', '7':'0'}", defrost->toString().c_str());
+    //delete defrost;
+
+    BasicPropertyType<bool> boolean(true);
+    boolean.fromString(boolean.toString());
+    CHECK_EQUAL_C_STRING("1", boolean.toString().c_str());
+
+}
+
+TEST(CANSimPlugin, createMappingTable)
+{
+    const char* expected = "{ "
+        "'sources': [ { 'source': { 'guid': '3f43e231-11ec-4782-9b5a-3dbcc5221eeb' }, "
+        "'signals': [ "
+            "{ 'can_id': 10, 'name': 'SeatHeater', 'zone': 0 }, "
+            "{ 'can_id': 11, 'name': 'SeatHeater', 'zone': 2 }, "
+            "{ 'can_id': 12, 'name': 'SeatHeater', 'zone': 4 }, "
+            "{ 'can_id': 13, 'name': 'SeatHeater', 'zone': 8 }, "
+            "{ 'can_id': 14, 'name': 'SeatHeater', 'zone': 16 }, "
+            "{ 'can_id': 15, 'name': 'SeatHeater', 'zone': 32 }, "
+            "{ 'can_id': 16, 'name': 'SeatHeater', 'zone': 64 }, "
+            "{ 'can_id': 17, 'name': 'SeatHeater', 'zone': 128 }, "
+            "{ 'can_id': 18, 'name': 'SeatHeater', 'zone': 256 }, "
+            "{ 'can_id': 19, 'name': 'SeatHeater', 'zone': 512 }, "
+            "{ 'can_id': 20, 'name': 'SeatHeater', 'zone': 5 }, "
+            "{ 'can_id': 21, 'name': 'SeatHeater', 'zone': 9 }, "
+            "{ 'can_id': 22, 'name': 'SeatHeater', 'zone': 6 }, "
+            "{ 'can_id': 23, 'name': 'SeatHeater', 'zone': 10 }, "
+            "{ 'can_id': 24, 'name': 'SeatHeater', 'zone': 20 }, "
+            "{ 'can_id': 25, 'name': 'SeatHeater', 'zone': 24 }"
+        " ] } ] }";
+
+    std::list<std::string> supportedProps(VehicleProperty::capabilities());
+    supportedProps.remove(VehicleProperty::SeatHeater);
+    mock().setData("properties", &supportedProps);
+    std::list<std::string> sources({"3f43e231-11ec-4782-9b5a-3dbcc5221eeb", "another-source"});
+    mock().setData("sources", &sources);
+    PropertyInfo pi(1, {Zone::FrontLeft, Zone::FrontRight});
+    mock().setData("PropertyInfo", &pi);
+    mock().expectNCalls(supportedProps.size(), "AbstractRoutingEngine::sourcesForProperty");
+    //mock().expectNCalls(supportedProps.size()*(sources.size()-1), "AbstractRoutingEngine::getPropertyInfo");
+    mock().expectOneCall("AbstractRoutingEngine::supported");
+    mock().expectOneCall("AbstractRoutingEngine::updateProperty");
+    //mock().expectOneCall("AbstractRoutingEngine::setSupported");
+
+    const std::string& mappingTable = plugin->getImplementation()->TestCreateMappingTable(routingEngine->supported());
+    LOG_ERROR("json: " << mappingTable);
+
+    CHECK(mappingTable.length() > 0);
+    json_object* mp = json_tokener_parse(mappingTable.c_str());
+    json_object_put(mp);
+    CHECK(mp != nullptr);
+    CHECK_EQUAL_C_STRING(expected, mappingTable.c_str());
+
+}
+
+TEST(CANSimPlugin, errorOccured)
+{
+    plugin->getImplementation()->errorOccured(static_cast<CANObserver::CANError>(-1));
+
+    CANObserver::CANError error(CANObserver::GENERAL_ERROR);
+    plugin->getImplementation()->errorOccured(error);
+}
+
+TEST(CANSimPlugin, standardFrameReceived)
+{
+    can_frame frame;
+    memset(&frame, 0, sizeof(frame));
+    frame.can_id = 0x00a;
+    plugin->getImplementation()->standardFrameReceived(frame);
+}
+
+TEST(CANSimPlugin, extendedFrameReceived)
+{
+    can_frame frame;
+    memset(&frame, 0, sizeof(frame));
+    frame.can_id = 0xFFFFFFFF;// not existing
+    mock().expectNCalls(0,"AbstractRoutingEngine::updateProperty");// no prop. with this can_id
+    plugin->getImplementation()->extendedFrameReceived(frame);
+
+    plugin->getImplementation()->TestAddPropertyToMappingTable(0xb, plugin->uuid().c_str(), "NotExisttingProperty", Zone::None);
+    frame.can_id = 0xb;
+    frame.can_dlc = 8;
+    mock().expectNCalls(0,"AbstractRoutingEngine::updateProperty");// no prop. with this can_id
+    plugin->getImplementation()->extendedFrameReceived(frame);
+
+    // existing property VehicleSpeed
+    plugin->getImplementation()->TestAddPropertyToMappingTable(0xa, plugin->uuid().c_str(), VehicleProperty::VehicleSpeed.c_str(), Zone::None);
+    frame.can_id = 0xa;
+    mock().expectOneCall("AbstractRoutingEngine::updateProperty");
+    plugin->getImplementation()->extendedFrameReceived(frame);
+
+}
+
+TEST(CANSimPlugin, errorFrameReceived)
+{
+    can_frame frame;
+    memset(&frame, 0, sizeof(frame));
+    plugin->getImplementation()->errorFrameReceived(frame);
+}
+
+TEST(CANSimPlugin, remoteTransmissionRequest)
+{
+    can_frame frame;
+    memset(&frame, 0, sizeof(frame));
+    plugin->getImplementation()->remoteTransmissionRequest(frame);
+}