Fawkes API  Fawkes Development Version
webview-ptzcam-processor.cpp
1 
2 /***************************************************************************
3  * webview-ptzcam-processor.cpp - Pan/tilt/zoom camera control for webview
4  *
5  * Created: Fri Feb 07 17:53:07 2014
6  * Copyright 2006-2014 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include "webview-ptzcam-processor.h"
23 
24 #include <core/exception.h>
25 #include <core/threading/mutex_locker.h>
26 #include <logging/logger.h>
27 #include <webview/page_reply.h>
28 #include <webview/error_reply.h>
29 #include <webview/redirect_reply.h>
30 #include <blackboard/blackboard.h>
31 #include <interfaces/PanTiltInterface.h>
32 #include <interfaces/CameraControlInterface.h>
33 #include <interfaces/SwitchInterface.h>
34 
35 #include <cstring>
36 #include <cmath>
37 #include <unistd.h>
38 
39 using namespace fawkes;
40 
41 /** @class WebviewPtzCamRequestProcessor "webview-ptzcam-processor.h"
42  * Pan/tilt/zoom camera request processor.
43  * @author Tim Niemueller
44  */
45 
46 
47 /** Constructor.
48  * @param base_url base URL of the webview PTZ cam web request processor.
49  * @param image_id Shared memory image buffer ID for viewing
50  * @param pantilt_id PanTiltInterface ID
51  * @param camctrl_id CameraControlInterface ID
52  * @param power_id SwitchInterface ID for powering PTU
53  * @param camera_id SwitchInterface ID for enabling/disabling image retrieval
54  * @param pan_increment value by which to increment pan value on request
55  * @param tilt_increment value by which to increment tilt value on request
56  * @param zoom_increment value by which to increment zoom value on request
57  * @param post_powerup_time time in seconds by which to delay reponse when
58  * turning on PTU and camera after inactivity
59  * @param presets pan/tilt preset values
60  * @param blackboard blackboard to open interfaces
61  * @param logger logger to report problems
62  */
64  std::string base_url, std::string image_id,
65  std::string pantilt_id, std::string camctrl_id,
66  std::string power_id, std::string camera_id,
67  float pan_increment, float tilt_increment,
68  unsigned int zoom_increment, float post_powerup_time,
69  std::map<std::string, std::tuple<std::string, float, float, unsigned int>> presets,
70  fawkes::BlackBoard *blackboard, fawkes::Logger *logger)
71 {
72  logger_ = logger;
73  blackboard_ = blackboard;
74  baseurl_ = base_url;
75  image_id_ = image_id;
76  pan_increment_ = pan_increment;
77  tilt_increment_ = tilt_increment;
78  zoom_increment_ = zoom_increment;
79  post_powerup_time_ = (long int)roundf(fabs(post_powerup_time) * 1000000);
80  presets_ = presets;
81 
82  ptu_if_ = blackboard->open_for_reading<PanTiltInterface>(pantilt_id.c_str());
83  camctrl_if_ = blackboard->open_for_reading<CameraControlInterface>(camctrl_id.c_str());
84  power_if_ = blackboard->open_for_reading<SwitchInterface>(power_id.c_str());
85  camen_if_ = blackboard->open_for_reading<SwitchInterface>(camera_id.c_str());
86 }
87 
88 
89 /** Destructor. */
91 {
92  blackboard_->close(ptu_if_);
93  blackboard_->close(camctrl_if_);
94  blackboard_->close(power_if_);
95  blackboard_->close(camen_if_);
96 }
97 
98 
99 WebReply *
101 {
102  if ( request->url().find(baseurl_) == 0 ) {
103  std::string subpath = request->url().substr(baseurl_.length());
104 
105  camen_if_->read();
106  if (power_if_->has_writer() && ! camen_if_->is_enabled()) {
107  try {
108  camen_if_->msgq_enqueue(new SwitchInterface::EnableSwitchMessage());
109  } catch (Exception &e) {
110  logger_->log_warn("WebviewPtzCamReqProc", "Failed to power up camera, exception follows");
111  logger_->log_warn("WebviewPtzCamReqProc", e);
112  }
113  }
114 
115  power_if_->read();
116  if (power_if_->has_writer() && ! power_if_->is_enabled()) {
117  try {
118  power_if_->msgq_enqueue(new SwitchInterface::EnableSwitchMessage());
119  usleep(post_powerup_time_);
120  } catch (Exception &e) {
121  logger_->log_warn("WebviewPtzCamReqProc", "Failed to power up PTU, exception follows");
122  logger_->log_warn("WebviewPtzCamReqProc", e);
123  }
124  }
125 
126  if (subpath == "/ping") {
128  r->add_header("Content-type", "text/plain");
129  r->append_body("OK\n");
130  return r;
131  } else if (subpath == "/move" || subpath == "/move/") {
132  ptu_if_->read();
133  camctrl_if_->read();
134 
135  // NOTE: this it at the moment mirrored for ceiling mounting!
136 
137  float pan_val = ptu_if_->pan(), tilt_val = ptu_if_->tilt();
138  unsigned int zoom_val = camctrl_if_->zoom();
139  float zoom = std::max(1u, camctrl_if_->zoom());
140  std::string pan_str = request->get_value("pan");
141  std::string tilt_str = request->get_value("tilt");
142  std::string zoom_str = request->get_value("zoom");
143 
144  if (pan_str != "") {
145  if (pan_str == "right") {
146  pan_val = std::max(ptu_if_->min_pan(), ptu_if_->pan() - pan_increment_ / zoom);
147  } else if (pan_str == "left") {
148  pan_val = std::min(ptu_if_->max_pan(), ptu_if_->pan() + pan_increment_ / zoom);
149  } else {
150  try {
151  pan_val = std::stof(request->get_value("pan").c_str());
152  } catch (std::exception &e) {} // ignored, use current val
153  }
154  }
155  if (tilt_str != "") {
156  if (tilt_str == "up") {
157  tilt_val = std::max(ptu_if_->min_tilt(), ptu_if_->tilt() - tilt_increment_ / zoom);
158  } else if (tilt_str == "down") {
159  tilt_val = std::min(ptu_if_->max_tilt(), ptu_if_->tilt() + tilt_increment_ / zoom);
160  } else {
161  try {
162  tilt_val = std::stof(request->get_value("tilt").c_str());
163  } catch (std::exception &e) {} // ignored, use current val
164  }
165  }
166 
167  if (tilt_str != "" || pan_str != "") {
169  new PanTiltInterface::GotoMessage(pan_val, tilt_val);
170  ptu_if_->msgq_enqueue(gotomsg);
171  }
172 
173  if (zoom_str != "") {
174  if (zoom_str == "out") {
175  zoom_val = std::max((long int)camctrl_if_->zoom_min(), (long int)camctrl_if_->zoom() - zoom_increment_);
176  } else if (zoom_str == "in") {
177  zoom_val = std::min((long int)camctrl_if_->zoom_max(), (long int)camctrl_if_->zoom() + zoom_increment_);
178  } else {
179  try {
180  zoom_val = std::stol(request->get_value("zoom").c_str());
181  } catch (std::exception &e) {} // ignored, use current val
182  }
183 
186  camctrl_if_->msgq_enqueue(setmsg);
187  }
188 
190  r->add_header("Content-type", "text/plain");
191  r->append_body("OK PAN %f TILT %f ZOOM %u\n", pan_val, tilt_val, zoom_val);
192  //r->append_body("FAIL DISABLED\n");
193  return r;
194 
195  } else if (subpath == "/effect" || subpath == "/effect/") {
196  camctrl_if_->read();
197 
200 
201  std::string effect_str = request->get_value("set");
202  if (effect_str == "none") {
204  } else if (effect_str == "negative") {
206  } else if (effect_str == "pastel") {
208  } else if (effect_str == "bw") {
210  } else if (effect_str == "solarize") {
212  } else {
214  r->add_header("Content-type", "text/plain");
215  r->append_body("FAIL UNKNOWN EFFECT %s\n", effect_str.c_str());
216  return r;
217  }
218 
219  camctrl_if_->msgq_enqueue(setmsg);
220 
222  r->add_header("Content-type", "text/plain");
223  r->append_body("OK EFFECT %s\n", effect_str.c_str());
224  return r;
225 
226  } else if (subpath == "" || subpath == "/") {
227  WebPageReply *r = new WebPageReply("SkyCam");
228  r->set_html_header(
229  " <link type=\"text/css\" href=\"/static/css/jqtheme/jquery-ui.custom.css\" rel=\"stylesheet\" />\n"
230  " <link type=\"text/css\" href=\"/static/css/webview-ptzcam.css\" rel=\"stylesheet\" />\n"
231  " <script type=\"text/javascript\" src=\"/static/js/jquery.min.js\"></script>\n"
232  " <script type=\"text/javascript\" src=\"/static/js/jquery-ui.custom.min.js\"></script>\n");
233 
234  *r += "<h2>SkyCam</h2>\n";
235 
236  r->append_body("<p><img id=\"image\" src=\"/images/view/%s.jpg\" /></p>\n", image_id_.c_str());
237 
238  // hardcoded baseurl here because it's so much simpler...
239  *r +=
240  "<script>\n"
241  "var frame_number = 0;\n"
242  "var move_jqxhr = null;\n"
243  "$(function() {\n"
244  " $( \"#toggle-stream\" ).button({\n"
245  " icons: {\n"
246  " primary: \"ui-icon-play\"\n"
247  " },\n"
248  " text: false\n"
249  " })\n"
250  " .click(function() {\n"
251  " $(this).blur();\n"
252  " var options;\n"
253  " var src = $('#image').attr('src');\n"
254  " var pos = src.indexOf('?');\n"
255  " if(pos != -1) src = src.substring(0, pos);\n"
256  " var srcstem = src.substring(0, src.lastIndexOf('.'));\n"
257  " if ( $( this ).text() === \"play\" ) {\n"
258  " options = {\n"
259  " label: \"pause\",\n"
260  " icons: {\n"
261  " primary: \"ui-icon-pause\"\n"
262  " }\n"
263  " };\n"
264  " $('#image').attr('src', srcstem + '.mjpeg');\n"
265  " $.ajax(\"/ptzcam/ping\");\n"
266  " } else {\n"
267  " options = {\n"
268  " label: \"play\",\n"
269  " icons: {\n"
270  " primary: \"ui-icon-play\"\n"
271  " }\n"
272  " };\n"
273  " frame_number += 1;\n"
274  " $('#image').attr('src', srcstem + '.jpg?' + frame_number);\n"
275  " }\n"
276  " $( this ).button( \"option\", options );\n"
277  " });\n"
278  " $( \"#refresh\" ).button({\n"
279  " icons: {\n"
280  " primary: \"ui-icon-refresh\"\n"
281  " },\n"
282  " text: false\n"
283  " })\n"
284  " .click(function() {\n"
285  " $(this).blur();\n"
286  " var src = $('#image').attr('src');\n"
287  " // check for existing ? and remove if found\n"
288  " var pos = src.indexOf('?');\n"
289  " if(pos != -1) src = src.substring(0, pos);\n"
290  " frame_number += 1;\n"
291  " $('#image').attr('src', src + '?' + frame_number);\n"
292  " $.ajax(\"/ptzcam/ping\");\n"
293  " return false;\n"
294  " });\n"
295  " $( \"#left\" ).button({\n"
296  " icons: {\n"
297  " primary: \"ui-icon-arrowthick-1-w\"\n"
298  " },\n"
299  " text: false\n"
300  " })\n"
301  " .click(function() {\n"
302  " $(this).blur();\n"
303  " if (move_jqxhr != null) move_jqxhr.abort();\n"
304  " move_jqxhr = $.ajax(\"/ptzcam/move?pan=left\");\n"
305  " });\n"
306  " $( \"#right\" ).button({\n"
307  " icons: {\n"
308  " primary: \"ui-icon-arrowthick-1-e\"\n"
309  " },\n"
310  " text: false\n"
311  " })\n"
312  " .click(function() {\n"
313  " $(this).blur();\n"
314  " if (move_jqxhr != null) move_jqxhr.abort();\n"
315  " move_jqxhr = $.ajax(\"/ptzcam/move?pan=right\");\n"
316  " });\n"
317  " $( \"#up\" ).button({\n"
318  " icons: {\n"
319  " primary: \"ui-icon-arrowthick-1-n\"\n"
320  " },\n"
321  " text: false\n"
322  " })\n"
323  " .click(function() {\n"
324  " $(this).blur();\n"
325  " if (move_jqxhr != null) move_jqxhr.abort();\n"
326  " move_jqxhr = $.ajax(\"/ptzcam/move?tilt=up\");\n"
327  " });\n"
328  " $( \"#down\" ).button({\n"
329  " icons: {\n"
330  " primary: \"ui-icon-arrowthick-1-s\"\n"
331  " },\n"
332  " text: false\n"
333  " })\n"
334  " .click(function() {\n"
335  " $(this).blur();\n"
336  " if (move_jqxhr != null) move_jqxhr.abort();\n"
337  " move_jqxhr = $.ajax(\"/ptzcam/move?tilt=down\");\n"
338  " });\n"
339  " $( \"#center\" ).button({\n"
340  " icons: {\n"
341  " primary: \"ui-icon-bullet\"\n"
342  " },\n"
343  " text: false\n"
344  " })\n"
345  " .click(function() {\n"
346  " $(this).blur();\n"
347  " if (move_jqxhr != null) move_jqxhr.abort();\n"
348  " move_jqxhr = $.ajax(\"/ptzcam/move?pan=0&tilt=0\");\n"
349  " });\n"
350  " $( \"#zoom-in\" ).button({\n"
351  " icons: {\n"
352  " primary: \"ui-icon-zoomin\"\n"
353  " },\n"
354  " text: false\n"
355  " })\n"
356  " .click(function() {\n"
357  " $(this).blur();\n"
358  " if (move_jqxhr != null) move_jqxhr.abort();\n"
359  " move_jqxhr = $.ajax(\"/ptzcam/move?zoom=in\");\n"
360  " });\n"
361  " $( \"#zoom-out\" ).button({\n"
362  " icons: {\n"
363  " primary: \"ui-icon-zoomout\"\n"
364  " },\n"
365  " text: false\n"
366  " })\n"
367  " .click(function() {\n"
368  " $(this).blur();\n"
369  " if (move_jqxhr != null) move_jqxhr.abort();\n"
370  " move_jqxhr = $.ajax(\"/ptzcam/move?zoom=out\");\n"
371  " });\n"
372  " $( \"#zoom-reset\" ).button({\n"
373  " icons: {\n"
374  " primary: \"ui-icon-search\"\n"
375  " },\n"
376  " text: false\n"
377  " })\n"
378  " .click(function() {\n"
379  " $(this).blur();\n"
380  " if (move_jqxhr != null) move_jqxhr.abort();\n"
381  " move_jqxhr = $.ajax(\"/ptzcam/move?zoom=0\");\n"
382  " });\n"
383  "});\n"
384  "</script>\n"
385  "\n"
386  "<button id=\"refresh\" title=\"Refresh\">Refresh</button>\n"
387  "<button id=\"toggle-stream\" title=\"Toggle Stream\">play</button>\n"
388  "<button id=\"left\" title=\"Pan left\">left</button>\n"
389  "<button id=\"right\" title=\"Pan right\">right</button>\n"
390  "<button id=\"up\" title=\"Tilt up\">up</button>\n"
391  "<button id=\"down\" title=\"Tilt down\">down</button>\n"
392  "<button id=\"center\" title=\"Re-center camera\">center</button>\n"
393  "<button id=\"zoom-in\" title=\"Zoom in\">zoom-out</button>\n"
394  "<button id=\"zoom-out\" title=\"Zoom out\">zoom-in</button>\n"
395  "<button id=\"zoom-reset\" title=\"Reset zoom\">zoom-reset</button>\n"
396  "<br/>\n"
397  "<form style=\"margin-top: 1em;\">\n"
398  " <div id=\"filter\">\n"
399  " <input type=\"radio\" id=\"filter-title\" name=\"radio\" />"
400  "<label for=\"filter-title\">Filter</label>\n"
401  " <input type=\"radio\" id=\"filter-none\" name=\"radio\" checked=\"checked\"/>"
402  "<label for=\"filter-none\">None</label>\n"
403  " <input type=\"radio\" id=\"filter-negative\" name=\"radio\" />"
404  "<label for=\"filter-negative\">Negative</label>\n"
405  " <input type=\"radio\" id=\"filter-bw\" name=\"radio\" />"
406  "<label for=\"filter-bw\">Black/White</label>\n"
407  " <input type=\"radio\" id=\"filter-solarize\" name=\"radio\" />"
408  "<label for=\"filter-solarize\">Solarize</label>\n"
409  " <input type=\"radio\" id=\"filter-pastel\" name=\"radio\" />"
410  "<label for=\"filter-pastel\">Pastel</label>\n"
411  " </div>\n"
412  "</form>\n"
413  "<script>\n"
414  "var effect_jqxhr = null;\n"
415  "$(function() {\n"
416  " $('#filter').buttonset();\n"
417  " $('#filter-title').button('option', 'disabled', true );\n"
418  " $('#filter-none').click(function() {\n"
419  " if (effect_jqxhr != null) effect_jqxhr.abort();\n"
420  " effect_jqxhr = $.ajax(\"/ptzcam/effect?set=none\");\n"
421  " });\n"
422  " $('#filter-negative').click(function() {\n"
423  " if (effect_jqxhr != null) effect_jqxhr.abort();\n"
424  " effect_jqxhr = $.ajax(\"/ptzcam/effect?set=negative\");\n"
425  " });\n"
426  " $('#filter-bw').click(function() {\n"
427  " if (effect_jqxhr != null) effect_jqxhr.abort();\n"
428  " effect_jqxhr = $.ajax(\"/ptzcam/effect?set=bw\");\n"
429  " });\n"
430  " $('#filter-solarize').click(function() {\n"
431  " if (effect_jqxhr != null) effect_jqxhr.abort();\n"
432  " effect_jqxhr = $.ajax(\"/ptzcam/effect?set=solarize\");\n"
433  " });\n"
434  " $('#filter-pastel').click(function() {\n"
435  " if (effect_jqxhr != null) effect_jqxhr.abort();\n"
436  " effect_jqxhr = $.ajax(\"/ptzcam/effect?set=pastel\");\n"
437  " });\n"
438  "});\n"
439  "</script>\n";
440 
441  if (! presets_.empty()) {
442  *r += "<br/>\n";
443  for (auto p : presets_) {
444  r->append_body(
445  "<button id=\"preset-%s\" title=\"Look at %s\">%s</button>\n"
446  "<script>\n"
447  "$(function() {\n"
448  " $( \"#preset-%s\" ).button()\n"
449  " .click(function() {\n"
450  " $(this).blur();\n"
451  " if (move_jqxhr != null) move_jqxhr.abort();\n"
452  " move_jqxhr = $.ajax(\"/ptzcam/move?pan=%f&tilt=%f&zoom=%u\");\n"
453  " });\n"
454  "});\n"
455  "</script>\n",
456  p.first.c_str(), std::get<0>(p.second).c_str(),
457  std::get<0>(p.second).c_str(), p.first.c_str(),
458  std::get<1>(p.second), std::get<2>(p.second), std::get<3>(p.second));
459  }
460  }
461 
462  return r;
463  } else {
464  return new WebErrorPageReply(WebReply::HTTP_NOT_FOUND, "Unknown request");
465  }
466  } else {
467  return NULL;
468  }
469 }
virtual fawkes::WebReply * process_request(const fawkes::WebRequest *request)
Process a request.
virtual void set_html_header(std::string h)
Set HTML header text.
Definition: page_reply.cpp:94
virtual ~WebviewPtzCamRequestProcessor()
Destructor.
Fawkes library namespace.
void add_header(std::string header, std::string content)
Add a HTTP header.
Definition: reply.cpp:97
WebviewPtzCamRequestProcessor(std::string base_url, std::string image_id, std::string pantilt_id, std::string camctrl_id, std::string power_id, std::string camera_id, float pan_increment, float tilt_increment, unsigned int zoom_increment, float post_powerup_time, std::map< std::string, std::tuple< std::string, float, float, unsigned int >> presets, fawkes::BlackBoard *blackboard, fawkes::Logger *logger)
Constructor.
CameraControlInterface Fawkes BlackBoard Interface.
void set_effect(const Effect new_effect)
Set effect value.
SwitchInterface Fawkes BlackBoard Interface.
SetEffectMessage Fawkes BlackBoard Interface Message.
Base class for exceptions in Fawkes.
Definition: exception.h:36
std::string get_value(std::string &key) const
Get specific GET value.
Definition: request.h:151
SetZoomMessage Fawkes BlackBoard Interface Message.
Web request meta data carrier.
Definition: request.h:42
GotoMessage Fawkes BlackBoard Interface Message.
Basic page reply.
Definition: page_reply.h:36
Basic web reply.
Definition: reply.h:36
EnableSwitchMessage Fawkes BlackBoard Interface Message.
void append_body(const char *format,...)
Append to body.
Definition: reply.cpp:224
virtual Interface * open_for_reading(const char *interface_type, const char *identifier, const char *owner=NULL)=0
Open interface for reading.
PanTiltInterface Fawkes BlackBoard Interface.
const std::string & url() const
Get URL.
Definition: request.h:69
The BlackBoard abstract class.
Definition: blackboard.h:48
Static error page reply.
Definition: error_reply.h:33
Static web reply.
Definition: reply.h:133
Interface for logging.
Definition: logger.h:34