From 6025152c3d3bd7bac0b078862fed6f17a2607699 Mon Sep 17 00:00:00 2001 From: Quentin Quadrat Date: Wed, 3 Jul 2024 23:35:18 +0200 Subject: [PATCH] WIP Reworking GRAFCET code generation --- Ex1.json | 32 +++ Ex2.json | 33 +++ Ex3.json | 33 +++ src/Editor/DearImGui/Editor.cpp | 273 ++++++++++++++++++------ src/Editor/DearImGui/Editor.hpp | 33 +-- src/Editor/DearImGui/KeyBindings.hpp | 2 +- src/Net/Exports/ExportGrafcetCpp.cpp | 298 ++++++++++++++------------- src/Net/Exports/ExportJSON.cpp | 23 ++- src/Net/Exports/Exports.cpp | 4 +- src/Net/Imports/ImportJSON.cpp | 1 + src/Net/Simulation.cpp | 63 ++++-- src/Net/Simulation.hpp | 18 +- 12 files changed, 556 insertions(+), 257 deletions(-) create mode 100644 Ex1.json create mode 100644 Ex2.json create mode 100644 Ex3.json diff --git a/Ex1.json b/Ex1.json new file mode 100644 index 0000000..1ae2a23 --- /dev/null +++ b/Ex1.json @@ -0,0 +1,32 @@ +{ + "revision": 3, + "type": "GRAFCET", + "nets": [ + { + "name": "Timed Petri net", + "places": [ + { "id": 0, "caption": "Step 1", "tokens": 1, "x": 205, "y": 79 }, + { "id": 1, "caption": "Step 2", "tokens": 0, "x": 206, "y": 249 }, + { "id": 2, "caption": "Step 3", "tokens": 0, "x": 206, "y": 440 } + ], + "transitions": [ + { "id": 0, "caption": "m", "x": 205, "y": 166, "angle": 0 }, + { "id": 1, "caption": "a", "x": 206, "y": 348, "angle": 0 }, + { "id": 2, "caption": "b", "x": 71, "y": 237, "angle": 0 } + ], + "arcs": [ + { "from": "P0", "to": "T0" }, + { "from": "T0", "to": "P1", "duration": 1 }, + { "from": "P1", "to": "T1" }, + { "from": "T1", "to": "P2", "duration": 4 }, + { "from": "P2", "to": "T2" }, + { "from": "T2", "to": "P0", "duration": 2 } + ], + "sensors": [ + { "name": "a", "value": 0 }, + { "name": "b", "value": 0 }, + { "name": "m", "value": 0 } + ] + } + ] +} diff --git a/Ex2.json b/Ex2.json new file mode 100644 index 0000000..90276be --- /dev/null +++ b/Ex2.json @@ -0,0 +1,33 @@ +{ + "revision": 3, + "type": "GRAFCET", + "nets": [ + { + "name": "Timed Petri net", + "places": [ + { "id": 0, "caption": "P0", "tokens": 1, "x": 235, "y": 86 }, + { "id": 1, "caption": "P1", "tokens": 0, "x": 238, "y": 268 }, + { "id": 2, "caption": "P2", "tokens": 0, "x": 106, "y": 449 }, + { "id": 3, "caption": "P3", "tokens": 0, "x": 368, "y": 458 } + ], + "transitions": [ + { "id": 0, "caption": "m", "x": 233, "y": 179, "angle": 0 }, + { "id": 1, "caption": "a", "x": 104, "y": 358, "angle": 0 }, + { "id": 2, "caption": "b", "x": 364, "y": 365, "angle": 0 } + ], + "arcs": [ + { "from": "P0", "to": "T0" }, + { "from": "T0", "to": "P1", "duration": 2 }, + { "from": "P1", "to": "T1" }, + { "from": "P1", "to": "T2" }, + { "from": "T1", "to": "P2", "duration": 2 }, + { "from": "T2", "to": "P3", "duration": 3 } + ], + "sensors": [ + { "name": "a", "value": 0 }, + { "name": "b", "value": 0 }, + { "name": "m", "value": 0 } + ] + } + ] +} diff --git a/Ex3.json b/Ex3.json new file mode 100644 index 0000000..1b03367 --- /dev/null +++ b/Ex3.json @@ -0,0 +1,33 @@ +{ + "revision": 3, + "type": "GRAFCET", + "nets": [ + { + "name": "Timed Petri net", + "places": [ + { "id": 0, "caption": "P0", "tokens": 0, "x": 130, "y": 88 }, + { "id": 1, "caption": "P1", "tokens": 0, "x": 295, "y": 84 }, + { "id": 2, "caption": "P2", "tokens": 0, "x": 220, "y": 252 }, + { "id": 3, "caption": "P3", "tokens": 0, "x": 223, "y": 410 } + ], + "transitions": [ + { "id": 0, "caption": "T0", "x": 130, "y": 171, "angle": 0 }, + { "id": 1, "caption": "T1", "x": 298, "y": 170, "angle": 0 }, + { "id": 2, "caption": "T2", "x": 221, "y": 333, "angle": 0 } + ], + "arcs": [ + { "from": "P0", "to": "T0" }, + { "from": "P1", "to": "T1" }, + { "from": "T0", "to": "P2", "duration": 4 }, + { "from": "T1", "to": "P2", "duration": 1 }, + { "from": "P2", "to": "T2" }, + { "from": "T2", "to": "P3", "duration": 5 } + ], + "sensors": [ + { "name": "T0", "value": 0 }, + { "name": "T1", "value": 0 }, + { "name": "T2", "value": 0 } + ] + } + ] +} diff --git a/src/Editor/DearImGui/Editor.cpp b/src/Editor/DearImGui/Editor.cpp index b5f57a1..4bc5731 100644 --- a/src/Editor/DearImGui/Editor.cpp +++ b/src/Editor/DearImGui/Editor.cpp @@ -46,9 +46,6 @@ static std::string g_ini_filename = "imgui.ini"; Editor::Editor(size_t const width, size_t const height, std::string const& title) : Application(width, height, title), -#ifdef WITH_MQTT - MQTT(MQTT_BROKER_ADDR, MQTT_BROKER_PORT), -#endif m_path(GET_DATA_PATH), m_simulation(m_net, m_messages), m_view(*this) @@ -59,7 +56,7 @@ Editor::Editor(size_t const width, size_t const height, # define FONT_SIZE 13.0f #endif - std::cout << "Path: " << m_path.toString() << std::endl; + m_messages.setInfo("Path: " + m_path.toString()); m_states.title = title; @@ -67,7 +64,7 @@ Editor::Editor(size_t const width, size_t const height, ImGuiIO &io = ImGui::GetIO(); g_ini_filename = m_path.expand("imgui.ini").c_str(); io.IniFilename = g_ini_filename.c_str(); - std::cout << "imgui.ini path: " << io.IniFilename << std::endl; + // std::cout << "imgui.ini path: " << io.IniFilename << std::endl; // Setup fonts io.Fonts->AddFontFromFileTTF(m_path.expand("font.ttf").c_str(), FONT_SIZE); @@ -75,14 +72,16 @@ Editor::Editor(size_t const width, size_t const height, // Theme ImGui::StyleColorsDark(); + +#ifdef WITH_MQTT + initMQTT(); +#endif } #ifdef WITH_MQTT //------------------------------------------------------------------------------ -void Editor::onConnected(int /*rc*/) +bool Editor::initMQTT() { - std::cout << "Connected to MQTT broker" << std::endl; - // Load a Petri net using the formalism used for TPNE json files. For example // mosquitto_pub -h localhost -t "tpne/load" -m '{ "revision": 3, "type": // "Petri net", "nets": [ { "name": "hello world", @@ -91,8 +90,8 @@ void Editor::onConnected(int /*rc*/) // "transitions": [ { "id": 0, "caption": "T0", "x": 298, "y": 207, "angle": 0 } ], // "arcs": [ { "from": "P0", "to": "T0" }, { "from": "T0", "to": "P1", "duration": 3 } // ] } ] }' - subscribe("tpne/load", [&](MQTT::Message const& msg){ - std::cout << "load\n"; + static auto onLoadCommandReceived = [&](const MQTT::Message& msg) + { if (m_simulation.running) { m_messages.setError("MQTT: cannot load new Petri net while the simulation is still in progress"); @@ -117,30 +116,34 @@ void Editor::onConnected(int /*rc*/) { m_messages.setError(error); } - }, MQTT::QoS::QoS0); + }; // Start the simulation for Petri net and GRAFCET. // mosquitto_pub -h localhost -t "tpne/start" -m '' - subscribe("tpne/start", [&](MQTT::Message const& /*msg*/){ - if ((m_net.type() == TypeOfNet::TimedEventGraph) || (m_net.type() == TypeOfNet::TimedPetriNet)) + static auto onStartSimulationCommandReceived = [&](MQTT::Message const& /*msg*/) + { + if ((m_net.type() == TypeOfNet::TimedEventGraph) || + (m_net.type() == TypeOfNet::TimedPetriNet)) { - m_messages.setError("MQTT: Please convert first to non timed net before starting simulation"); + m_messages.setError( + "MQTT: Please convert first to non timed net before starting simulation"); return ; } - m_simulation.running = true; - framerate(30); - }, MQTT::QoS::QoS0); + toogleStartSimulation(); + }; // Stop the simulation. // mosquitto_pub -h localhost -t "tpne/stop" -m '' - subscribe("tpne/stop", [&](MQTT::Message const& /*msg*/){ + static auto onStopSimulationCommandReceived = [&](MQTT::Message const& /*msg*/) + { m_simulation.running = false; framerate(60); - }, MQTT::QoS::QoS0); + }; // Fire transitions. // mosquitto_pub -h localhost -t "tpne/fire" -m '10100' - subscribe("tpne/fire", [&](MQTT::Message const& msg){ + static auto onFireTransitionCommandReceived = [&](MQTT::Message const& msg) + { if (!m_simulation.running) { m_messages.setError("MQTT: The simulation is not running"); @@ -160,9 +163,33 @@ void Editor::onConnected(int /*rc*/) { m_messages.setError("MQTT: fire command length does not match number of transitions"); } - }, MQTT::QoS::QoS0); + }; + + // Subscription to MQTT messages + auto onConnected = [&](int rc) + { + static MQTT::Topic TOPIC_LOAD{"tpne/load"}; + static MQTT::Topic TOPIC_START{"tpne/start"}; + static MQTT::Topic TOPIC_STOP{"tpne/stop"}; + static MQTT::Topic TOPIC_FIRE{"tpne/fire"}; + + m_messages.setInfo("Connected to MQTT broker with return code " + std::to_string(rc)); + m_mqtt.subscribe(TOPIC_LOAD, MQTT::QoS::QoS0, onLoadCommandReceived); + m_mqtt.subscribe(TOPIC_START, MQTT::QoS::QoS0, onStartSimulationCommandReceived); + m_mqtt.subscribe(TOPIC_STOP, MQTT::QoS::QoS0, onStopSimulationCommandReceived); + m_mqtt.subscribe(TOPIC_FIRE, MQTT::QoS::QoS0, onFireTransitionCommandReceived); + }; + + // Connection to the MQTT broker + if (!m_mqtt.connect({"localhost", 1883, std::chrono::seconds(60)}, onConnected)) + { + m_messages.setError(m_mqtt.error().message()); + return false; + } + + return true; } -#endif +#endif // WITH_MQTT //------------------------------------------------------------------------------ void Editor::showStyleSelector() @@ -914,9 +941,13 @@ void Editor::messagebox() //------------------------------------------------------------------------------ void Editor::inspector() { + // InputText modify callback: modified => net shall be saved ? + static bool modified = false; + // InputText callback: GRAFCET transitivities modified ? + static bool compiled = false; // Do not allow editing when running simulation const auto readonly = m_simulation.running ? - ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None; + ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None; // Place captions and tokens { @@ -934,7 +965,13 @@ void Editor::inspector() { ImGui::PushID(place.key.c_str()); ImGui::AlignTextToFramePadding(); - ImGui::InputText(place.key.c_str(), &place.caption, readonly); + ImGui::InputText(place.key.c_str(), &place.caption, + readonly | ImGuiInputTextFlags_CallbackEdit, + [](ImGuiInputTextCallbackData*) + { + modified = true; + return 0; + }); // Increment/decrement tokens ImGui::SameLine(); @@ -942,11 +979,13 @@ void Editor::inspector() if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { place.decrement(); + modified = true; } ImGui::SameLine(); if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { place.increment(); + modified = true; } ImGui::PopButtonRepeat(); @@ -971,34 +1010,55 @@ void Editor::inspector() ImGui::Separator(); ImGui::Text("%s", (m_net.type() == TypeOfNet::GRAFCET) ? "Transitivities:" : "Captions:"); - // Show contents of transitivities + // Compile transitivities for GRAFCET the initial time and each time one of transitions + // have been edited (Currently: any InputText invalid the whole sensors. Slow but easier + // to implement). + compiled |= m_simulation.compiled; + if ((m_net.type() == TypeOfNet::GRAFCET) && (!compiled)) + { + compiled = m_simulation.generateSensors(); + } for (auto& t: m_net.transitions()) { - ImGui::InputText(t.key.c_str(), &t.caption, readonly); - std::vector const& receptivities = m_simulation.receptivities(); - if ((m_net.type() == TypeOfNet::GRAFCET) && (!receptivities.empty()) && (!m_simulation.running)) + // Show contents of transition + ImGui::InputText(t.key.c_str(), &t.caption, + readonly | ImGuiInputTextFlags_CallbackEdit, + [](ImGuiInputTextCallbackData*) + { + modified = true; + compiled = false; + return 0; + }); + + // For GRAFCET and show syntax error on the transitivity + if ((m_net.type() == TypeOfNet::GRAFCET) && (!m_simulation.running)) { - Receptivity const& recp = receptivities[t.id]; - // FIXME parse and clear sensors if and only if we modified entrytext - if (!recp.isValid()) // && recp.compiled() + Simulation::Receptivities const& receptivities = m_simulation.receptivities(); + auto it = receptivities.find(t.id); + if (it == receptivities.end()) + { + m_messages.setError("Could not find receptivity. This should not happened. Please report this error"); + } + else if (!it->second.isValid()) { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "%s", recp.error().c_str()); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "%s", it->second.error().c_str()); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "%s", "See help for the syntax"); } } } ImGui::End(); + // For GRAFCET show sensor names from transitivities if (m_net.type() == TypeOfNet::GRAFCET) { - if (m_simulation.running) + ImGui::Begin("Sensors"); + for (auto& it: Sensors::instance().database()) { - ImGui::Begin("Sensors"); - for (auto& it: Sensors::instance().database()) - { - ImGui::SliderInt(it.first.c_str(), &it.second, 0, 1); - } - ImGui::End(); + int prev_value = it.second; + ImGui::SliderInt(it.first.c_str(), &it.second, 0, 1); + modified = (prev_value != it.second) && (!m_simulation.running); } + ImGui::End(); } } @@ -1012,7 +1072,9 @@ void Editor::inspector() if (arc.from.type == Node::Type::Transition) { std::string text(arc.from.key + " -> " + arc.to.arcsOut[0]->to.key); + float prev_value = arc.duration; ImGui::InputFloat(text.c_str(), &arc.duration, 0.01f, 1.0f, "%.3f", readonly); + modified = (prev_value != arc.duration); } } ImGui::End(); @@ -1024,10 +1086,18 @@ void Editor::inspector() for (auto& arc: m_net.arcs()) { std::string text(arc.from.key + " -> " + arc.to.key); + float prev_value = arc.duration; ImGui::InputFloat(text.c_str(), &arc.duration, 0.01f, 1.0f, "%.3f", readonly); + modified = (prev_value != arc.duration); } ImGui::End(); } + + // Modified net ? If yes, set it as dirty to force its save when the app + // is closed. Compiled receptivities ? + m_simulation.compiled = compiled; + m_net.modified |= modified; + modified = false; } //------------------------------------------------------------------------------ @@ -1045,6 +1115,27 @@ void Editor::toogleStartSimulation() // will be displayed at the same position instead of a // single AnimatedToken carying 2 tokens. framerate(m_simulation.running ? 30 : 60); // FPS + +#ifdef WITH_MQTT + // Simulate sensor reading value. + auto& database = Sensors::instance().database(); + for (auto const& sensor: database) + { + /* + auto const& sensor_name = sensor.first; + m_mqtt.subscribe("tpne/" + sensor_name, MQTT::QoS::QoS0, + [&](MQTT::Message const& msg) + { + const char* payload = static_cast(msg.payload); + auto it = database.find(sensor_name); + if (it != database.end()) + { + it->second = (payload[0] != '0'); + } + }); + */ + } +#endif } //------------------------------------------------------------------------------ @@ -1104,6 +1195,72 @@ Transition* Editor::getTransition(ImVec2 const& position) return nullptr; } +//------------------------------------------------------------------------------ +Transition& Editor::addTransition(float const x, float const y) +{ + auto action = std::make_unique(*this); + action->before(m_net); + Transition& transition = m_net.addTransition(x, y); + m_simulation.generateSensors(); // m_simulation.generateSensor(transition); + action->after(m_net); + m_history.add(std::move(action)); + return transition; +} + +//------------------------------------------------------------------------------ +void Editor::addPlace(float const x, float const y) +{ + auto action = std::make_unique(*this); + action->before(m_net); + m_net.addPlace(x, y); + action->after(m_net); + m_history.add(std::move(action)); +} + +//------------------------------------------------------------------------------ +void Editor::removeNode(Node& node) +{ + Node::Type type = node.type; + auto action = std::make_unique(*this); + action->before(m_net); + m_net.removeNode(node); + if (type == Node::Type::Transition) + { + m_simulation.generateSensors(); + } + action->after(m_net); + m_history.add(std::move(action)); +} + +//------------------------------------------------------------------------------ +Node& Editor::addOppositeNode(Node::Type const type, float const x, + float const y, size_t const tokens) +{ + auto action = std::make_unique(*this); + action->before(m_net); + m_simulation.generateSensors(); + Node& node = m_net.addOppositeNode(type, x, y, tokens); + if (node.type == Node::Type::Transition) + { + // FIXME m_simulation.generateSensor(node); + m_simulation.generateSensors(); + } + action->after(m_net); + m_history.add(std::move(action)); + return node; +} + +//------------------------------------------------------------------------------ +void Editor::addArc(Node& from, Node& to, float const duration) +{ + auto action = std::make_unique(*this); + action->before(m_net); + m_net.addArc(from, to, duration); + m_simulation.generateSensors(); + action->after(m_net); + m_history.add(std::move(action)); +} + //------------------------------------------------------------------------------ void Editor::loadNetFile() { @@ -1323,7 +1480,6 @@ std::vector const& Editor::getLogs() const void Editor::clearLogs() { m_messages.clear(); - } //-------------------------------------------------------------------------- @@ -1339,8 +1495,8 @@ void Editor::undo() else { m_messages.setInfo("Undo!"); + m_net.modified = true; } - m_net.modified = true; } //-------------------------------------------------------------------------- @@ -1356,8 +1512,9 @@ void Editor::redo() else { m_messages.setInfo("Redo!"); + m_net.modified = true; + m_simulation.compiled = false; } - m_net.modified = true; } //-------------------------------------------------------------------------- @@ -1509,29 +1666,25 @@ void Editor::PetriView::handleAddNode(ImGuiMouseButton button) { float const x = m_mouse.position.x; float const y = m_mouse.position.y; - auto action = std::make_unique(m_editor); - action->before(m_editor.m_net); if (m_editor.m_net.type() == TypeOfNet::TimedEventGraph) { // In TimedEventGraph mode, we prefer avoiding creating // places because they are not displayed. So only create // transitions and arcs. - m_editor.m_net.addTransition(x, y); + m_editor.addTransition(x, y); } else { // In other mode, Petri nets have two types of nodes. if (button == MOUSE_BOUTON_ADD_PLACE) { - m_editor.m_net.addPlace(x, y); + m_editor.addPlace(x, y); } else if (button == MOUSE_BOUTON_ADD_TRANSITION) { - m_editor.m_net.addTransition(x, y); + m_editor.addTransition(x, y); } } - action->after(m_editor.m_net); - m_editor.m_history.add(std::move(action)); } } else if (m_editor.m_net.type() == TypeOfNet::PetriNet) @@ -1564,9 +1717,6 @@ void Editor::PetriView::handleArcDestination() m_mouse.to = m_editor.getNode(m_mouse.position); m_mouse.handling_arc = false; - auto action = std::make_unique(m_editor); - action->before(m_editor.m_net); - if (m_editor.m_net.type() == TypeOfNet::TimedEventGraph) { // In TimedEventGraph mode we only create transitions since places are @@ -1574,12 +1724,12 @@ void Editor::PetriView::handleArcDestination() if (m_mouse.from == nullptr) { assert(m_mouse.to != nullptr); - m_mouse.from = &m_editor.m_net.addTransition(m_mouse.clicked_at.x, m_mouse.clicked_at.y); + m_mouse.from = &m_editor.addTransition(m_mouse.clicked_at.x, m_mouse.clicked_at.y); } if (m_mouse.to == nullptr) { assert(m_mouse.from != nullptr); - m_mouse.to = &m_editor.m_net.addTransition(m_mouse.position.x, m_mouse.position.y); + m_mouse.to = &m_editor.addTransition(m_mouse.position.x, m_mouse.position.y); } } else @@ -1591,12 +1741,12 @@ void Editor::PetriView::handleArcDestination() else if (m_mouse.from == nullptr) { assert(m_mouse.to != nullptr); - m_mouse.from = &m_editor.m_net.addOppositeNode(m_mouse.to->type, m_mouse.clicked_at.x, m_mouse.clicked_at.y); + m_mouse.from = &m_editor.addOppositeNode(m_mouse.to->type, m_mouse.clicked_at.x, m_mouse.clicked_at.y); } else if (m_mouse.to == nullptr) { assert(m_mouse.from != nullptr); - m_mouse.to = &m_editor.m_net.addOppositeNode(m_mouse.from->type, m_mouse.position.x, m_mouse.position.y); + m_mouse.to = &m_editor.addOppositeNode(m_mouse.from->type, m_mouse.position.x, m_mouse.position.y); } } @@ -1604,10 +1754,7 @@ void Editor::PetriView::handleArcDestination() assert(m_mouse.to != nullptr); // The case where two nodes have the same type is managed by addArc - m_editor.m_net.addArc(*m_mouse.from, *m_mouse.to, randomInt(1, 5)); - - action->after(m_editor.m_net); - m_editor.m_history.add(std::move(action)); + m_editor.addArc(*m_mouse.from, *m_mouse.to, randomInt(1, 5)); // Reset states m_mouse.from = m_mouse.to = nullptr; @@ -1714,11 +1861,7 @@ void Editor::PetriView::onHandleInput() Node* node = m_editor.getNode(m_mouse.position); if (node != nullptr) { - auto action = std::make_unique(m_editor); - action->before(m_editor.m_net); - m_editor.m_net.removeNode(*node); - action->after(m_editor.m_net); - m_editor.m_history.add(std::move(action)); + m_editor.removeNode(*node); } } } diff --git a/src/Editor/DearImGui/Editor.hpp b/src/Editor/DearImGui/Editor.hpp index dabe8f3..f099ef6 100644 --- a/src/Editor/DearImGui/Editor.hpp +++ b/src/Editor/DearImGui/Editor.hpp @@ -36,15 +36,10 @@ namespace tpne { -# ifndef WITH_MQTT -//! \brief Dummy MQTT class since disabled by the Makefile -class MQTT {}; -# endif - // **************************************************************************** //! \brief Graphical User interface for manipulating and simulating Petri net. // **************************************************************************** -class Editor: public PetriNetEditor, public Application, protected MQTT +class Editor: public PetriNetEditor, public Application { public: @@ -69,12 +64,6 @@ class Editor: public PetriNetEditor, public Application, protected MQTT virtual void onDraw() override; void close(); -#ifdef WITH_MQTT -private: // Inheritance from MQTT - - virtual void onConnected(int rc) override; -#endif - private: // Widgets void menu(); @@ -96,9 +85,6 @@ class Editor: public PetriNetEditor, public Application, protected MQTT private: // Petri net services Node* getNode(ImVec2 const& position); - Place* getPlace(ImVec2 const& position); - Transition* getTransition(ImVec2 const& position); - bool switchOfNet(TypeOfNet const type); void exportNetTo(Exporter const& exporter); void importNetFrom(Importer const& importer); void loadNetFile(); @@ -110,6 +96,19 @@ class Editor: public PetriNetEditor, public Application, protected MQTT void undo(); void redo(); void springify(); + bool initMQTT(); + +private: + + Place* getPlace(ImVec2 const& position); + Transition* getTransition(ImVec2 const& position); + bool switchOfNet(TypeOfNet const type); + Transition& addTransition(float const x, float const y); + void addPlace(float const x, float const y); + void removeNode(Node& node); + Node& addOppositeNode(Node::Type const type, float const x, float const y, + size_t const tokens = 0u); + void addArc(Node& from, Node& to, float const duration = 0.0f); private: // Error logs @@ -298,6 +297,10 @@ class Editor: public PetriNetEditor, public Application, protected MQTT History m_history; //! \brief Instance allowing to do timed simulation. Simulation m_simulation; +#ifdef WITH_MQTT + //! \brief Allow to control the net from network. + MQTT m_mqtt; +#endif //! \brief Critical cycle found by Howard algorithm. Also used to show //! where are erroneous arcs making the Petri net not be a graph event. std::vector m_marked_arcs; diff --git a/src/Editor/DearImGui/KeyBindings.hpp b/src/Editor/DearImGui/KeyBindings.hpp index 4cf08ca..a714a11 100644 --- a/src/Editor/DearImGui/KeyBindings.hpp +++ b/src/Editor/DearImGui/KeyBindings.hpp @@ -30,7 +30,7 @@ # define KEY_RUN_SIMULATION_ALT ImGuiKey_R # define KEY_ROTATE_CW ImGuiKey_PageUp # define KEY_ROTATE_CCW ImGuiKey_PageDown -# define KEY_MOVE_PETRI_NODE ImGuiKey_M +# define KEY_MOVE_PETRI_NODE ImGuiKey_Semicolon # define KEY_INCREMENT_TOKENS ImGuiKey_KeypadAdd # define KEY_DECREMENT_TOKENS ImGuiKey_KeypadSubtract # define KEY_DELETE_NODE ImGuiKey_Delete diff --git a/src/Net/Exports/ExportGrafcetCpp.cpp b/src/Net/Exports/ExportGrafcetCpp.cpp index 8a14029..d59020e 100644 --- a/src/Net/Exports/ExportGrafcetCpp.cpp +++ b/src/Net/Exports/ExportGrafcetCpp.cpp @@ -26,6 +26,33 @@ namespace tpne { +static std::string camelCase(std::string const& line) +{ + std::string res(line); + bool active = true; + + for(int i = 0; res[i] != '\0'; i++) + { + if (std::isalpha(res[i])) + { + if (active) + { + res[i] = char(std::toupper(res[i])); + active = false; + } + else + { + res[i] = char(std::tolower(res[i])); + } + } + else if (res[i] == ' ') + { + active = true; + } + } + return res; +} + //------------------------------------------------------------------------------ std::string exportToGrafcetCpp(Net const& net, std::string const& filename) { @@ -39,11 +66,17 @@ std::string exportToGrafcetCpp(Net const& net, std::string const& filename) return error.str(); } - // Generate the C++ namespace and header guards + // Generate the C++ namespace std::string name_space = net.name; + std::for_each(name_space.begin(), name_space.end(), [](char & c) { + c = char(::tolower(int(c))); + if (c == ' ') { c = '_'; } + }); + // Generate the C++ header guards std::string header_guards(name_space); std::for_each(header_guards.begin(), header_guards.end(), [](char & c) { c = char(::toupper(int(c))); + if (c == ' ') { c = '_'; } }); file << "// This file has been generated and you should avoid editing it." << std::endl; @@ -52,219 +85,194 @@ std::string exportToGrafcetCpp(Net const& net, std::string const& filename) file << "#ifndef GENERATED_GRAFCET_" << header_guards << "_HPP" << std::endl; file << "# define GENERATED_GRAFCET_" << header_guards << "_HPP" << std::endl; file << "" << std::endl; - file << "# include " << std::endl; - file << "# include \"MQTT.hpp\"" << std::endl; - file << "" << std::endl; + // FIXME #ifndef GRAFCET_WITH_DEBUG + //file << "# include " << std::endl; + //file << "" << std::endl; + file << "# ifndef GRAFCET_SENSOR_TYPE" << std::endl; + file << "# define GRAFCET_SENSOR_TYPE bool" << std::endl; + file << "# endif" << std::endl << std::endl; file << "namespace " << name_space << " {" << std::endl; file << R"PN( // ***************************************************************************** //! \brief // ***************************************************************************** -class Grafcet: public MQTT +class Grafcet { -private: // MQTT - - //------------------------------------------------------------------------- - //! \brief Callback when this class is connected to the MQTT broker. - //------------------------------------------------------------------------- - virtual void onConnected(int /*rc*/) override; - - //------------------------------------------------------------------------- - //! \brief Callback when this class is has received a new message from the - //! MQTT broker. - //------------------------------------------------------------------------- - virtual void onMessageReceived(const struct mosquitto_message& message) override; - - //------------------------------------------------------------------------- - //! \brief Transmit to the Petri net editor all transitions that have been - //! fired. - //------------------------------------------------------------------------- - void publish() - { - static char message[MAX_TRANSITIONS + 1u] = { 'T' }; - - for (size_t i = 0u; i < MAX_TRANSITIONS; ++i) - message[i + 1u] = T[i]; - - MQTT::publish(topic().c_str(), std::string(message, MAX_TRANSITIONS + 1u), MQTT::QoS::QoS0); - } - public: //------------------------------------------------------------------------- //! \brief Restore all states of the GRAFCET to their initial states. //------------------------------------------------------------------------- - Grafcet() { initGPIO(); reset(); } - - //------------------------------------------------------------------------- - //! \brief Return the MQTT topic to talk with the Petri net editor. - //! Call Grafcet grafcet - //------------------------------------------------------------------------- - std::string& topic() { return m_topic; } + Grafcet() { initInputsGPIOs(); initOutputGPIOs(); reset(); } //------------------------------------------------------------------------- - //! \brief Print values of transitions and steps - //------------------------------------------------------------------------- - void debug() const - { - std::cout << "Transitions:" << std::endl; - for (size_t i = 0u; i < MAX_TRANSITIONS; ++i) - { - std::cout << " Transition[" << i << "] = " << T[i] - << std::endl; - } - - std::cout << "Steps:" << std::endl; - for (size_t i = 0u; i < MAX_STEPS; ++i) - { - std::cout << " Step[" << i << "] = " << X[i] - << std::endl; - } - } - - //------------------------------------------------------------------------- - //! \brief Desactivate all steps except the ones initially activated + //! \brief Reset the sequence to the initial step. //------------------------------------------------------------------------- void reset() { )PN"; - + file << "// Reset sensors ?" << std::endl; + file << " init = true;" << std::endl; auto const& places = net.places(); for (size_t i = 0; i < places.size(); ++i) { file << " X[" << places[i].id << "] = " << (places[i].tokens ? "true; " : "false;") - << " // " << places[i].caption << std::endl; } file << R"PN( } //------------------------------------------------------------------------- - //! \brief + //! \brief Update one cycle of the GRAFCET: read sensors, update states, + //! write outputs. The update follows the document + //! http://legins69.free.fr/automatisme/PL7Pro/GRAFCET.pdf //------------------------------------------------------------------------- - void step() - { - doActions(); - readInputs(); - setTransitions(); - setSteps(); - } - -private: - - //------------------------------------------------------------------------- - //! \brief - //------------------------------------------------------------------------- - void initGPIO(); - - //------------------------------------------------------------------------- - //! \brief - //------------------------------------------------------------------------- - void readInputs(); - - //------------------------------------------------------------------------- - //! \brief - //------------------------------------------------------------------------- - void doActions() + void update() { )PN"; - for (size_t p = 0u; p < net.places().size(); ++p) + std::string del; + + // Read sensors + file << " // Read sensors:" << std::endl; + for (auto const& s: Sensors::instance().database()) { - file << " if (X[" << p << "]) { P" << p << "(); }" + file << " " << s.first + << " = readSensor" << camelCase(s.first) << "();" << std::endl; } - file << " }" << std::endl << R"PN( - //------------------------------------------------------------------------- - //! \brief - //------------------------------------------------------------------------- - void setTransitions() - { -)PN"; - - for (auto const& trans: net.transitions()) + file << std::endl << " // Update GRAFCET states:" << std::endl; + // Compute T[n] = X[n] . R[n] + for (auto const& t: net.transitions()) { - file << " T[" << trans.id << "] ="; - for (size_t a = 0; a < trans.arcsIn.size(); ++a) + file << " T[" << t.id << "] = "; + del = ""; + for (auto const& p: t.arcsIn) { - Arc& arc = *trans.arcsIn[a]; - if (a > 0u) { file << " &&"; } - file << " X[" << arc.from.id << "]"; + file << del << "X[" << p->from.id << "]"; + del = " & "; } - file << " && T" << trans.id << "();\n"; + file << del << t.key << "();" + << " // Transition " << t.id << ": " << t.caption + << std::endl; } - file << " publish();" << std::endl << " }" << std::endl << R"PN( - //------------------------------------------------------------------------- - //! \brief - //------------------------------------------------------------------------- - void setSteps() - { -)PN"; - - for (auto const& trans: net.transitions()) + // Compute X[n] = T[n-1] + X[n] . /T[n] + for (auto const& p: net.places()) { - file << " if (T[" << trans.id << "])" << std::endl; - file << " {" << std::endl; - - for (auto const& arc: trans.arcsIn) + file << " X[" << p.id << "] = "; + del = ""; + for (auto const& t: p.arcsIn) { - file << " X[" << arc->from.id << "] = false;" << std::endl; + file << del << "T[" << t->from.id << "]"; + del = " | "; } - - for (auto const& arc: trans.arcsOut) + if (p.arcsIn.size() > 0u) + { + file << " | "; + } + if (p.arcsOut.size() == 0u) + { + file << "X[" << p.id << "]"; + } + else + { + file << "(X[" << p.id << "] & "; + del = ""; + for (auto const& t: p.arcsOut) + { + file << del << "(!T[" << t->to.id << "])"; + del = " & "; + } + file << ")"; + } + if (p.tokens > 0u) { - file << " X[" << arc->to.id << "] = true;" << std::endl; + file << " | init"; } + file << "; // Step " << p.id << ": " << p.caption << std::endl; + } - file << " }" << std::endl;; + file << std::endl << " // Update outputs:" << std::endl; + // TODO Sorties + // Pour toutes les sorties: faire la liste des Etapes qui les utilisent avec | + for (auto const& p: net.places()) + { + file << " outputs[xxx] = X[yyy] + (X[zzz] & inibiteur[zzz]);" << std::endl; } + for (auto const& p: net.places()) + { + file << " P" << p.id << "(outputs[xxx]);" << std::endl; + } + + file << std::endl << " // End of the initial GRAFCET cycle" << std::endl; + file << " init = false;"; + file << R"PN( + } + +private: // You have to implement the following methods in the C++ file + + //------------------------------------------------------------------------- + //! \brief Initialize the input GPIOs. + //------------------------------------------------------------------------- + void initInputsGPIOs(); + //------------------------------------------------------------------------- + //! \brief Initialize the output GPIOs. + //------------------------------------------------------------------------- + void initOutputGPIOs(); - file << " }" << std::endl << std::endl << "private: // You have to implement the following methods in the C++ file" - << std::endl << std::endl; +)PN"; + + for (auto const& s: Sensors::instance().database()) + { + file << " //-------------------------------------------------------------------------" << std::endl; + file << " //! \\brief Read sensor " << s.first << std::endl; + file << " //-------------------------------------------------------------------------" << std::endl; + file << " bool readSensor" << camelCase(s.first) << "();" << std::endl; + } + file << std::endl; for (auto const& t: net.transitions()) { file << " //-------------------------------------------------------------------------" << std::endl; - file << " //! \\brief Transition " << t.id << ": \"" << t.caption << "\"" << std::endl; + file << " //! \\brief Compute the receptivity of the transition " << t.id << "." << std::endl; + file << " //! RPN boolean equation: \"" << t.caption << "\"" << std::endl; file << " //! \\return true if the transition is enabled." << std::endl; file << " //-------------------------------------------------------------------------" << std::endl; - if (net.type() == TypeOfNet::GRAFCET) - { - file << " bool T" << t.id << "() { return " << Receptivity::Parser::translate(t.caption, "C") << "; } const"; - } - else - { - file << " bool T" << t.id << "() const;"; - } - file << std::endl << std::endl; + file << " bool T" << t.id << "() const { return !!(" + << Receptivity::Parser::translate(t.caption, "C") + << "); }" << std::endl; } + file << std::endl; for (auto const& p: net.places()) { file << " //-------------------------------------------------------------------------" << std::endl; file << " //! \\brief Do actions associated with the step " << p.id << ": " << p.caption << std::endl; file << " //-------------------------------------------------------------------------" << std::endl; - file << " void P" << p.id << "();" << std::endl << std::endl; + file << " void P" << p.id << "(const bool activated);" << std::endl; } file << std::endl << "private:" << std::endl << std::endl; - file << " const size_t MAX_STEPS = " << net.places().size() << "u;" << std::endl; - file << " const size_t MAX_TRANSITIONS = " << net.transitions().size() << "u;" << std::endl; - file << " //! \\brief Steps" << std::endl; - file << " bool X[MAX_STEPS];" << std::endl; - file << " //! \\brief Transitions" << std::endl; - file << " bool T[MAX_TRANSITIONS];" << std::endl; - file << " //! \\brief MQTT topic to communicate with the Petri net editor" << std::endl; - file << " std::string m_topic = \"pneditor/" << name_space << "\";" << std::endl; + file << " //! \\brief States of transitions." << std::endl; + file << " bool T[" << net.transitions().size() << "];" << std::endl; + file << " //! \\brief States of steps." << std::endl; + file << " bool X[" << net.places().size() << "];" << std::endl; + file << " //! \\brief List of sensors:" << std::endl; for (auto const& s: Sensors::instance().database()) { - file << " //! \\brief" << std::endl; - file << " bool " << s.first << " = " << s.second << ";" << std::endl; + file << " GRAFCET_SENSOR_TYPE " << s.first << " = " << s.second << ";" << std::endl; } + //file << " //! \\brief List of actions:" << std::endl; + //for (auto const& s: Actuators::instance().database()) + //{ + // file << " GRAFCET_SENSOR_TYPE " << s.first << " = " << s.second << ";" << std::endl; + //} + file << " //! \\brief Initial GRAFCET cycle." << std::endl; + file << " bool init = true;" << std::endl; file << "};" << std::endl; file << "" << std::endl; file << "} // namespace " << name_space << std::endl; diff --git a/src/Net/Exports/ExportJSON.cpp b/src/Net/Exports/ExportJSON.cpp index 4fcd034..ee0bb14 100644 --- a/src/Net/Exports/ExportJSON.cpp +++ b/src/Net/Exports/ExportJSON.cpp @@ -19,6 +19,7 @@ //============================================================================= #include "Net/Exports/Exports.hpp" +#include "Net/Receptivities.hpp" #include "TimedPetriNetEditor/PetriNet.hpp" #include "nlohmann/json.hpp" #include @@ -40,8 +41,6 @@ std::string exportToJSON(Net const& net, std::string const& filename) return error.str(); } - // TODO sensors - file << "{" << std::endl; file << " \"revision\": 3," << std::endl; file << " \"type\": \"" << to_str(net.type()) << "\"," << std::endl; @@ -74,17 +73,27 @@ std::string exportToJSON(Net const& net, std::string const& filename) for (auto const& a: net.arcs()) { file << separator; separator = ",\n"; - file << " { \"from\": \"" << a.from.key << "\", " << "\"to\": \"" << a.to.key - << "\""; + file << " { \"from\": \"" << a.from.key << "\", " << "\"to\": \"" << a.to.key << "\""; if (a.from.type == Node::Type::Transition) file << ", \"duration\": " << a.duration; file << " }"; } + + // GRAFCET sensors + separator = "\n"; + file << "\n ],\n \"sensors\": ["; + for (auto& it: Sensors::instance().database()) + { + file << separator; separator = ",\n"; + file << " { \"name\": \"" << it.first.c_str() << "\", " + << "\"value\": " << it.second << " }"; + } file << "\n ]" << std::endl; - file << " }" << std::endl; - file << " ]" << std::endl; - file << "}" << std::endl; + // TODO GRAFCET actions + + file << " }\n ]\n"; // nets + file << "}" << std::endl; // json document return {}; } diff --git a/src/Net/Exports/Exports.cpp b/src/Net/Exports/Exports.cpp index b2e7cef..9d56e54 100644 --- a/src/Net/Exports/Exports.cpp +++ b/src/Net/Exports/Exports.cpp @@ -28,13 +28,13 @@ std::vector const& exporters() { static const std::vector s_exporters = { { "JSON", ".json", exportToJSON }, - { "Grafcet C++", ".hpp,.h,.hh,.h++", exportToGrafcetCpp }, + { "Arduino GRAFCET", ".hpp,.h,.hh,.h++", exportToGrafcetCpp }, { "Symfony", ".yaml", exportToSymfony }, { "Julia", ".jl", exportToJulia }, { "Draw.io", ".drawio.xml", exportToDrawIO }, { "Graphviz", ".gv,.dot", exportToGraphviz }, { "PN-Editor", ".pns,.pnl,.pnk,.pnkp", exportToPNEditor }, - { "Petri-LaTeX", ".tex", exportToPetriLaTeX }, + { "Petri LaTeX", ".tex", exportToPetriLaTeX }, { "Petri Net Markup Language", ".pnml", exportToPNML }, { "Timed Event Graph", ".teg", exportToTimedEventGraph }, //{ "Codesys", ".codesys.xml", exportToCodesys }, diff --git a/src/Net/Imports/ImportJSON.cpp b/src/Net/Imports/ImportJSON.cpp index 79098cf..90f7648 100644 --- a/src/Net/Imports/ImportJSON.cpp +++ b/src/Net/Imports/ImportJSON.cpp @@ -28,6 +28,7 @@ namespace tpne { //------------------------------------------------------------------------------ +// TODO read sensor values std::string importFromJSON(Net& net, std::string const& filename) { std::stringstream error; diff --git a/src/Net/Simulation.cpp b/src/Net/Simulation.cpp index e26df7a..d2cc242 100644 --- a/src/Net/Simulation.cpp +++ b/src/Net/Simulation.cpp @@ -105,25 +105,12 @@ void Simulation::stateStarting() { a.count = 0u; } - m_net.resetReceptivies(); - // Check for GRAFCET if boolean expressions in transitivities have - // not syntaxical errors. - if (m_net.type() == TypeOfNet::GRAFCET) + // Reset values on transitivities and sensors for GRAFCET + m_net.resetReceptivies(); + if (!generateSensors()) { - Sensors::instance().clear(); - m_receptivities.clear(); - m_receptivities.resize(m_net.transitions().size()); - for (auto const& it: m_net.transitions()) - { - std::string error = m_receptivities[it.id].compile(it.caption, m_net); - if (!error.empty()) - { - m_messages.setWarning(error); - running = false; - return ; - } - } + running = false; } // @@ -144,6 +131,48 @@ void Simulation::stateStarting() m_state = Simulation::State::Simulating; } +//------------------------------------------------------------------------------ +// FIXME Sensors::instance().clear(); is violent we loose current sensor values +// => they are reset to false. +// https://github.com/Lecrapouille/TimedPetriNetEditor/issues/29 +bool Simulation::generateSensors() +{ + //if (this->compiled) + // return true; + + // Check for GRAFCET if boolean expressions in transitivities have + // not syntaxical errors. + if (m_net.type() == TypeOfNet::GRAFCET) + { + Sensors::instance().clear(); + m_receptivities.clear(); + for (auto const& it: m_net.transitions()) + { + std::string error = m_receptivities[it.id].compile(it.caption, m_net); + if (!error.empty()) + { + m_messages.setWarning(error); + return false; + } + } + } + this->compiled = true; + return true; +} + +//------------------------------------------------------------------------------ +bool Simulation::generateSensor(Transition const& transition) +{ + std::string error = m_receptivities[transition.id].compile(transition.caption, m_net); + if (!error.empty()) + { + m_messages.setWarning(error); + this->compiled = false; + return false; + } + return true; +} + //------------------------------------------------------------------------------ void Simulation::stateHalting() { diff --git a/src/Net/Simulation.hpp b/src/Net/Simulation.hpp index 7554fed..36075c3 100644 --- a/src/Net/Simulation.hpp +++ b/src/Net/Simulation.hpp @@ -35,6 +35,9 @@ class Simulation { public: + using TimedTokens = std::vector; + using Receptivities = std::map; + // ************************************************************************* //! \brief State machine for the Petri net simulation. // ************************************************************************* @@ -47,8 +50,10 @@ class Simulation Simulation(Net& net, Messages& m_messages); void step(float const dt); - inline std::vector const& timedTokens() const { return m_timed_tokens; } - inline std::vector const& receptivities() const { return m_receptivities; } + bool generateSensors(); + bool generateSensor(Transition const& transition); + inline TimedTokens const& timedTokens() const { return m_timed_tokens; } + inline Receptivities const& receptivities() const { return m_receptivities; } private: @@ -63,6 +68,9 @@ class Simulation //! maintain the simulation running. Set false to halt the simulation. std::atomic running{false}; + //! \brief When set to true then receptivities shall be recompiled. + std::atomic compiled{false}; + private: //! \brief The single Petri net we are simulating @@ -72,11 +80,11 @@ class Simulation //! \brief List of shuffled Transitions. std::vector m_shuffled_transitions; //! \brief Animation of tokens when transitioning from Transitions to Places. - std::vector m_timed_tokens; + TimedTokens m_timed_tokens; //! \brief Memorize initial number of tokens in places. std::vector m_initial_tokens; - //! \brief For GRAFCET boolean expressions in transitions - std::vector m_receptivities; + //! \brief For GRAFCET boolean expressions in transitions. + Receptivities m_receptivities; //! \brief State machine for the simulation. Simulation::State m_state{Simulation::State::Idle};