API mission
This commit is contained in:
278
src/server/api_mission_routes.cpp
Normal file
278
src/server/api_mission_routes.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "server/api_server.hpp"
|
||||
|
||||
#include "mission/mission_enqueue.hpp"
|
||||
#include "util/http_util.hpp"
|
||||
|
||||
namespace lm {
|
||||
|
||||
namespace {
|
||||
|
||||
nlohmann::json mirError(const std::string& msg)
|
||||
{
|
||||
return nlohmann::json{{"error", msg}, {"error_code", 400}};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ApiServer::enqueueRequest(const nlohmann::json& request, httplib::Response& res, int status_code)
|
||||
{
|
||||
nlohmann::json payload;
|
||||
std::string err;
|
||||
if (!MissionEnqueue::buildPayload(mission_store_, request, payload, err))
|
||||
{
|
||||
HttpUtil::jsonError(res, 400, err);
|
||||
return false;
|
||||
}
|
||||
const auto entry = mission_queue_.enqueue(payload, err);
|
||||
if (!entry)
|
||||
{
|
||||
HttpUtil::jsonError(res, 400, err);
|
||||
return false;
|
||||
}
|
||||
HttpUtil::addCors(res);
|
||||
res.status = status_code;
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = entry->dump();
|
||||
return true;
|
||||
}
|
||||
|
||||
nlohmann::json ApiServer::toMirQueueEntry(const nlohmann::json& entry) const
|
||||
{
|
||||
return nlohmann::json{{"id", entry.value("id", 0)},
|
||||
{"mission_id", entry.value("mission_id", "")},
|
||||
{"state", entry.value("status", "pending")},
|
||||
{"message", entry.value("mission_name", "")},
|
||||
{"priority", entry.value("priority", 0)},
|
||||
{"robot_id", entry.value("robot_id", "default")}};
|
||||
}
|
||||
|
||||
void ApiServer::registerMissionRoutes(httplib::Server& svr)
|
||||
{
|
||||
svr.Get("/api/missions", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_store_.snapshot().dump();
|
||||
});
|
||||
|
||||
svr.Put("/api/missions", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json payload;
|
||||
try
|
||||
{
|
||||
payload = nlohmann::json::parse(req.body);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
std::string err;
|
||||
if (!mission_store_.replace(payload, err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_store_.snapshot().dump();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiServer::registerIntegrationRoutes(httplib::Server& svr)
|
||||
{
|
||||
svr.Get("/api/triggers", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_store_.listTriggers().dump();
|
||||
});
|
||||
|
||||
svr.Post("/api/triggers", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json payload;
|
||||
try
|
||||
{
|
||||
payload = nlohmann::json::parse(req.body);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
std::string err;
|
||||
const auto trigger = mission_store_.addTrigger(payload, err);
|
||||
if (!trigger)
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.status = 201;
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = trigger->dump();
|
||||
});
|
||||
|
||||
svr.Delete(R"(/api/triggers/([0-9a-fA-F]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
std::string err;
|
||||
if (!mission_store_.deleteTrigger(req.matches[1].str(), err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.status = 204;
|
||||
});
|
||||
|
||||
svr.Get("/api/modbus/coils", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = modbus_.coilStates().dump();
|
||||
});
|
||||
|
||||
svr.Put(R"(/api/modbus/coils/([0-9]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json payload;
|
||||
try
|
||||
{
|
||||
payload = nlohmann::json::parse(req.body.empty() ? "{}" : req.body);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
const int coil_id = std::stoi(req.matches[1].str());
|
||||
const bool value = !payload.contains("value") || payload["value"].get<bool>();
|
||||
std::string err;
|
||||
if (!modbus_.writeCoil(coil_id, value, err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = nlohmann::json({{"coil_id", coil_id}, {"value", value}}).dump();
|
||||
});
|
||||
|
||||
svr.Post(R"(/api/modbus/coils/([0-9]+)/trigger)", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
const int coil_id = std::stoi(req.matches[1].str());
|
||||
std::string err;
|
||||
if (!modbus_.fireCoil(coil_id, err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = nlohmann::json({{"ok", true}, {"coil_id", coil_id}}).dump();
|
||||
});
|
||||
|
||||
svr.Get("/api/fleet/robots", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_store_.listRobots().dump();
|
||||
});
|
||||
|
||||
svr.Get("/api/fleet/schedules", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_store_.listSchedules().dump();
|
||||
});
|
||||
|
||||
svr.Post("/api/fleet/schedules", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json payload;
|
||||
try
|
||||
{
|
||||
payload = nlohmann::json::parse(req.body);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
std::string err;
|
||||
const auto schedule = mission_store_.addSchedule(payload, err);
|
||||
if (!schedule)
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
if (schedule->value("start_mode", "asap") == "asap" && schedule->value("enabled", true))
|
||||
scheduler_.runScheduleNow(schedule->value("id", ""), err);
|
||||
res.status = 201;
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = schedule->dump();
|
||||
});
|
||||
|
||||
svr.Put(R"(/api/fleet/schedules/([0-9a-fA-F]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json payload;
|
||||
try
|
||||
{
|
||||
payload = nlohmann::json::parse(req.body);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
std::string err;
|
||||
if (!mission_store_.updateSchedule(req.matches[1].str(), payload, err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = nlohmann::json({{"ok", true}}).dump();
|
||||
});
|
||||
|
||||
svr.Delete(R"(/api/fleet/schedules/([0-9a-fA-F]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
std::string err;
|
||||
if (!mission_store_.deleteSchedule(req.matches[1].str(), err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.status = 204;
|
||||
});
|
||||
|
||||
svr.Post(R"(/api/fleet/schedules/([0-9a-fA-F]+)/run)", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
std::string err;
|
||||
if (!scheduler_.runScheduleNow(req.matches[1].str(), err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = nlohmann::json({{"ok", true}}).dump();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiServer::registerMirV2Routes(httplib::Server& svr)
|
||||
{
|
||||
svr.Get("/api/v2.0.0/missions", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_store_.listMissions().dump();
|
||||
});
|
||||
|
||||
svr.Get("/api/v2.0.0/mission_queue", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json out = nlohmann::json::array();
|
||||
for (const auto& item : mission_queue_.list())
|
||||
{
|
||||
if (item.is_object())
|
||||
out.push_back(toMirQueueEntry(item));
|
||||
}
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = out.dump();
|
||||
});
|
||||
|
||||
svr.Post("/api/v2.0.0/mission_queue", [this](const httplib::Request& req, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
nlohmann::json payload;
|
||||
try
|
||||
{
|
||||
payload = nlohmann::json::parse(req.body);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
if (!payload.contains("source"))
|
||||
payload["source"] = "rest_api_v2";
|
||||
if (!enqueueRequest(payload, res, 201))
|
||||
return;
|
||||
nlohmann::json created = nlohmann::json::parse(res.body);
|
||||
res.body = toMirQueueEntry(created).dump();
|
||||
});
|
||||
|
||||
svr.Delete("/api/v2.0.0/mission_queue", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
std::string err;
|
||||
if (!mission_queue_.clearAll(err))
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.status = 204;
|
||||
});
|
||||
|
||||
svr.Get("/api/v2.0.0/status", [this](const httplib::Request&, httplib::Response& res) {
|
||||
HttpUtil::addCors(res);
|
||||
const auto runner = mission_queue_.runnerStatus();
|
||||
nlohmann::json body = {{"state_id", runner.value("state", "idle") == "running" ? 3
|
||||
: runner.value("state", "") == "paused" ? 4
|
||||
: 1},
|
||||
{"state_text", runner.value("state", "idle")},
|
||||
{"message", runner.value("message", "")}};
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = body.dump();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace lm
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "domain/layout_profile.hpp"
|
||||
#include "domain/layout_schema.hpp"
|
||||
#include "mission/mission_enqueue.hpp"
|
||||
#include "util/http_util.hpp"
|
||||
#include "util/id_util.hpp"
|
||||
#include "util/string_util.hpp"
|
||||
@@ -9,8 +10,16 @@
|
||||
|
||||
namespace lm {
|
||||
|
||||
ApiServer::ApiServer(StateRepository& repo, MissionQueue& mission_queue)
|
||||
: repo_(repo), mission_queue_(mission_queue)
|
||||
ApiServer::ApiServer(StateRepository& repo,
|
||||
MissionQueue& mission_queue,
|
||||
MissionStore& mission_store,
|
||||
ModbusTriggerService& modbus,
|
||||
MissionScheduler& scheduler)
|
||||
: repo_(repo),
|
||||
mission_queue_(mission_queue),
|
||||
mission_store_(mission_store),
|
||||
modbus_(modbus),
|
||||
scheduler_(scheduler)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -457,13 +466,9 @@ void ApiServer::registerRoutes(httplib::Server& svr)
|
||||
{
|
||||
return HttpUtil::jsonError(res, 400, "invalid JSON");
|
||||
}
|
||||
std::string err;
|
||||
const auto entry = mission_queue_.enqueue(payload, err);
|
||||
if (!entry)
|
||||
return HttpUtil::jsonError(res, 400, err);
|
||||
res.status = 201;
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = entry->dump();
|
||||
if (!payload.contains("source"))
|
||||
payload["source"] = "ui";
|
||||
enqueueRequest(payload, res, 201);
|
||||
});
|
||||
|
||||
svr.Delete("/api/mission_queue", [this](const httplib::Request&, httplib::Response& res) {
|
||||
@@ -520,6 +525,10 @@ void ApiServer::registerRoutes(httplib::Server& svr)
|
||||
res.set_header("Content-Type", "application/json; charset=utf-8");
|
||||
res.body = mission_queue_.runnerStatus().dump();
|
||||
});
|
||||
|
||||
registerMissionRoutes(svr);
|
||||
registerIntegrationRoutes(svr);
|
||||
registerMirV2Routes(svr);
|
||||
}
|
||||
|
||||
} // namespace lm
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <httplib.h>
|
||||
|
||||
#include "mission/mission_queue.hpp"
|
||||
#include "mission/mission_scheduler.hpp"
|
||||
#include "mission/mission_store.hpp"
|
||||
#include "mission/modbus_trigger_service.hpp"
|
||||
#include "storage/state_repository.hpp"
|
||||
|
||||
namespace lm {
|
||||
@@ -10,13 +13,26 @@ namespace lm {
|
||||
class ApiServer
|
||||
{
|
||||
public:
|
||||
ApiServer(StateRepository& repo, MissionQueue& mission_queue);
|
||||
ApiServer(StateRepository& repo,
|
||||
MissionQueue& mission_queue,
|
||||
MissionStore& mission_store,
|
||||
ModbusTriggerService& modbus,
|
||||
MissionScheduler& scheduler);
|
||||
|
||||
void registerRoutes(httplib::Server& svr);
|
||||
|
||||
private:
|
||||
StateRepository& repo_;
|
||||
MissionQueue& mission_queue_;
|
||||
MissionStore& mission_store_;
|
||||
ModbusTriggerService& modbus_;
|
||||
MissionScheduler& scheduler_;
|
||||
|
||||
bool enqueueRequest(const nlohmann::json& request, httplib::Response& res, int status_code = 201);
|
||||
nlohmann::json toMirQueueEntry(const nlohmann::json& entry) const;
|
||||
void registerMissionRoutes(httplib::Server& svr);
|
||||
void registerMirV2Routes(httplib::Server& svr);
|
||||
void registerIntegrationRoutes(httplib::Server& svr);
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
|
||||
Reference in New Issue
Block a user