MotionLib  1.0.0
SamBuCa motion library
AsyncTriggerController.hpp
1 /*
2 ** Copyright (C) 2023 CERN
3 **
4 ** This software is provided 'as-is', without any express or implied
5 ** warranty. In no event will the authors be held liable for any damages
6 ** arising from the use of this software.
7 **
8 ** Permission is granted to anyone to use this software for any purpose,
9 ** including commercial applications, and to alter it and redistribute it
10 ** freely, subject to the following restrictions:
11 **
12 ** 1. The origin of this software must not be misrepresented; you must not
13 ** claim that you wrote the original software. If you use this software
14 ** in a product, an acknowledgment in the product documentation would be
15 ** appreciated but is not required.
16 ** 2. Altered source versions must be plainly marked as such, and must not be
17 ** misrepresented as being the original software.
18 ** 3. This notice may not be removed or altered from any source distribution.
19 **
20 ** Created on: 2023-10-16
21 ** Author: Sam Wilson <samuel.wilson@cern.ch>
22 */
23 
24 #ifndef ASYNCTRIGGERCONTROLLER_HPP__
25 #define ASYNCTRIGGERCONTROLLER_HPP__
26 
27 #include <algorithm>
28 #include <cmath>
29 #include <memory>
30 
31 #include <ccut/async.hpp>
32 #include <ccut/utils.hpp>
33 #include <logger/Logger.hpp>
34 #include <oatpp/core/macro/component.hpp>
35 
36 #include "Exception.hpp"
37 #include "FutureCoroutine.hpp"
38 #include "OatExt.hpp"
39 #include "SmcController.hpp"
40 #include "device/DeviceBase.hpp"
41 #include "device/DeviceStore.hpp"
42 #include "device/Trigger.hpp"
43 #include "platform/PlatformFactory.hpp"
44 #include "util/serialize.hpp"
45 
46 #include OATPP_CODEGEN_BEGIN(ApiController)
47 
48 // clang-format off
50 {
51 public:
52  // create trigger component in UI
53  explicit AsyncTriggerController(
54  OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper)) :
55  SmcController(objectMapper)
56  {
57  addTag(*this, "trigger");
58  }
59  // create parameter sections and defaults
60  void addParams(
61  const std::shared_ptr<oatpp::web::server::api::Endpoint::Info> &info)
62  {
63  info->pathParams.add<String>("id")
64  .addExample("Global Trigger", String("grbl://1/trigger"))
65  .addExample("Trigger 0", String("grbl://1/trigger/0"));
66  }
67 
68  // Get Trigger State
69  ENDPOINT_INFO(getTriggerState)
70  {
71  // set summary and response type
72  info->summary = "trigger is armed?";
73  info->addResponse<Boolean>(Status::CODE_200, "application/json")
74  .addExample("example", Boolean(true));
75  info->summary = "checking if a trigger is armed is not currently "
76  "possible at grbl level";
77  addParams(info);
78  }
79  // create async endpoint for getting trigger state
80  ENDPOINT_ASYNC("GET", "/trigger/{id}/state", getTriggerState)
81  {
82  public:
83  // initialise endpoint
84  ENDPOINT_ASYNC_INIT(getTriggerState)
85  Action act() override
86  {
87  // get device store
88  auto deviceStore(controller->deviceStore.lock());
89  if (deviceStore)
90  {
91  // get trigger device from device store - if no id -> global trigger
92  smc::Trigger::Shared trigger = deviceStore->getDevice<smc::Trigger>(
93  ccut::urldecode(request->getPathVariable("id").getValue("")));
94 
95  // get trigger state - is it armed or not?
96  auto future = trigger->isArmed();
97  // wait for future to complete and then call onResult
98  return smc::debug::wait(future.share())
99  .callbackTo(&getTriggerState::onResult);
100  }
101  // return error if device store is not available
102  return _return(
103  controller->createResponse(Status::CODE_500, "no deviceStore"));
104  }
105 
106  Action onResult(const bool &armed)
107  {
108  return _return(
109  controller->createDtoResponse(Status::CODE_200, Boolean(armed)));
110  }
111  };
112 
113  // Arm the Trigger
114  ENDPOINT_INFO(putTriggerState)
115  {
116  // set summary and consumes type
117  info->summary = "arm trigger";
118  info->addResponse(Status::CODE_200, "application/json");
119  info->addConsumes<Boolean>("application/json")
120  .addExample("arm trigger", Boolean(true))
121  .addExample("disarm trigger", Boolean(false));
122 
123  addParams(info);
124  }
125  // create async endpoint for arming trigger
126  ENDPOINT_ASYNC("PUT", "/trigger/{id}/arm", putTriggerState)
127  {
128  public:
129  // initialise endpoint
130  ENDPOINT_ASYNC_INIT(putTriggerState)
131  Action act() override
132  {
133  // read body to dto and call onArmTrigger
134  return request
135  ->readBodyToDtoAsync<Boolean>(
136  controller->getDefaultObjectMapper())
137  .callbackTo(&putTriggerState::onArmTrigger);
138  }
139 
140  // arm trigger if armed is true, disarm if false
141  Action onArmTrigger(const Boolean &armed)
142  {
143  // get device store
144  auto deviceStore(controller->deviceStore.lock());
145  if (deviceStore)
146  {
147  // get trigger device from device store - if no id -> global trigger
148  smc::Trigger::Shared trigger = deviceStore->getDevice<smc::Trigger>(
149  ccut::urldecode(request->getPathVariable("id").getValue("")));
150 
151  // arm or disarm trigger
152  logger::info("arming trigger:") << trigger->toString();
153 
154  auto future = armed ? trigger->arm() : trigger->disarm();
155  logger::info("armed trigger:") << trigger->toString();
156  return smc::debug::wait(future.share())
157  .next(_return(
158  controller->createResponse(Status::CODE_200, "trigger state changed")));
159  }
160  return _return(
161  controller->createResponse(Status::CODE_500, "no deviceStore"));
162  }
163  };
164 
165  // Trigger the Trigger
166  ENDPOINT_INFO(postTriggerTrigger)
167  {
168  // set summary and consumes type
169  info->summary = "trigger the trigger";
170  info->addResponse(Status::CODE_200, "application/json");
171  addParams(info);
172  }
173 
174  // create async endpoint for triggering trigger
175  ENDPOINT_ASYNC("POST", "/trigger/{id}/trigger", postTriggerTrigger)
176  {
177  public:
178  // initialise endpoint
179  ENDPOINT_ASYNC_INIT(postTriggerTrigger)
180  Action act() override
181  {
182  // get device store
183  auto deviceStore(controller->deviceStore.lock());
184  if (deviceStore)
185  {
186  // get trigger device from device store - if no id -> global trigger
187  smc::Trigger::Shared trigger = deviceStore->getDevice<smc::Trigger>(
188  ccut::urldecode(request->getPathVariable("id").getValue("")));
189 
190  // trigger trigger
191  logger::info("triggering trigger:") << trigger->toString();
192 
193  auto future = trigger->trig();
194 
195  return smc::debug::wait(future.share())
196  .next(_return(
197  controller->createResponse(Status::CODE_200, "trigger triggered")));
198 
199  }
200  return _return(
201  controller->createResponse(Status::CODE_500, "no deviceStore"));
202 
203  }
204  };
205 };
206 
207 // clang-format on
208 
209 #include OATPP_CODEGEN_END(ApiController)
210 
211 #endif // ASYNCTRIGGERCONTROLLER_HPP__
std::string toString() const
debug and logging operation
Definition: DeviceBase.cpp:111
virtual std::future< bool > isArmed() const =0
check if trigger is armed