1 /*
2 * event_server.cpp
3 * PHD Guiding
4 *
5 * Created by Andy Galasso.
6 * Copyright (c) 2013 Andy Galasso.
7 * All rights reserved.
8 *
9 * This source code is distributed under the following "BSD" license
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * Neither the name of Craig Stark, Stark Labs nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 *
33 */
34
35 #include "phd.h"
36
37 #include <wx/sstream.h>
38 #include <wx/sckstrm.h>
39 #include <sstream>
40 #include <string.h>
41
42 EventServer EvtServer;
43
44 BEGIN_EVENT_TABLE(EventServer, wxEvtHandler)
45 EVT_SOCKET(EVENT_SERVER_ID, EventServer::OnEventServerEvent)
46 EVT_SOCKET(EVENT_SERVER_CLIENT_ID, EventServer::OnEventServerClientEvent)
47 END_EVENT_TABLE()
48
49 enum
50 {
51 MSG_PROTOCOL_VERSION = 1,
52 };
53
54 static const wxString literal_null("null");
55 static const wxString literal_true("true");
56 static const wxString literal_false("false");
57
state_name(EXPOSED_STATE st)58 static wxString state_name(EXPOSED_STATE st)
59 {
60 switch (st)
61 {
62 case EXPOSED_STATE_NONE: return "Stopped";
63 case EXPOSED_STATE_SELECTED: return "Selected";
64 case EXPOSED_STATE_CALIBRATING: return "Calibrating";
65 case EXPOSED_STATE_GUIDING_LOCKED: return "Guiding";
66 case EXPOSED_STATE_GUIDING_LOST: return "LostLock";
67 case EXPOSED_STATE_PAUSED: return "Paused";
68 case EXPOSED_STATE_LOOPING: return "Looping";
69 default: return "Unknown";
70 }
71 }
72
json_escape(const wxString & s)73 static wxString json_escape(const wxString& s)
74 {
75 wxString t(s);
76 static const wxString BACKSLASH("\\");
77 static const wxString BACKSLASHBACKSLASH("\\\\");
78 static const wxString DQUOT("\"");
79 static const wxString BACKSLASHDQUOT("\\\"");
80 static const wxString CR("\r");
81 static const wxString BACKSLASHCR("\\r");
82 static const wxString LF("\n");
83 static const wxString BACKSLASHLF("\\n");
84 t.Replace(BACKSLASH, BACKSLASHBACKSLASH);
85 t.Replace(DQUOT, BACKSLASHDQUOT);
86 t.Replace(CR, BACKSLASHCR);
87 t.Replace(LF, BACKSLASHLF);
88 return t;
89 }
90
91 template<char LDELIM, char RDELIM>
92 struct JSeq
93 {
94 wxString m_s;
95 bool m_first;
96 bool m_closed;
JSeqJSeq97 JSeq() : m_first(true), m_closed(false) { m_s << LDELIM; }
closeJSeq98 void close() { m_s << RDELIM; m_closed = true; }
strJSeq99 wxString str() { if (!m_closed) close(); return m_s; }
100 };
101
102 typedef JSeq<'[', ']'> JAry;
103 typedef JSeq<'{', '}'> JObj;
104
operator <<(JAry & a,const wxString & str)105 static JAry& operator<<(JAry& a, const wxString& str)
106 {
107 if (a.m_first)
108 a.m_first = false;
109 else
110 a.m_s << ',';
111 a.m_s << str;
112 return a;
113 }
114
operator <<(JAry & a,double d)115 static JAry& operator<<(JAry& a, double d)
116 {
117 return a << wxString::Format("%.2f", d);
118 }
119
operator <<(JAry & a,int i)120 static JAry& operator<<(JAry& a, int i)
121 {
122 return a << wxString::Format("%d", i);
123 }
124
json_format(const json_value * j)125 static wxString json_format(const json_value *j)
126 {
127 if (!j)
128 return literal_null;
129
130 switch (j->type) {
131 default:
132 case JSON_NULL: return literal_null;
133 case JSON_OBJECT: {
134 wxString ret("{");
135 bool first = true;
136 json_for_each (jj, j)
137 {
138 if (first)
139 first = false;
140 else
141 ret << ",";
142 ret << '"' << jj->name << "\":" << json_format(jj);
143 }
144 ret << "}";
145 return ret;
146 }
147 case JSON_ARRAY: {
148 wxString ret("[");
149 bool first = true;
150 json_for_each (jj, j)
151 {
152 if (first)
153 first = false;
154 else
155 ret << ",";
156 ret << json_format(jj);
157 }
158 ret << "]";
159 return ret;
160 }
161 case JSON_STRING: return '"' + json_escape(j->string_value) + '"';
162 case JSON_INT: return wxString::Format("%d", j->int_value);
163 case JSON_FLOAT: return wxString::Format("%g", (double) j->float_value);
164 case JSON_BOOL: return j->int_value ? literal_true : literal_false;
165 }
166 }
167
168 struct NULL_TYPE { } NULL_VALUE;
169
170 // name-value pair
171 struct NV
172 {
173 wxString n;
174 wxString v;
NVNV175 NV(const wxString& n_, const wxString& v_) : n(n_), v('"' + json_escape(v_) + '"') { }
NVNV176 NV(const wxString& n_, const char *v_) : n(n_), v('"' + json_escape(v_) + '"') { }
NVNV177 NV(const wxString& n_, const wchar_t *v_) : n(n_), v('"' + json_escape(v_) + '"') { }
NVNV178 NV(const wxString& n_, int v_) : n(n_), v(wxString::Format("%d", v_)) { }
NVNV179 NV(const wxString& n_, unsigned int v_) : n(n_), v(wxString::Format("%u", v_)) { }
NVNV180 NV(const wxString& n_, double v_) : n(n_), v(wxString::Format("%g", v_)) { }
NVNV181 NV(const wxString& n_, double v_, int prec) : n(n_), v(wxString::Format("%.*f", prec, v_)) { }
NVNV182 NV(const wxString& n_, bool v_) : n(n_), v(v_ ? literal_true : literal_false) { }
183 template<typename T>
184 NV(const wxString& n_, const std::vector<T>& vec);
NVNV185 NV(const wxString& n_, JAry& ary) : n(n_), v(ary.str()) { }
NVNV186 NV(const wxString& n_, JObj& obj) : n(n_), v(obj.str()) { }
NVNV187 NV(const wxString& n_, const json_value *v_) : n(n_), v(json_format(v_)) { }
NVNV188 NV(const wxString& n_, const PHD_Point& p) : n(n_) { JAry ary; ary << p.X << p.Y; v = ary.str(); }
NVNV189 NV(const wxString& n_, const wxPoint& p) : n(n_) { JAry ary; ary << p.x << p.y; v = ary.str(); }
NVNV190 NV(const wxString& n_, const wxSize& s) : n(n_) { JAry ary; ary << s.x << s.y; v = ary.str(); }
NVNV191 NV(const wxString& n_, const NULL_TYPE& nul) : n(n_), v(literal_null) { }
192 };
193
194 template<typename T>
NV(const wxString & n_,const std::vector<T> & vec)195 NV::NV(const wxString& n_, const std::vector<T>& vec)
196 : n(n_)
197 {
198 std::ostringstream os;
199 os << '[';
200 for (unsigned int i = 0; i < vec.size(); i++)
201 {
202 if (i != 0)
203 os << ',';
204 os << vec[i];
205 }
206 os << ']';
207 v = os.str();
208 }
209
operator <<(JObj & j,const NV & nv)210 static JObj& operator<<(JObj& j, const NV& nv)
211 {
212 if (j.m_first)
213 j.m_first = false;
214 else
215 j.m_s << ',';
216 j.m_s << '"' << nv.n << "\":" << nv.v;
217 return j;
218 }
219
NVMount(const Mount * mount)220 static NV NVMount(const Mount *mount)
221 {
222 return NV("Mount", mount->Name());
223 }
224
operator <<(JObj & j,const PHD_Point & pt)225 static JObj& operator<<(JObj& j, const PHD_Point& pt)
226 {
227 return j << NV("X", pt.X, 3) << NV("Y", pt.Y, 3);
228 }
229
operator <<(JAry & a,JObj & j)230 static JAry& operator<<(JAry& a, JObj& j)
231 {
232 return a << j.str();
233 }
234
235 struct Ev : public JObj
236 {
EvEv237 Ev(const wxString& event)
238 {
239 double const now = ::wxGetUTCTimeMillis().ToDouble() / 1000.0;
240 *this << NV("Event", event)
241 << NV("Timestamp", now, 3)
242 << NV("Host", wxGetHostName())
243 << NV("Inst", wxGetApp().GetInstanceNumber());
244 }
245 };
246
ev_message_version()247 static Ev ev_message_version()
248 {
249 Ev ev("Version");
250 ev << NV("PHDVersion", PHDVERSION)
251 << NV("PHDSubver", PHDSUBVER)
252 << NV("OverlapSupport", true)
253 << NV("MsgVersion", MSG_PROTOCOL_VERSION);
254 return ev;
255 }
256
ev_set_lock_position(const PHD_Point & xy)257 static Ev ev_set_lock_position(const PHD_Point& xy)
258 {
259 Ev ev("LockPositionSet");
260 ev << xy;
261 return ev;
262 }
263
ev_calibration_complete(const Mount * mount)264 static Ev ev_calibration_complete(const Mount *mount)
265 {
266 Ev ev("CalibrationComplete");
267 ev << NVMount(mount);
268
269 if (mount->IsStepGuider())
270 {
271 ev << NV("Limit", mount->GetAoMaxPos());
272 }
273
274 return ev;
275 }
276
ev_star_selected(const PHD_Point & pos)277 static Ev ev_star_selected(const PHD_Point& pos)
278 {
279 Ev ev("StarSelected");
280 ev << pos;
281 return ev;
282 }
283
ev_start_guiding()284 static Ev ev_start_guiding()
285 {
286 return Ev("StartGuiding");
287 }
288
ev_paused()289 static Ev ev_paused()
290 {
291 return Ev("Paused");
292 }
293
ev_start_calibration(const Mount * mount)294 static Ev ev_start_calibration(const Mount *mount)
295 {
296 Ev ev("StartCalibration");
297 ev << NVMount(mount);
298 return ev;
299 }
300
ev_app_state(EXPOSED_STATE st=Guider::GetExposedState ())301 static Ev ev_app_state(EXPOSED_STATE st = Guider::GetExposedState())
302 {
303 Ev ev("AppState");
304 ev << NV("State", state_name(st));
305 return ev;
306 }
307
ev_settling(double distance,double time,double settleTime,bool starLocked)308 static Ev ev_settling(double distance, double time, double settleTime, bool starLocked)
309 {
310 Ev ev("Settling");
311
312 ev << NV("Distance", distance, 2)
313 << NV("Time", time, 1)
314 << NV("SettleTime", settleTime, 1)
315 << NV("StarLocked", starLocked);
316
317 return ev;
318 }
319
ev_settle_done(const wxString & errorMsg,int settleFrames,int droppedFrames)320 static Ev ev_settle_done(const wxString& errorMsg, int settleFrames, int droppedFrames)
321 {
322 Ev ev("SettleDone");
323
324 int status = errorMsg.IsEmpty() ? 0 : 1;
325
326 ev << NV("Status", status);
327
328 if (status != 0)
329 {
330 ev << NV("Error", errorMsg);
331 }
332
333 ev << NV("TotalFrames", settleFrames)
334 << NV("DroppedFrames", droppedFrames);
335
336 return ev;
337 }
338
339 struct ClientReadBuf
340 {
341 enum { SIZE = 1024 };
342 char m_buf[SIZE];
343 char *dest;
344
ClientReadBufClientReadBuf345 ClientReadBuf() { reset(); }
bufClientReadBuf346 char *buf() { return &m_buf[0]; }
lenClientReadBuf347 size_t len() const { return dest - &m_buf[0]; }
availClientReadBuf348 size_t avail() const { return &m_buf[SIZE] - dest; }
resetClientReadBuf349 void reset() { dest = &m_buf[0]; }
350 };
351
352 struct ClientData
353 {
354 wxSocketClient *cli;
355 int refcnt;
356 ClientReadBuf rdbuf;
357 wxMutex wrlock;
358
ClientDataClientData359 ClientData(wxSocketClient *cli_) : cli(cli_), refcnt(1) { }
AddRefClientData360 void AddRef() { ++refcnt; }
RemoveRefClientData361 void RemoveRef()
362 {
363 if (--refcnt == 0)
364 {
365 cli->Destroy();
366 delete this;
367 }
368 }
369 };
370
371 struct ClientDataGuard
372 {
373 ClientData *cd;
ClientDataGuardClientDataGuard374 ClientDataGuard(wxSocketClient *cli) : cd((ClientData *) cli->GetClientData()) { cd->AddRef(); }
~ClientDataGuardClientDataGuard375 ~ClientDataGuard() { cd->RemoveRef(); }
operator ->ClientDataGuard376 ClientData *operator->() const { return cd; }
377 };
378
client_wrlock(wxSocketClient * cli)379 inline static wxMutex *client_wrlock(wxSocketClient *cli)
380 {
381 return &((ClientData *) cli->GetClientData())->wrlock;
382 }
383
SockErrStr(wxSocketError e)384 static wxString SockErrStr(wxSocketError e)
385 {
386 switch (e) {
387 case wxSOCKET_NOERROR: return "";
388 case wxSOCKET_INVOP: return "Invalid operation";
389 case wxSOCKET_IOERR: return "Input / Output error";
390 case wxSOCKET_INVADDR: return "Invalid address";
391 case wxSOCKET_INVSOCK: return "Invalid socket(uninitialized)";
392 case wxSOCKET_NOHOST: return "No corresponding host";
393 case wxSOCKET_INVPORT: return "Invalid port";
394 case wxSOCKET_WOULDBLOCK: return "operation would block";
395 case wxSOCKET_TIMEDOUT: return "timeout expired";
396 case wxSOCKET_MEMERR: return "Memory exhausted";
397 default: return wxString::Format("unknown socket error %d", e);
398 }
399 }
400
send_buf(wxSocketClient * client,const wxCharBuffer & buf)401 static void send_buf(wxSocketClient *client, const wxCharBuffer& buf)
402 {
403 wxMutexLocker lock(*client_wrlock(client));
404 client->Write(buf.data(), buf.length());
405 if (client->LastWriteCount() != buf.length())
406 {
407 Debug.Write(wxString::Format("evsrv: cli %p short write %u/%u %s\n",
408 client, client->LastWriteCount(), (unsigned int) buf.length(),
409 SockErrStr(client->Error() ? client->LastError() : wxSOCKET_NOERROR)));
410 }
411 }
412
do_notify1(wxSocketClient * client,const JAry & ary)413 static void do_notify1(wxSocketClient *client, const JAry& ary)
414 {
415 send_buf(client, (JAry(ary).str() + "\r\n").ToUTF8());
416 }
417
do_notify1(wxSocketClient * client,const JObj & j)418 static void do_notify1(wxSocketClient *client, const JObj& j)
419 {
420 send_buf(client, (JObj(j).str() + "\r\n").ToUTF8());
421 }
422
do_notify(const EventServer::CliSockSet & cli,const JObj & jj)423 static void do_notify(const EventServer::CliSockSet& cli, const JObj& jj)
424 {
425 wxCharBuffer buf = (JObj(jj).str() + "\r\n").ToUTF8();
426
427 for (EventServer::CliSockSet::const_iterator it = cli.begin();
428 it != cli.end(); ++it)
429 {
430 send_buf(*it, buf);
431 }
432 }
433
simple_notify(const EventServer::CliSockSet & cli,const wxString & ev)434 inline static void simple_notify(const EventServer::CliSockSet& cli, const wxString& ev)
435 {
436 if (!cli.empty())
437 do_notify(cli, Ev(ev));
438 }
439
simple_notify_ev(const EventServer::CliSockSet & cli,const Ev & ev)440 inline static void simple_notify_ev(const EventServer::CliSockSet& cli, const Ev& ev)
441 {
442 if (!cli.empty())
443 do_notify(cli, ev);
444 }
445
446 #define SIMPLE_NOTIFY(s) simple_notify(m_eventServerClients, s)
447 #define SIMPLE_NOTIFY_EV(ev) simple_notify_ev(m_eventServerClients, ev)
448
send_catchup_events(wxSocketClient * cli)449 static void send_catchup_events(wxSocketClient *cli)
450 {
451 EXPOSED_STATE st = Guider::GetExposedState();
452
453 do_notify1(cli, ev_message_version());
454
455 if (pFrame->pGuider)
456 {
457 if (pFrame->pGuider->LockPosition().IsValid())
458 do_notify1(cli, ev_set_lock_position(pFrame->pGuider->LockPosition()));
459
460 if (pFrame->pGuider->CurrentPosition().IsValid())
461 do_notify1(cli, ev_star_selected(pFrame->pGuider->CurrentPosition()));
462 }
463
464 if (pMount && pMount->IsCalibrated())
465 do_notify1(cli, ev_calibration_complete(pMount));
466
467 if (pSecondaryMount && pSecondaryMount->IsCalibrated())
468 do_notify1(cli, ev_calibration_complete(pSecondaryMount));
469
470 if (st == EXPOSED_STATE_GUIDING_LOCKED)
471 {
472 do_notify1(cli, ev_start_guiding());
473 }
474 else if (st == EXPOSED_STATE_CALIBRATING)
475 {
476 Mount *mount = pMount;
477 if (pFrame->pGuider->GetState() == STATE_CALIBRATING_SECONDARY)
478 mount = pSecondaryMount;
479 do_notify1(cli, ev_start_calibration(mount));
480 }
481 else if (st == EXPOSED_STATE_PAUSED) {
482 do_notify1(cli, ev_paused());
483 }
484
485 do_notify1(cli, ev_app_state());
486 }
487
destroy_client(wxSocketClient * cli)488 static void destroy_client(wxSocketClient *cli)
489 {
490 ClientData *buf = (ClientData *) cli->GetClientData();
491 buf->RemoveRef();
492 }
493
drain_input(wxSocketInputStream & sis)494 static void drain_input(wxSocketInputStream& sis)
495 {
496 while (sis.CanRead())
497 {
498 char buf[1024];
499 if (sis.Read(buf, sizeof(buf)).LastRead() == 0)
500 break;
501 }
502 }
503
504 enum {
505 JSONRPC_PARSE_ERROR = -32700,
506 JSONRPC_INVALID_REQUEST = -32600,
507 JSONRPC_METHOD_NOT_FOUND = -32601,
508 JSONRPC_INVALID_PARAMS = -32602,
509 JSONRPC_INTERNAL_ERROR = -32603,
510 };
511
jrpc_error(int code,const wxString & msg)512 static NV jrpc_error(int code, const wxString& msg)
513 {
514 JObj err;
515 err << NV("code", code) << NV("message", msg);
516 return NV("error", err);
517 }
518
519 template<typename T>
jrpc_result(const T & t)520 static NV jrpc_result(const T& t)
521 {
522 return NV("result", t);
523 }
524
525 template<typename T>
jrpc_result(T & t)526 static NV jrpc_result(T& t)
527 {
528 return NV("result", t);
529 }
530
jrpc_id(const json_value * id)531 static NV jrpc_id(const json_value *id)
532 {
533 return NV("id", id);
534 }
535
536 struct JRpcResponse : public JObj
537 {
JRpcResponseJRpcResponse538 JRpcResponse() { *this << NV("jsonrpc", "2.0"); }
539 };
540
parser_error(const JsonParser & parser)541 static wxString parser_error(const JsonParser& parser)
542 {
543 return wxString::Format("invalid JSON request: %s on line %d at \"%.12s...\"",
544 parser.ErrorDesc(), parser.ErrorLine(), parser.ErrorPos());
545 }
546
547 static void
parse_request(const json_value * req,const json_value ** pmethod,const json_value ** pparams,const json_value ** pid)548 parse_request(const json_value *req, const json_value **pmethod, const json_value **pparams,
549 const json_value **pid)
550 {
551 *pmethod = *pparams = *pid = 0;
552
553 if (req)
554 {
555 json_for_each (t, req)
556 {
557 if (t->name)
558 {
559 if (t->type == JSON_STRING && strcmp(t->name, "method") == 0)
560 *pmethod = t;
561 else if (strcmp(t->name, "params") == 0)
562 *pparams = t;
563 else if (strcmp(t->name, "id") == 0)
564 *pid = t;
565 }
566 }
567 }
568 }
569
570 // paranoia
571 #define VERIFY_GUIDER(response) do { \
572 if (!pFrame || !pFrame->pGuider) \
573 { \
574 response << jrpc_error(1, "internal error"); \
575 return; \
576 } \
577 } while (0)
578
deselect_star(JObj & response,const json_value * params)579 static void deselect_star(JObj& response, const json_value *params)
580 {
581 VERIFY_GUIDER(response);
582 pFrame->pGuider->Reset(true);
583 response << jrpc_result(0);
584 }
585
get_exposure(JObj & response,const json_value * params)586 static void get_exposure(JObj& response, const json_value *params)
587 {
588 response << jrpc_result(pFrame->RequestedExposureDuration());
589 }
590
get_exposure_durations(JObj & response,const json_value * params)591 static void get_exposure_durations(JObj& response, const json_value *params)
592 {
593 const std::vector<int>& exposure_durations = pFrame->GetExposureDurations();
594 response << jrpc_result(exposure_durations);
595 }
596
get_profiles(JObj & response,const json_value * params)597 static void get_profiles(JObj& response, const json_value *params)
598 {
599 JAry ary;
600 wxArrayString names = pConfig->ProfileNames();
601 for (unsigned int i = 0; i < names.size(); i++)
602 {
603 wxString name = names[i];
604 int id = pConfig->GetProfileId(name);
605 if (id)
606 {
607 JObj t;
608 t << NV("id", id) << NV("name", name);
609 if (id == pConfig->GetCurrentProfileId())
610 t << NV("selected", true);
611 ary << t;
612 }
613 }
614 response << jrpc_result(ary);
615 }
616
617 struct Params
618 {
619 std::map<std::string, const json_value *> dict;
620
InitParams621 void Init(const char *names[], size_t nr_names, const json_value *params)
622 {
623 if (!params)
624 return;
625 if (params->type == JSON_ARRAY)
626 {
627 const json_value *jv = params->first_child;
628 for (size_t i = 0; jv && i < nr_names; i++, jv = jv->next_sibling)
629 {
630 const char *name = names[i];
631 dict.insert(std::make_pair(std::string(name), jv));
632 }
633 }
634 else if (params->type == JSON_OBJECT)
635 {
636 json_for_each(jv, params)
637 {
638 dict.insert(std::make_pair(std::string(jv->name), jv));
639 }
640 }
641 }
ParamsParams642 Params(const char *n1, const json_value *params)
643 {
644 const char *n[] = { n1 };
645 Init(n, 1, params);
646 }
ParamsParams647 Params(const char *n1, const char *n2, const json_value *params)
648 {
649 const char *n[] = { n1, n2 };
650 Init(n, 2, params);
651 }
ParamsParams652 Params(const char *n1, const char *n2, const char *n3, const json_value *params)
653 {
654 const char *n[] = { n1, n2, n3 };
655 Init(n, 3, params);
656 }
ParamsParams657 Params(const char *n1, const char *n2, const char *n3, const char *n4, const json_value *params)
658 {
659 const char *n[] = { n1, n2, n3, n4 };
660 Init(n, 4, params);
661 }
paramParams662 const json_value *param(const std::string& name) const
663 {
664 auto it = dict.find(name);
665 return it == dict.end() ? 0 : it->second;
666 }
667 };
668
set_exposure(JObj & response,const json_value * params)669 static void set_exposure(JObj& response, const json_value *params)
670 {
671 Params p("exposure", params);
672 const json_value *exp = p.param("exposure");
673
674 if (!exp || exp->type != JSON_INT)
675 {
676 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected exposure param");
677 return;
678 }
679
680 bool ok = pFrame->SetExposureDuration(exp->int_value);
681 if (ok)
682 {
683 response << jrpc_result(0);
684 }
685 else
686 {
687 response << jrpc_error(1, "could not set exposure duration");
688 }
689 }
690
get_profile(JObj & response,const json_value * params)691 static void get_profile(JObj& response, const json_value *params)
692 {
693 int id = pConfig->GetCurrentProfileId();
694 wxString name = pConfig->GetCurrentProfile();
695 JObj t;
696 t << NV("id", id) << NV("name", name);
697 response << jrpc_result(t);
698 }
699
devstat(JObj & t,const char * dev,const wxString & name,bool connected)700 inline static void devstat(JObj& t, const char *dev, const wxString& name, bool connected)
701 {
702 JObj o;
703 t << NV(dev, o << NV("name", name) << NV("connected", connected));
704 }
705
get_current_equipment(JObj & response,const json_value * params)706 static void get_current_equipment(JObj& response, const json_value *params)
707 {
708 JObj t;
709
710 if (pCamera)
711 devstat(t, "camera", pCamera->Name, pCamera->Connected);
712
713 Mount *mount = TheScope();
714 if (mount)
715 devstat(t, "mount", mount->Name(), mount->IsConnected());
716
717 Mount *auxMount = pFrame->pGearDialog->AuxScope();
718 if (auxMount)
719 devstat(t, "aux_mount", auxMount->Name(), auxMount->IsConnected());
720
721 Mount *ao = TheAO();
722 if (ao)
723 devstat(t, "AO", ao->Name(), ao->IsConnected());
724
725 Rotator *rotator = pRotator;
726 if (rotator)
727 devstat(t, "rotator", rotator->Name(), rotator->IsConnected());
728
729 response << jrpc_result(t);
730 }
731
all_equipment_connected()732 static bool all_equipment_connected()
733 {
734 return pCamera && pCamera->Connected &&
735 (!pMount || pMount->IsConnected()) &&
736 (!pSecondaryMount || pSecondaryMount->IsConnected());
737 }
738
set_profile(JObj & response,const json_value * params)739 static void set_profile(JObj& response, const json_value *params)
740 {
741 Params p("id", params);
742 const json_value *id = p.param("id");
743 if (!id || id->type != JSON_INT)
744 {
745 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected profile id param");
746 return;
747 }
748
749 VERIFY_GUIDER(response);
750
751 wxString errMsg;
752 bool error = pFrame->pGearDialog->SetProfile(id->int_value, &errMsg);
753
754 if (error)
755 {
756 response << jrpc_error(1, errMsg);
757 }
758 else
759 {
760 response << jrpc_result(0);
761 }
762 }
763
get_connected(JObj & response,const json_value * params)764 static void get_connected(JObj& response, const json_value *params)
765 {
766 response << jrpc_result(all_equipment_connected());
767 }
768
set_connected(JObj & response,const json_value * params)769 static void set_connected(JObj& response, const json_value *params)
770 {
771 Params p("connected", params);
772 const json_value *val = p.param("connected");
773 if (!val || val->type != JSON_BOOL)
774 {
775 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected connected boolean param");
776 return;
777 }
778
779 VERIFY_GUIDER(response);
780
781 wxString errMsg;
782 bool error = val->int_value ? pFrame->pGearDialog->ConnectAll(&errMsg) :
783 pFrame->pGearDialog->DisconnectAll(&errMsg);
784
785 if (error)
786 {
787 response << jrpc_error(1, errMsg);
788 }
789 else
790 {
791 response << jrpc_result(0);
792 }
793 }
794
get_calibrated(JObj & response,const json_value * params)795 static void get_calibrated(JObj& response, const json_value *params)
796 {
797 bool calibrated = pMount && pMount->IsCalibrated() && (!pSecondaryMount || pSecondaryMount->IsCalibrated());
798 response << jrpc_result(calibrated);
799 }
800
float_param(const json_value * v,double * p)801 static bool float_param(const json_value *v, double *p)
802 {
803 if (v->type == JSON_INT)
804 {
805 *p = (double) v->int_value;
806 return true;
807 }
808 else if (v->type == JSON_FLOAT)
809 {
810 *p = v->float_value;
811 return true;
812 }
813
814 return false;
815 }
816
float_param(const char * name,const json_value * v,double * p)817 static bool float_param(const char *name, const json_value *v, double *p)
818 {
819 if (strcmp(name, v->name) != 0)
820 return false;
821
822 return float_param(v, p);
823 }
824
bool_value(const json_value * v)825 inline static bool bool_value(const json_value *v)
826 {
827 return v->int_value ? true : false;
828 }
829
bool_param(const json_value * jv,bool * val)830 static bool bool_param(const json_value *jv, bool *val)
831 {
832 if (jv->type != JSON_BOOL && jv->type != JSON_INT)
833 return false;
834 *val = bool_value(jv);
835 return true;
836 }
837
get_paused(JObj & response,const json_value * params)838 static void get_paused(JObj& response, const json_value *params)
839 {
840 VERIFY_GUIDER(response);
841 response << jrpc_result(pFrame->pGuider->IsPaused());
842 }
843
set_paused(JObj & response,const json_value * params)844 static void set_paused(JObj& response, const json_value *params)
845 {
846 Params p("paused", "type", params);
847 const json_value *jv = p.param("paused");
848
849 bool val;
850 if (!jv || !bool_param(jv, &val))
851 {
852 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected bool param at index 0");
853 return;
854 }
855
856 PauseType pause = PAUSE_NONE;
857
858 if (val)
859 {
860 pause = PAUSE_GUIDING;
861
862 jv = p.param("type");
863 if (jv)
864 {
865 if (jv->type == JSON_STRING)
866 {
867 if (strcmp(jv->string_value, "full") == 0)
868 pause = PAUSE_FULL;
869 }
870 else
871 {
872 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected string param at index 1");
873 return;
874 }
875 }
876 }
877
878 pFrame->SetPaused(pause);
879
880 response << jrpc_result(0);
881 }
882
loop(JObj & response,const json_value * params)883 static void loop(JObj& response, const json_value *params)
884 {
885 bool error = pFrame->StartLooping();
886
887 if (error)
888 response << jrpc_error(1, "could not start looping");
889 else
890 response << jrpc_result(0);
891 }
892
stop_capture(JObj & response,const json_value * params)893 static void stop_capture(JObj& response, const json_value *params)
894 {
895 pFrame->StopCapturing();
896 response << jrpc_result(0);
897 }
898
parse_rect(wxRect * r,const json_value * j)899 static bool parse_rect(wxRect *r, const json_value *j)
900 {
901 if (j->type != JSON_ARRAY)
902 return false;
903
904 int a[4];
905 const json_value *jv = j->first_child;
906 for (int i = 0; i < 4; i++)
907 {
908 if (!jv || jv->type != JSON_INT)
909 return false;
910 a[i] = jv->int_value;
911 jv = jv->next_sibling;
912 }
913 if (jv)
914 return false; // extra value
915
916 r->x = a[0];
917 r->y = a[1];
918 r->width = a[2];
919 r->height = a[3];
920
921 return true;
922 }
923
find_star(JObj & response,const json_value * params)924 static void find_star(JObj& response, const json_value *params)
925 {
926 VERIFY_GUIDER(response);
927
928 Params p("roi", params);
929
930 wxRect roi;
931 const json_value *j = p.param("roi");
932 if (j && !parse_rect(&roi, j))
933 {
934 response << jrpc_error(JSONRPC_INVALID_PARAMS, "invalid ROI param");
935 return;
936 }
937
938 bool error = pFrame->AutoSelectStar(roi);
939
940 if (!error)
941 {
942 const PHD_Point& lockPos = pFrame->pGuider->LockPosition();
943 if (lockPos.IsValid())
944 {
945 response << jrpc_result(lockPos);
946 return;
947 }
948 }
949
950 response << jrpc_error(1, "could not find star");
951 }
952
get_pixel_scale(JObj & response,const json_value * params)953 static void get_pixel_scale(JObj& response, const json_value *params)
954 {
955 double scale = pFrame->GetCameraPixelScale();
956 if (scale == 1.0)
957 response << jrpc_result(NULL_VALUE); // scale unknown
958 else
959 response << jrpc_result(scale);
960 }
961
get_app_state(JObj & response,const json_value * params)962 static void get_app_state(JObj& response, const json_value *params)
963 {
964 EXPOSED_STATE st = Guider::GetExposedState();
965 response << jrpc_result(state_name(st));
966 }
967
get_lock_position(JObj & response,const json_value * params)968 static void get_lock_position(JObj& response, const json_value *params)
969 {
970 VERIFY_GUIDER(response);
971
972 const PHD_Point& lockPos = pFrame->pGuider->LockPosition();
973 if (lockPos.IsValid())
974 response << jrpc_result(lockPos);
975 else
976 response << jrpc_result(NULL_VALUE);
977 }
978
979 // {"method": "set_lock_position", "params": [X, Y, true], "id": 1}
set_lock_position(JObj & response,const json_value * params)980 static void set_lock_position(JObj& response, const json_value *params)
981 {
982 Params p("x", "y", "exact", params);
983 const json_value *p0 = p.param("x"), *p1 = p.param("y");
984 double x, y;
985
986 if (!p0 || !p1 || !float_param(p0, &x) || !float_param(p1, &y))
987 {
988 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected lock position x, y params");
989 return;
990 }
991
992 bool exact = true;
993 const json_value *p2 = p.param("exact");
994
995 if (p2)
996 {
997 if (!bool_param(p2, &exact))
998 {
999 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected boolean param at index 2");
1000 return;
1001 }
1002 }
1003
1004 VERIFY_GUIDER(response);
1005
1006 bool error;
1007
1008 if (exact)
1009 error = pFrame->pGuider->SetLockPosition(PHD_Point(x, y));
1010 else
1011 error = pFrame->pGuider->SetLockPosToStarAtPosition(PHD_Point(x, y));
1012
1013 if (error)
1014 {
1015 response << jrpc_error(JSONRPC_INVALID_REQUEST, "could not set lock position");
1016 return;
1017 }
1018
1019 response << jrpc_result(0);
1020 }
1021
string_val(const json_value * j)1022 inline static const char *string_val(const json_value *j)
1023 {
1024 return j->type == JSON_STRING ? j->string_value : "";
1025 }
1026
1027 enum WHICH_MOUNT
1028 {
1029 MOUNT, AO, WHICH_MOUNT_BOTH, WHICH_MOUNT_ERR
1030 };
1031
which_mount(const json_value * p)1032 static WHICH_MOUNT which_mount(const json_value *p)
1033 {
1034 WHICH_MOUNT r = MOUNT;
1035 if (p)
1036 {
1037 r = WHICH_MOUNT_ERR;
1038 if (p->type == JSON_STRING)
1039 {
1040 if (wxStricmp(p->string_value, "ao") == 0)
1041 r = AO;
1042 else if (wxStricmp(p->string_value, "mount") == 0)
1043 r = MOUNT;
1044 else if (wxStricmp(p->string_value, "both") == 0)
1045 r = WHICH_MOUNT_BOTH;
1046 }
1047 }
1048 return r;
1049 }
1050
clear_calibration(JObj & response,const json_value * params)1051 static void clear_calibration(JObj& response, const json_value *params)
1052 {
1053 bool clear_mount;
1054 bool clear_ao;
1055
1056 if (!params)
1057 {
1058 clear_mount = clear_ao = true;
1059 }
1060 else
1061 {
1062 Params p("which", params);
1063
1064 clear_mount = clear_ao = false;
1065
1066 WHICH_MOUNT which = which_mount(p.param("which"));
1067 switch (which)
1068 {
1069 case MOUNT: clear_mount = true; break;
1070 case AO: clear_ao = true; break;
1071 case WHICH_MOUNT_BOTH: clear_mount = clear_ao = true; break;
1072 case WHICH_MOUNT_ERR:
1073 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected param \"mount\", \"ao\", or \"both\"");
1074 return;
1075 }
1076 }
1077
1078 Mount *mount = TheScope();
1079 Mount *ao = TheAO();
1080
1081 if (mount && clear_mount)
1082 mount->ClearCalibration();
1083
1084 if (ao && clear_ao)
1085 ao->ClearCalibration();
1086
1087 response << jrpc_result(0);
1088 }
1089
flip_calibration(JObj & response,const json_value * params)1090 static void flip_calibration(JObj& response, const json_value *params)
1091 {
1092 bool error = pFrame->FlipCalibrationData();
1093
1094 if (error)
1095 response << jrpc_error(1, "could not flip calibration");
1096 else
1097 response << jrpc_result(0);
1098 }
1099
get_lock_shift_enabled(JObj & response,const json_value * params)1100 static void get_lock_shift_enabled(JObj& response, const json_value *params)
1101 {
1102 VERIFY_GUIDER(response);
1103 bool enabled = pFrame->pGuider->GetLockPosShiftParams().shiftEnabled;
1104 response << jrpc_result(enabled);
1105 }
1106
set_lock_shift_enabled(JObj & response,const json_value * params)1107 static void set_lock_shift_enabled(JObj& response, const json_value *params)
1108 {
1109 Params p("enabled", params);
1110 const json_value *val = p.param("enabled");
1111 bool enable;
1112 if (!val || !bool_param(val, &enable))
1113 {
1114 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected enabled boolean param");
1115 return;
1116 }
1117
1118 VERIFY_GUIDER(response);
1119
1120 pFrame->pGuider->EnableLockPosShift(enable);
1121
1122 response << jrpc_result(0);
1123 }
1124
is_camera_shift_req(const json_value * params)1125 static bool is_camera_shift_req(const json_value *params)
1126 {
1127 Params p("axes", params);
1128 const json_value *j = p.param("axes");
1129 if (j)
1130 {
1131 const char *axes = string_val(j);
1132 if (wxStricmp(axes, "x/y") == 0 ||
1133 wxStricmp(axes, "camera") == 0)
1134 {
1135 return true;
1136 }
1137 }
1138 return false;
1139 }
1140
operator <<(JObj & j,const LockPosShiftParams & l)1141 static JObj& operator<<(JObj& j, const LockPosShiftParams& l)
1142 {
1143 j << NV("enabled", l.shiftEnabled);
1144 if (l.shiftRate.IsValid())
1145 {
1146 j << NV("rate", l.shiftRate)
1147 << NV("units", l.shiftUnits == UNIT_ARCSEC ? "arcsec/hr" : "pixels/hr")
1148 << NV("axes", l.shiftIsMountCoords ? "RA/Dec" : "X/Y");
1149 }
1150 return j;
1151 }
1152
get_lock_shift_params(JObj & response,const json_value * params)1153 static void get_lock_shift_params(JObj& response, const json_value *params)
1154 {
1155 VERIFY_GUIDER(response);
1156
1157 const LockPosShiftParams& lockShift = pFrame->pGuider->GetLockPosShiftParams();
1158 JObj rslt;
1159
1160 if (is_camera_shift_req(params))
1161 {
1162 LockPosShiftParams tmp;
1163 tmp.shiftEnabled = lockShift.shiftEnabled;
1164 const ShiftPoint& lock = pFrame->pGuider->LockPosition();
1165 tmp.shiftRate = lock.ShiftRate() * 3600; // px/sec => px/hr
1166 tmp.shiftUnits = UNIT_PIXELS;
1167 tmp.shiftIsMountCoords = false;
1168 rslt << tmp;
1169 }
1170 else
1171 rslt << lockShift;
1172
1173 response << jrpc_result(rslt);
1174 }
1175
get_double(double * d,const json_value * j)1176 static bool get_double(double *d, const json_value *j)
1177 {
1178 if (j->type == JSON_FLOAT)
1179 {
1180 *d = j->float_value;
1181 return true;
1182 }
1183 else if (j->type == JSON_INT)
1184 {
1185 *d = j->int_value;
1186 return true;
1187 }
1188 return false;
1189 }
1190
parse_point(PHD_Point * pt,const json_value * j)1191 static bool parse_point(PHD_Point *pt, const json_value *j)
1192 {
1193 if (j->type != JSON_ARRAY)
1194 return false;
1195 const json_value *jx = j->first_child;
1196 if (!jx)
1197 return false;
1198 const json_value *jy = jx->next_sibling;
1199 if (!jy || jy->next_sibling)
1200 return false;
1201 double x, y;
1202 if (!get_double(&x, jx) || !get_double(&y, jy))
1203 return false;
1204 pt->SetXY(x, y);
1205 return true;
1206 }
1207
parse_lock_shift_params(LockPosShiftParams * shift,const json_value * params,wxString * error)1208 static bool parse_lock_shift_params(LockPosShiftParams *shift, const json_value *params, wxString *error)
1209 {
1210 // "params":[{"rate":[3.3,1.1],"units":"arcsec/hr","axes":"RA/Dec"}]
1211 // or
1212 // "params":{"rate":[3.3,1.1],"units":"arcsec/hr","axes":"RA/Dec"}
1213
1214 if (params && params->type == JSON_ARRAY)
1215 params = params->first_child;
1216
1217 Params p("rate", "units", "axes", params);
1218
1219 shift->shiftUnits = UNIT_ARCSEC;
1220 shift->shiftIsMountCoords = true;
1221
1222 const json_value *j;
1223
1224 j = p.param("rate");
1225 if (!j || !parse_point(&shift->shiftRate, j))
1226 {
1227 *error = "expected rate value array";
1228 return false;
1229 }
1230
1231 j = p.param("units");
1232 const char *units = j ? string_val(j) : "";
1233
1234 if (wxStricmp(units, "arcsec/hr") == 0 ||
1235 wxStricmp(units, "arc-sec/hr") == 0)
1236 {
1237 shift->shiftUnits = UNIT_ARCSEC;
1238 }
1239 else if (wxStricmp(units, "pixels/hr") == 0)
1240 {
1241 shift->shiftUnits = UNIT_PIXELS;
1242 }
1243 else
1244 {
1245 *error = "expected units 'arcsec/hr' or 'pixels/hr'";
1246 return false;
1247 }
1248
1249 j = p.param("axes");
1250 const char *axes = j ? string_val(j) : "";
1251
1252 if (wxStricmp(axes, "RA/Dec") == 0)
1253 {
1254 shift->shiftIsMountCoords = true;
1255 }
1256 else if (wxStricmp(axes, "X/Y") == 0)
1257 {
1258 shift->shiftIsMountCoords = false;
1259 }
1260 else
1261 {
1262 *error = "expected axes 'RA/Dec' or 'X/Y'";
1263 return false;
1264 }
1265
1266 return true;
1267 }
1268
set_lock_shift_params(JObj & response,const json_value * params)1269 static void set_lock_shift_params(JObj& response, const json_value *params)
1270 {
1271 wxString err;
1272 LockPosShiftParams shift;
1273 if (!parse_lock_shift_params(&shift, params, &err))
1274 {
1275 response << jrpc_error(JSONRPC_INVALID_PARAMS, err);
1276 return;
1277 }
1278
1279 VERIFY_GUIDER(response);
1280
1281 pFrame->pGuider->SetLockPosShiftRate(shift.shiftRate, shift.shiftUnits, shift.shiftIsMountCoords, true);
1282
1283 response << jrpc_result(0);
1284 }
1285
save_image(JObj & response,const json_value * params)1286 static void save_image(JObj& response, const json_value *params)
1287 {
1288 VERIFY_GUIDER(response);
1289
1290 if (!pFrame->pGuider->CurrentImage()->ImageData)
1291 {
1292 response << jrpc_error(2, "no image available");
1293 return;
1294 }
1295
1296 wxString fname = wxFileName::CreateTempFileName(MyFrame::GetDefaultFileDir() + PATHSEPSTR + "save_image_");
1297
1298 if (pFrame->pGuider->SaveCurrentImage(fname))
1299 {
1300 ::wxRemove(fname);
1301 response << jrpc_error(3, "error saving image");
1302 return;
1303 }
1304
1305 JObj rslt;
1306 rslt << NV("filename", fname);
1307 response << jrpc_result(rslt);
1308 }
1309
capture_single_frame(JObj & response,const json_value * params)1310 static void capture_single_frame(JObj& response, const json_value *params)
1311 {
1312 if (pFrame->CaptureActive)
1313 {
1314 response << jrpc_error(1, "cannot capture single frame when capture is currently active");
1315 return;
1316 }
1317
1318 Params p("exposure", "subframe", params);
1319 const json_value *j = p.param("exposure");
1320 int exposure;
1321 if (j)
1322 {
1323 if (j->type != JSON_INT || j->int_value < 1 || j->int_value > 10 * 60000)
1324 {
1325 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected exposure param");
1326 return;
1327 }
1328 exposure = j->int_value;
1329 }
1330 else
1331 {
1332 exposure = pFrame->RequestedExposureDuration();
1333 }
1334
1335 wxRect subframe;
1336
1337 if ((j = p.param("subframe")) != nullptr)
1338 if (!parse_rect(&subframe, j))
1339 {
1340 response << jrpc_error(JSONRPC_INVALID_PARAMS, "invalid subframe param");
1341 return;
1342 }
1343
1344 bool err = pFrame->StartSingleExposure(exposure, subframe);
1345 if (err)
1346 {
1347 response << jrpc_error(2, "failed to start exposure");
1348 return;
1349 }
1350
1351 response << jrpc_result(0);
1352 }
1353
get_use_subframes(JObj & response,const json_value * params)1354 static void get_use_subframes(JObj& response, const json_value *params)
1355 {
1356 response << jrpc_result(pCamera && pCamera->UseSubframes);
1357 }
1358
get_search_region(JObj & response,const json_value * params)1359 static void get_search_region(JObj& response, const json_value *params)
1360 {
1361 VERIFY_GUIDER(response);
1362 response << jrpc_result(pFrame->pGuider->GetSearchRegion());
1363 }
1364
1365 struct B64Encode
1366 {
1367 static const char *const E;
1368 std::ostringstream os;
1369 unsigned int t;
1370 size_t nread;
1371
B64EncodeB64Encode1372 B64Encode()
1373 : t(0), nread(0)
1374 {
1375 }
append1B64Encode1376 void append1(unsigned char ch)
1377 {
1378 t <<= 8;
1379 t |= ch;
1380 if (++nread % 3 == 0)
1381 {
1382 os << E[t >> 18]
1383 << E[(t >> 12) & 0x3F]
1384 << E[(t >> 6) & 0x3F]
1385 << E[t & 0x3F];
1386 t = 0;
1387 }
1388 }
appendB64Encode1389 void append(const void *src_, size_t len)
1390 {
1391 const unsigned char *src = (const unsigned char *) src_;
1392 const unsigned char *const end = src + len;
1393 while (src < end)
1394 append1(*src++);
1395 }
finishB64Encode1396 std::string finish()
1397 {
1398 switch (nread % 3) {
1399 case 1:
1400 os << E[t >> 2]
1401 << E[(t & 0x3) << 4]
1402 << "==";
1403 break;
1404 case 2:
1405 os << E[t >> 10]
1406 << E[(t >> 4) & 0x3F]
1407 << E[(t & 0xf) << 2]
1408 << '=';
1409 break;
1410 }
1411 os << std::ends;
1412 return os.str();
1413 }
1414 };
1415 const char *const B64Encode::E = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1416
get_star_image(JObj & response,const json_value * params)1417 static void get_star_image(JObj& response, const json_value *params)
1418 {
1419 int reqsize = 15;
1420 Params p("size", params);
1421 const json_value *val = p.param("size");
1422 if (val)
1423 {
1424 if (val->type != JSON_INT || (reqsize = val->int_value) < 15)
1425 {
1426 response << jrpc_error(JSONRPC_INVALID_PARAMS, "invalid image size param");
1427 return;
1428 }
1429 }
1430
1431 VERIFY_GUIDER(response);
1432
1433 Guider *guider = pFrame->pGuider;
1434 const usImage *img = guider->CurrentImage();
1435 const PHD_Point& star = guider->CurrentPosition();
1436
1437 if (guider->GetState() < GUIDER_STATE::STATE_SELECTED || !img->ImageData || !star.IsValid())
1438 {
1439 response << jrpc_error(2, "no star selected");
1440 return;
1441 }
1442
1443 int const halfw = wxMin((reqsize - 1) / 2, 31);
1444 int const fullw = 2 * halfw + 1;
1445 int const sx = (int) rint(star.X);
1446 int const sy = (int) rint(star.Y);
1447 wxRect rect(sx - halfw, sy - halfw, fullw, fullw);
1448 if (img->Subframe.IsEmpty())
1449 rect.Intersect(wxRect(img->Size));
1450 else
1451 rect.Intersect(img->Subframe);
1452
1453 B64Encode enc;
1454 for (int y = rect.GetTop(); y <= rect.GetBottom(); y++)
1455 {
1456 const unsigned short *p = img->ImageData + y * img->Size.GetWidth() + rect.GetLeft();
1457 enc.append(p, rect.GetWidth() * sizeof(unsigned short));
1458 }
1459
1460 PHD_Point pos(star);
1461 pos.X -= rect.GetLeft();
1462 pos.Y -= rect.GetTop();
1463
1464 JObj rslt;
1465 rslt << NV("frame", img->FrameNum)
1466 << NV("width", rect.GetWidth())
1467 << NV("height", rect.GetHeight())
1468 << NV("star_pos", pos)
1469 << NV("pixels", enc.finish());
1470
1471 response << jrpc_result(rslt);
1472 }
1473
parse_settle(SettleParams * settle,const json_value * j,wxString * error)1474 static bool parse_settle(SettleParams *settle, const json_value *j, wxString *error)
1475 {
1476 bool found_pixels = false, found_time = false, found_timeout = false;
1477
1478 json_for_each (t, j)
1479 {
1480 if (float_param("pixels", t, &settle->tolerancePx))
1481 {
1482 found_pixels = true;
1483 continue;
1484 }
1485 double d;
1486 if (float_param("time", t, &d))
1487 {
1488 settle->settleTimeSec = (int) floor(d);
1489 found_time = true;
1490 continue;
1491 }
1492 if (float_param("timeout", t, &d))
1493 {
1494 settle->timeoutSec = (int) floor(d);
1495 found_timeout = true;
1496 continue;
1497 }
1498 }
1499
1500 settle->frames = 99999;
1501
1502 bool ok = found_pixels && found_time && found_timeout;
1503 if (!ok)
1504 *error = "invalid settle params";
1505
1506 return ok;
1507 }
1508
guide(JObj & response,const json_value * params)1509 static void guide(JObj& response, const json_value *params)
1510 {
1511 // params:
1512 // settle [object]:
1513 // pixels [float]
1514 // arcsecs [float]
1515 // frames [integer]
1516 // time [integer]
1517 // timeout [integer]
1518 // recalibrate: boolean
1519 //
1520 // {"method": "guide", "params": [{"pixels": 0.5, "time": 6, "timeout": 30}, false], "id": 42}
1521 // or
1522 // {"method": "guide", "params": {"settle": {"pixels": 0.5, "time": 6, "timeout": 30}, "recalibrate": false}, "id": 42}
1523 //
1524 // todo:
1525 // accept tolerance in arcsec or pixels
1526 // accept settle time in seconds or frames
1527
1528 SettleParams settle;
1529
1530 Params p("settle", "recalibrate", "roi", params);
1531 const json_value *p0 = p.param("settle");
1532 if (!p0 || p0->type != JSON_OBJECT)
1533 {
1534 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected settle object param");
1535 return;
1536 }
1537 wxString errMsg;
1538 if (!parse_settle(&settle, p0, &errMsg))
1539 {
1540 response << jrpc_error(JSONRPC_INVALID_PARAMS, errMsg);
1541 return;
1542 }
1543
1544 bool recalibrate = false;
1545 const json_value *p1 = p.param("recalibrate");
1546 if (p1)
1547 {
1548 if (!bool_param(p1, &recalibrate))
1549 {
1550 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected bool value for recalibrate");
1551 return;
1552 }
1553 }
1554
1555 wxRect roi;
1556 const json_value *p2 = p.param("roi");
1557 if (p2 && !parse_rect(&roi, p2))
1558 {
1559 response << jrpc_error(JSONRPC_INVALID_PARAMS, "invalid ROI param");
1560 return;
1561 }
1562
1563 if (recalibrate && !pConfig->Global.GetBoolean("/server/guide_allow_recalibrate", true))
1564 {
1565 Debug.AddLine("ignoring client recalibration request since guide_allow_recalibrate = false");
1566 recalibrate = false;
1567 }
1568
1569 wxString err;
1570
1571 if (!PhdController::CanGuide(&err))
1572 response << jrpc_error(1, err);
1573 else if (PhdController::Guide(recalibrate, settle, roi, &err))
1574 response << jrpc_result(0);
1575 else
1576 response << jrpc_error(1, err);
1577 }
1578
dither(JObj & response,const json_value * params)1579 static void dither(JObj& response, const json_value *params)
1580 {
1581 // params:
1582 // amount [integer] - max pixels to move in each axis
1583 // raOnly [bool] - when true, only dither ra
1584 // settle [object]:
1585 // pixels [float]
1586 // arcsecs [float]
1587 // frames [integer]
1588 // time [integer]
1589 // timeout [integer]
1590 //
1591 // {"method": "dither", "params": [10, false, {"pixels": 1.5, "time": 8, "timeout": 30}], "id": 42}
1592 // or
1593 // {"method": "dither", "params": {"amount": 10, "raOnly": false, "settle": {"pixels": 1.5, "time": 8, "timeout": 30}}, "id": 42}
1594
1595 Params p("amount", "raOnly", "settle", params);
1596 const json_value *jv;
1597 double ditherAmt;
1598
1599 jv = p.param("amount");
1600 if (!jv || !float_param(jv, &ditherAmt))
1601 {
1602 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected dither amount param");
1603 return;
1604 }
1605
1606 bool raOnly = false;
1607 jv = p.param("raOnly");
1608 if (jv)
1609 {
1610 if (!bool_param(jv, &raOnly))
1611 {
1612 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected dither raOnly param");
1613 return;
1614 }
1615 }
1616
1617 SettleParams settle;
1618
1619 jv = p.param("settle");
1620 if (!jv || jv->type != JSON_OBJECT)
1621 {
1622 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected settle object param");
1623 return;
1624 }
1625 wxString errMsg;
1626 if (!parse_settle(&settle, jv, &errMsg))
1627 {
1628 response << jrpc_error(JSONRPC_INVALID_PARAMS, errMsg);
1629 return;
1630 }
1631
1632 wxString error;
1633 if (PhdController::Dither(fabs(ditherAmt), raOnly, settle, &error))
1634 response << jrpc_result(0);
1635 else
1636 response << jrpc_error(1, error);
1637 }
1638
shutdown(JObj & response,const json_value * params)1639 static void shutdown(JObj& response, const json_value *params)
1640 {
1641 wxGetApp().TerminateApp();
1642
1643 response << jrpc_result(0);
1644 }
1645
get_camera_binning(JObj & response,const json_value * params)1646 static void get_camera_binning(JObj& response, const json_value *params)
1647 {
1648 if (pCamera && pCamera->Connected)
1649 {
1650 int binning = pCamera->Binning;
1651 response << jrpc_result(binning);
1652 }
1653 else
1654 response << jrpc_error(1, "camera not connected");
1655 }
1656
get_camera_frame_size(JObj & response,const json_value * params)1657 static void get_camera_frame_size(JObj& response, const json_value *params)
1658 {
1659 if (pCamera && pCamera->Connected)
1660 {
1661 response << jrpc_result(pCamera->FullSize);
1662 }
1663 else
1664 response << jrpc_error(1, "camera not connected");
1665 }
1666
get_guide_output_enabled(JObj & response,const json_value * params)1667 static void get_guide_output_enabled(JObj& response, const json_value *params)
1668 {
1669 if (pMount)
1670 response << jrpc_result(pMount->GetGuidingEnabled());
1671 else
1672 response << jrpc_error(1, "mount not defined");
1673 }
1674
set_guide_output_enabled(JObj & response,const json_value * params)1675 static void set_guide_output_enabled(JObj& response, const json_value *params)
1676 {
1677 Params p("enabled", params);
1678 const json_value *val = p.param("enabled");
1679 bool enable;
1680 if (!val || !bool_param(val, &enable))
1681 {
1682 response << jrpc_error(JSONRPC_INVALID_PARAMS, "expected enabled boolean param");
1683 return;
1684 }
1685
1686 if (pMount)
1687 {
1688 pMount->SetGuidingEnabled(enable);
1689 response << jrpc_result(0);
1690 }
1691 else
1692 response << jrpc_error(1, "mount not defined");
1693 }
1694
axis_param(const Params & p,GuideAxis * a)1695 static bool axis_param(const Params& p, GuideAxis *a)
1696 {
1697 const json_value *val = p.param("axis");
1698 if (!val || val->type != JSON_STRING)
1699 return false;
1700
1701 bool ok = true;
1702
1703 if (wxStricmp(val->string_value, "ra") == 0)
1704 *a = GUIDE_RA;
1705 else if (wxStricmp(val->string_value, "x") == 0)
1706 *a = GUIDE_X;
1707 else if (wxStricmp(val->string_value, "dec") == 0)
1708 *a = GUIDE_DEC;
1709 else if (wxStricmp(val->string_value, "y") == 0)
1710 *a = GUIDE_Y;
1711 else
1712 ok = false;
1713
1714 return ok;
1715 }
1716
get_algo_param_names(JObj & response,const json_value * params)1717 static void get_algo_param_names(JObj& response, const json_value *params)
1718 {
1719 Params p("axis", params);
1720 GuideAxis a;
1721 if (!axis_param(p, &a))
1722 {
1723 response << jrpc_error(1, "expected axis name param");
1724 return;
1725 }
1726 wxArrayString ary;
1727 ary.push_back("algorithmName");
1728
1729 if (pMount)
1730 {
1731 GuideAlgorithm *alg = a == GUIDE_X ? pMount->GetXGuideAlgorithm() : pMount->GetYGuideAlgorithm();
1732 alg->GetParamNames(ary);
1733 }
1734
1735 JAry names;
1736 for (auto it = ary.begin(); it != ary.end(); ++it)
1737 names << ('"' + json_escape(*it) + '"');
1738
1739 response << jrpc_result(names);
1740 }
1741
get_algo_param(JObj & response,const json_value * params)1742 static void get_algo_param(JObj& response, const json_value *params)
1743 {
1744 Params p("axis", "name", params);
1745 GuideAxis a;
1746 if (!axis_param(p, &a))
1747 {
1748 response << jrpc_error(1, "expected axis name param");
1749 return;
1750 }
1751 const json_value *name = p.param("name");
1752 if (!name || name->type != JSON_STRING)
1753 {
1754 response << jrpc_error(1, "expected param name param");
1755 return;
1756 }
1757 bool ok = false;
1758 double val;
1759 if (pMount)
1760 {
1761 GuideAlgorithm *alg = a == GUIDE_X ? pMount->GetXGuideAlgorithm() : pMount->GetYGuideAlgorithm();
1762 if (strcmp(name->string_value, "algorithmName") == 0)
1763 {
1764 response << jrpc_result(alg->GetGuideAlgorithmClassName());
1765 return;
1766 }
1767 ok = alg->GetParam(name->string_value, &val);
1768 }
1769 if (ok)
1770 response << jrpc_result(val);
1771 else
1772 response << jrpc_error(1, "could not get param");
1773 }
1774
set_algo_param(JObj & response,const json_value * params)1775 static void set_algo_param(JObj& response, const json_value *params)
1776 {
1777 Params p("axis", "name", "value", params);
1778 GuideAxis a;
1779 if (!axis_param(p, &a))
1780 {
1781 response << jrpc_error(1, "expected axis name param");
1782 return;
1783 }
1784 const json_value *name = p.param("name");
1785 if (!name || name->type != JSON_STRING)
1786 {
1787 response << jrpc_error(1, "expected param name param");
1788 return;
1789 }
1790 const json_value *val = p.param("value");
1791 double v;
1792 if (!float_param(val, &v))
1793 {
1794 response << jrpc_error(1, "expected param value param");
1795 return;
1796 }
1797 bool ok = false;
1798 if (pMount)
1799 {
1800 GuideAlgorithm *alg = a == GUIDE_X ? pMount->GetXGuideAlgorithm() : pMount->GetYGuideAlgorithm();
1801 ok = alg->SetParam(name->string_value, v);
1802 }
1803 if (ok)
1804 {
1805 response << jrpc_result(0);
1806 if (pFrame->pGraphLog)
1807 pFrame->pGraphLog->UpdateControls();
1808 }
1809 else
1810 response << jrpc_error(1, "could not set param");
1811 }
1812
get_dec_guide_mode(JObj & response,const json_value * params)1813 static void get_dec_guide_mode(JObj& response, const json_value *params)
1814 {
1815 Scope *scope = TheScope();
1816 DEC_GUIDE_MODE mode = scope ? scope->GetDecGuideMode() : DEC_NONE;
1817 wxString s = Scope::DecGuideModeStr(mode);
1818 response << jrpc_result(s);
1819 }
1820
set_dec_guide_mode(JObj & response,const json_value * params)1821 static void set_dec_guide_mode(JObj& response, const json_value *params)
1822 {
1823 Params p("mode", params);
1824 const json_value *mode = p.param("mode");
1825 if (!mode || mode->type != JSON_STRING)
1826 {
1827 response << jrpc_error(1, "expected mode param");
1828 return;
1829 }
1830 DEC_GUIDE_MODE m = DEC_AUTO;
1831 bool found = false;
1832 for (int im = DEC_NONE; im <= DEC_SOUTH; im++)
1833 {
1834 m = (DEC_GUIDE_MODE) im;
1835 if (wxStricmp(mode->string_value, Scope::DecGuideModeStr(m)) == 0)
1836 {
1837 found = true;
1838 break;
1839 }
1840 }
1841 if (!found)
1842 {
1843 response << jrpc_error(1, "invalid dec guide mode param");
1844 return;
1845 }
1846
1847 Scope *scope = TheScope();
1848 if (scope)
1849 scope->SetDecGuideMode(m);
1850
1851 if (pFrame->pGraphLog)
1852 pFrame->pGraphLog->UpdateControls();
1853
1854 response << jrpc_result(0);
1855 }
1856
get_settling(JObj & response,const json_value * params)1857 static void get_settling(JObj& response, const json_value *params)
1858 {
1859 bool settling = PhdController::IsSettling();
1860 response << jrpc_result(settling);
1861 }
1862
dir_param(const json_value * p)1863 static GUIDE_DIRECTION dir_param(const json_value *p)
1864 {
1865 if (!p || p->type != JSON_STRING)
1866 return GUIDE_DIRECTION::NONE;
1867
1868 struct {
1869 const char *s; GUIDE_DIRECTION d;
1870 } dirs[] = {
1871 { "n", GUIDE_DIRECTION::NORTH },
1872 { "s", GUIDE_DIRECTION::SOUTH },
1873 { "e", GUIDE_DIRECTION::EAST },
1874 { "w", GUIDE_DIRECTION::WEST },
1875 { "north", GUIDE_DIRECTION::NORTH },
1876 { "south", GUIDE_DIRECTION::SOUTH },
1877 { "east", GUIDE_DIRECTION::EAST },
1878 { "west", GUIDE_DIRECTION::WEST },
1879 { "up", GUIDE_DIRECTION::UP },
1880 { "down", GUIDE_DIRECTION::DOWN },
1881 { "left", GUIDE_DIRECTION::LEFT },
1882 { "right", GUIDE_DIRECTION::RIGHT },
1883 };
1884
1885 for (unsigned int i = 0; i < WXSIZEOF(dirs); i++)
1886 if (wxStricmp(p->string_value, dirs[i].s) == 0)
1887 return dirs[i].d;
1888
1889 return GUIDE_DIRECTION::NONE;
1890 }
1891
opposite(GUIDE_DIRECTION d)1892 static GUIDE_DIRECTION opposite(GUIDE_DIRECTION d)
1893 {
1894 switch (d) {
1895 case UP: return DOWN;
1896 case DOWN: return UP;
1897 case LEFT: return RIGHT;
1898 case RIGHT: return LEFT;
1899 default: return d;
1900 }
1901 }
1902
guide_pulse(JObj & response,const json_value * params)1903 static void guide_pulse(JObj& response, const json_value *params)
1904 {
1905 Params p("amount", "direction", "which", params);
1906
1907 const json_value *amount = p.param("amount");
1908 if (!amount || amount->type != JSON_INT)
1909 {
1910 response << jrpc_error(1, "expected amount param");
1911 return;
1912 }
1913
1914 GUIDE_DIRECTION dir = dir_param(p.param("direction"));
1915 if (dir == GUIDE_DIRECTION::NONE)
1916 {
1917 response << jrpc_error(1, "expected direction param");
1918 return;
1919 }
1920
1921 WHICH_MOUNT which = which_mount(p.param("which"));
1922 Mount *m = nullptr;
1923 switch (which)
1924 {
1925 case MOUNT: m = TheScope(); break;
1926 case AO: m = TheAO(); break;
1927 case WHICH_MOUNT_BOTH:
1928 case WHICH_MOUNT_ERR:
1929 response << jrpc_error(1, "invalid 'which' param");
1930 return;
1931 }
1932
1933 if (!m || !m->IsConnected())
1934 {
1935 response << jrpc_error(1, "device not connected");
1936 return;
1937 }
1938
1939 if (pFrame->pGuider->IsCalibratingOrGuiding() || m->IsBusy())
1940 {
1941 response << jrpc_error(1, "cannot issue guide pulse while calibrating or guiding");
1942 return;
1943 }
1944
1945 int duration = amount->int_value;
1946 if (duration < 0)
1947 {
1948 duration = -duration;
1949 dir = opposite(dir);
1950 }
1951
1952 pFrame->ScheduleManualMove(m, dir, duration);
1953
1954 response << jrpc_result(0);
1955 }
1956
parity_str(GuideParity p)1957 static const char *parity_str(GuideParity p)
1958 {
1959 switch (p) {
1960 case GUIDE_PARITY_EVEN: return "+";
1961 case GUIDE_PARITY_ODD: return "-";
1962 default: return "?";
1963 }
1964 }
1965
get_calibration_data(JObj & response,const json_value * params)1966 static void get_calibration_data(JObj& response, const json_value *params)
1967 {
1968 Params p("which", params);
1969
1970 WHICH_MOUNT which = which_mount(p.param("which"));
1971 Mount *m = nullptr;
1972 switch (which)
1973 {
1974 case MOUNT: m = TheScope(); break;
1975 case AO: m = TheAO(); break;
1976 case WHICH_MOUNT_BOTH:
1977 case WHICH_MOUNT_ERR:
1978 {
1979 response << jrpc_error(1, "invalid 'which' param");
1980 return;
1981 }
1982 }
1983
1984 if (!m || !m->IsConnected())
1985 {
1986 response << jrpc_error(1, "device not connected");
1987 return;
1988 }
1989
1990 JObj rslt;
1991 rslt << NV("calibrated", m->IsCalibrated());
1992
1993 if (m->IsCalibrated())
1994 {
1995 rslt << NV("xAngle", degrees(m->xAngle()), 1)
1996 << NV("xRate", m->xRate() * 1000.0, 3)
1997 << NV("xParity", parity_str(m->RAParity()))
1998 << NV("yAngle", degrees(m->yAngle()), 1)
1999 << NV("yRate", m->yRate() * 1000.0, 3)
2000 << NV("yParity", parity_str(m->DecParity()))
2001 << NV("declination", degrees(m->GetCalibrationDeclination()));
2002 }
2003
2004 response << jrpc_result(rslt);
2005 }
2006
get_cooler_status(JObj & response,const json_value * params)2007 static void get_cooler_status(JObj& response, const json_value *params)
2008 {
2009 if (!pCamera || !pCamera->Connected)
2010 {
2011 response << jrpc_error(1, "camera not connected");
2012 return;
2013 }
2014
2015 bool on;
2016 double setpoint, power, temperature;
2017
2018 bool err = pCamera->GetCoolerStatus(&on, &setpoint, &power, &temperature);
2019 if (err)
2020 {
2021 response << jrpc_error(1, "failed to get cooler status");
2022 return;
2023 }
2024
2025 JObj rslt;
2026
2027 rslt << NV("coolerOn", on)
2028 << NV("temperature", temperature, 1);
2029
2030 if (on)
2031 {
2032 rslt << NV("setpoint", setpoint, 1)
2033 << NV("power", power, 1);
2034 }
2035
2036 response << jrpc_result(rslt);
2037 }
2038
get_sensor_temperature(JObj & response,const json_value * params)2039 static void get_sensor_temperature(JObj& response, const json_value *params)
2040 {
2041 if (!pCamera || !pCamera->Connected)
2042 {
2043 response << jrpc_error(1, "camera not connected");
2044 return;
2045 }
2046
2047 double temperature;
2048 bool err = pCamera->GetSensorTemperature(&temperature);
2049 if (err)
2050 {
2051 response << jrpc_error(1, "failed to get sensor temperature");
2052 return;
2053 }
2054
2055 JObj rslt;
2056 rslt << NV("temperature", temperature, 1);
2057
2058 response << jrpc_result(rslt);
2059 }
2060
export_config_settings(JObj & response,const json_value * params)2061 static void export_config_settings(JObj& response, const json_value *params)
2062 {
2063 wxString filename(MyFrame::GetDefaultFileDir() + PATHSEPSTR + "phd2_settings.txt");
2064 bool err = pConfig->SaveAll(filename);
2065
2066 if (err)
2067 {
2068 response << jrpc_error(1, "export settings failed");
2069 return;
2070 }
2071
2072 JObj rslt;
2073 rslt << NV("filename", filename);
2074
2075 response << jrpc_result(rslt);
2076 }
2077
2078 struct JRpcCall
2079 {
2080 wxSocketClient *cli;
2081 const json_value *req;
2082 const json_value *method;
2083 JRpcResponse response;
2084
JRpcCallJRpcCall2085 JRpcCall(wxSocketClient *cli_, const json_value *req_) : cli(cli_), req(req_), method(nullptr) { }
2086 };
2087
dump_request(const JRpcCall & call)2088 static void dump_request(const JRpcCall& call)
2089 {
2090 Debug.Write(wxString::Format("evsrv: cli %p request: %s\n", call.cli, json_format(call.req)));
2091 }
2092
dump_response(const JRpcCall & call)2093 static void dump_response(const JRpcCall& call)
2094 {
2095 wxString s(const_cast<JRpcResponse&>(call.response).str());
2096
2097 // trim output for huge responses
2098
2099 // this is very hacky operating directly on the string, but it's not
2100 // worth bothering to parse and reformat the response
2101 if (call.method && strcmp(call.method->string_value, "get_star_image") == 0)
2102 {
2103 size_t p0, p1;
2104 if ((p0 = s.find("\"pixels\":\"")) != wxString::npos && (p1 = s.find('"', p0 + 10)) != wxString::npos)
2105 s.replace(p0 + 10, p1 - (p0 + 10), "...");
2106 }
2107
2108 Debug.Write(wxString::Format("evsrv: cli %p response: %s\n", call.cli, s));
2109 }
2110
handle_request(JRpcCall & call)2111 static bool handle_request(JRpcCall& call)
2112 {
2113 const json_value *params;
2114 const json_value *id;
2115
2116 dump_request(call);
2117
2118 parse_request(call.req, &call.method, ¶ms, &id);
2119
2120 if (!call.method)
2121 {
2122 call.response << jrpc_error(JSONRPC_INVALID_REQUEST, "invalid request - missing method") << jrpc_id(0);
2123 return true;
2124 }
2125
2126 if (params && !(params->type == JSON_ARRAY || params->type == JSON_OBJECT))
2127 {
2128 call.response << jrpc_error(JSONRPC_INVALID_REQUEST, "invalid request - params must be an array or object")
2129 << jrpc_id(0);
2130 return true;
2131 }
2132
2133 static struct {
2134 const char *name;
2135 void (*fn)(JObj& response, const json_value *params);
2136 } methods[] = {
2137 { "clear_calibration", &clear_calibration, },
2138 { "deselect_star", &deselect_star, },
2139 { "get_exposure", &get_exposure, },
2140 { "set_exposure", &set_exposure, },
2141 { "get_exposure_durations", &get_exposure_durations, },
2142 { "get_profiles", &get_profiles, },
2143 { "get_profile", &get_profile, },
2144 { "set_profile", &set_profile, },
2145 { "get_connected", &get_connected, },
2146 { "set_connected", &set_connected, },
2147 { "get_calibrated", &get_calibrated, },
2148 { "get_paused", &get_paused, },
2149 { "set_paused", &set_paused, },
2150 { "get_lock_position", &get_lock_position, },
2151 { "set_lock_position", &set_lock_position, },
2152 { "loop", &loop, },
2153 { "stop_capture", &stop_capture, },
2154 { "guide", &guide, },
2155 { "dither", &dither, },
2156 { "find_star", &find_star, },
2157 { "get_pixel_scale", &get_pixel_scale, },
2158 { "get_app_state", &get_app_state, },
2159 { "flip_calibration", &flip_calibration, },
2160 { "get_lock_shift_enabled", &get_lock_shift_enabled, },
2161 { "set_lock_shift_enabled", &set_lock_shift_enabled, },
2162 { "get_lock_shift_params", &get_lock_shift_params, },
2163 { "set_lock_shift_params", &set_lock_shift_params, },
2164 { "save_image", &save_image, },
2165 { "get_star_image", &get_star_image, },
2166 { "get_use_subframes", &get_use_subframes, },
2167 { "get_search_region", &get_search_region, },
2168 { "shutdown", &shutdown, },
2169 { "get_camera_binning", &get_camera_binning, },
2170 { "get_camera_frame_size", &get_camera_frame_size, },
2171 { "get_current_equipment", &get_current_equipment, },
2172 { "get_guide_output_enabled", &get_guide_output_enabled, },
2173 { "set_guide_output_enabled", &set_guide_output_enabled, },
2174 { "get_algo_param_names", &get_algo_param_names, },
2175 { "get_algo_param", &get_algo_param, },
2176 { "set_algo_param", &set_algo_param, },
2177 { "get_dec_guide_mode", &get_dec_guide_mode, },
2178 { "set_dec_guide_mode", &set_dec_guide_mode, },
2179 { "get_settling", &get_settling, },
2180 { "guide_pulse", &guide_pulse, },
2181 { "get_calibration_data", &get_calibration_data, },
2182 { "capture_single_frame", &capture_single_frame, },
2183 { "get_cooler_status", &get_cooler_status, },
2184 { "get_ccd_temperature", &get_sensor_temperature, },
2185 { "export_config_settings", &export_config_settings, },
2186 };
2187
2188 for (unsigned int i = 0; i < WXSIZEOF(methods); i++)
2189 {
2190 if (strcmp(call.method->string_value, methods[i].name) == 0)
2191 {
2192 (*methods[i].fn)(call.response, params);
2193 if (id)
2194 {
2195 call.response << jrpc_id(id);
2196 return true;
2197 }
2198 else
2199 {
2200 return false;
2201 }
2202 }
2203 }
2204
2205 if (id)
2206 {
2207 call.response << jrpc_error(JSONRPC_METHOD_NOT_FOUND, "method not found") << jrpc_id(id);
2208 return true;
2209 }
2210 else
2211 {
2212 return false;
2213 }
2214 }
2215
handle_cli_input_complete(wxSocketClient * cli,char * input,JsonParser & parser)2216 static void handle_cli_input_complete(wxSocketClient *cli, char *input, JsonParser& parser)
2217 {
2218 if (!parser.Parse(input))
2219 {
2220 JRpcCall call(cli, nullptr);
2221 call.response << jrpc_error(JSONRPC_PARSE_ERROR, parser_error(parser)) << jrpc_id(0);
2222 dump_response(call);
2223 do_notify1(cli, call.response);
2224 return;
2225 }
2226
2227 const json_value *root = parser.Root();
2228
2229 if (root->type == JSON_ARRAY)
2230 {
2231 // a batch request
2232
2233 JAry ary;
2234
2235 bool found = false;
2236 json_for_each (req, root)
2237 {
2238 JRpcCall call(cli, req);
2239 if (handle_request(call))
2240 {
2241 dump_response(call);
2242 ary << call.response;
2243 found = true;
2244 }
2245 }
2246
2247 if (found)
2248 do_notify1(cli, ary);
2249 }
2250 else
2251 {
2252 // a single request
2253
2254 const json_value *const req = root;
2255 JRpcCall call(cli, req);
2256 if (handle_request(call))
2257 {
2258 dump_response(call);
2259 do_notify1(cli, call.response);
2260 }
2261 }
2262 }
2263
handle_cli_input(wxSocketClient * cli,JsonParser & parser)2264 static void handle_cli_input(wxSocketClient *cli, JsonParser& parser)
2265 {
2266 // Bump refcnt to protect against reentrancy.
2267 //
2268 // Some functions like set_connected can cause the event loop to run reentrantly. If the
2269 // client disconnects before the response is sent and a socket disconnect event is
2270 // dispatched the client data could be destroyed before we respond.
2271
2272 ClientDataGuard clidata(cli);
2273
2274 ClientReadBuf *rdbuf = &clidata->rdbuf;
2275
2276 wxSocketInputStream sis(*cli);
2277
2278 while (sis.CanRead())
2279 {
2280 if (rdbuf->avail() == 0)
2281 {
2282 drain_input(sis);
2283
2284 JRpcResponse response;
2285 response << jrpc_error(JSONRPC_INTERNAL_ERROR, "too big") << jrpc_id(0);
2286 do_notify1(cli, response);
2287
2288 rdbuf->reset();
2289 break;
2290 }
2291 size_t n = sis.Read(rdbuf->dest, rdbuf->avail()).LastRead();
2292 if (n == 0)
2293 break;
2294
2295 rdbuf->dest += n;
2296
2297 char *end;
2298 while ((end = static_cast<char *>(memchr(rdbuf->buf(), '\n', rdbuf->len()))) != nullptr)
2299 {
2300 // Copy to temporary buffer to avoid rentrancy problem
2301 char line[ClientReadBuf::SIZE];
2302 size_t len1 = end - rdbuf->buf();
2303 memcpy(line, rdbuf->buf(), len1);
2304 line[len1] = 0;
2305
2306 char *next = end + 1;
2307 size_t len2 = rdbuf->dest - next;
2308 memmove(rdbuf->buf(), next, len2);
2309 rdbuf->dest = rdbuf->buf() + len2;
2310
2311 handle_cli_input_complete(cli, line, parser);
2312 }
2313 }
2314 }
2315
EventServer()2316 EventServer::EventServer()
2317 : m_configEventDebouncer(nullptr)
2318 {
2319 }
2320
~EventServer()2321 EventServer::~EventServer()
2322 {
2323 }
2324
EventServerStart(unsigned int instanceId)2325 bool EventServer::EventServerStart(unsigned int instanceId)
2326 {
2327 if (m_serverSocket)
2328 {
2329 Debug.AddLine("attempt to start event server when it is already started?");
2330 return false;
2331 }
2332
2333 unsigned int port = 4400 + instanceId - 1;
2334 wxIPV4address eventServerAddr;
2335 eventServerAddr.Service(port);
2336 m_serverSocket = new wxSocketServer(eventServerAddr, wxSOCKET_REUSEADDR);
2337
2338 if (!m_serverSocket->Ok())
2339 {
2340 Debug.Write(wxString::Format("Event server failed to start - Could not listen at port %u\n", port));
2341 delete m_serverSocket;
2342 m_serverSocket = nullptr;
2343 return true;
2344 }
2345
2346 m_serverSocket->SetEventHandler(*this, EVENT_SERVER_ID);
2347 m_serverSocket->SetNotify(wxSOCKET_CONNECTION_FLAG);
2348 m_serverSocket->Notify(true);
2349
2350 m_configEventDebouncer = new wxTimer();
2351
2352 Debug.Write(wxString::Format("event server started, listening on port %u\n", port));
2353
2354 return false;
2355 }
2356
EventServerStop()2357 void EventServer::EventServerStop()
2358 {
2359 if (!m_serverSocket)
2360 return;
2361
2362 for (CliSockSet::const_iterator it = m_eventServerClients.begin();
2363 it != m_eventServerClients.end(); ++it)
2364 {
2365 destroy_client(*it);
2366 }
2367 m_eventServerClients.clear();
2368
2369 delete m_serverSocket;
2370 m_serverSocket = nullptr;
2371
2372 delete m_configEventDebouncer;
2373 m_configEventDebouncer = nullptr;
2374
2375 Debug.AddLine("event server stopped");
2376 }
2377
OnEventServerEvent(wxSocketEvent & event)2378 void EventServer::OnEventServerEvent(wxSocketEvent& event)
2379 {
2380 wxSocketServer *server = static_cast<wxSocketServer *>(event.GetSocket());
2381
2382 if (event.GetSocketEvent() != wxSOCKET_CONNECTION)
2383 return;
2384
2385 wxSocketClient *client = static_cast<wxSocketClient *>(server->Accept(false));
2386
2387 if (!client)
2388 return;
2389
2390 Debug.Write(wxString::Format("evsrv: cli %p connect\n", client));
2391
2392 client->SetEventHandler(*this, EVENT_SERVER_CLIENT_ID);
2393 client->SetNotify(wxSOCKET_LOST_FLAG | wxSOCKET_INPUT_FLAG);
2394 client->SetFlags(wxSOCKET_NOWAIT);
2395 client->Notify(true);
2396 client->SetClientData(new ClientData(client));
2397
2398 send_catchup_events(client);
2399
2400 m_eventServerClients.insert(client);
2401 }
2402
OnEventServerClientEvent(wxSocketEvent & event)2403 void EventServer::OnEventServerClientEvent(wxSocketEvent& event)
2404 {
2405 wxSocketClient *cli = static_cast<wxSocketClient *>(event.GetSocket());
2406
2407 if (event.GetSocketEvent() == wxSOCKET_LOST)
2408 {
2409 Debug.Write(wxString::Format("evsrv: cli %p disconnect\n", cli));
2410
2411 unsigned int const n = m_eventServerClients.erase(cli);
2412 if (n != 1)
2413 Debug.AddLine("client disconnected but not present in client set!");
2414
2415 destroy_client(cli);
2416 }
2417 else if (event.GetSocketEvent() == wxSOCKET_INPUT)
2418 {
2419 handle_cli_input(cli, m_parser);
2420 }
2421 else
2422 {
2423 Debug.Write(wxString::Format("unexpected client socket event %d\n", event.GetSocketEvent()));
2424 }
2425 }
2426
NotifyStartCalibration(const Mount * mount)2427 void EventServer::NotifyStartCalibration(const Mount *mount)
2428 {
2429 SIMPLE_NOTIFY_EV(ev_start_calibration(mount));
2430 }
2431
NotifyCalibrationStep(const CalibrationStepInfo & info)2432 void EventServer::NotifyCalibrationStep(const CalibrationStepInfo& info)
2433 {
2434 if (m_eventServerClients.empty())
2435 return;
2436
2437 Ev ev("Calibrating");
2438
2439 ev << NVMount(info.mount)
2440 << NV("dir", info.direction)
2441 << NV("dist", info.dist)
2442 << NV("dx", info.dx)
2443 << NV("dy", info.dy)
2444 << NV("pos", info.pos)
2445 << NV("step", info.stepNumber);
2446
2447 if (!info.msg.empty())
2448 ev << NV("State", info.msg);
2449
2450 do_notify(m_eventServerClients, ev);
2451 }
2452
NotifyCalibrationFailed(const Mount * mount,const wxString & msg)2453 void EventServer::NotifyCalibrationFailed(const Mount *mount, const wxString& msg)
2454 {
2455 if (m_eventServerClients.empty())
2456 return;
2457
2458 Ev ev("CalibrationFailed");
2459 ev << NVMount(mount) << NV("Reason", msg);
2460
2461 do_notify(m_eventServerClients, ev);
2462 }
2463
NotifyCalibrationComplete(const Mount * mount)2464 void EventServer::NotifyCalibrationComplete(const Mount *mount)
2465 {
2466 if (m_eventServerClients.empty())
2467 return;
2468
2469 do_notify(m_eventServerClients, ev_calibration_complete(mount));
2470 }
2471
NotifyCalibrationDataFlipped(const Mount * mount)2472 void EventServer::NotifyCalibrationDataFlipped(const Mount *mount)
2473 {
2474 if (m_eventServerClients.empty())
2475 return;
2476
2477 Ev ev("CalibrationDataFlipped");
2478 ev << NVMount(mount);
2479
2480 do_notify(m_eventServerClients, ev);
2481 }
2482
NotifyLooping(unsigned int exposure,const Star * star,const FrameDroppedInfo * info)2483 void EventServer::NotifyLooping(unsigned int exposure, const Star *star, const FrameDroppedInfo *info)
2484 {
2485 if (m_eventServerClients.empty())
2486 return;
2487
2488 Ev ev("LoopingExposures");
2489 ev << NV("Frame", exposure);
2490
2491 double mass = 0., snr, hfd;
2492 int err = 0;
2493 wxString status;
2494
2495 if (star)
2496 {
2497 mass = star->Mass;
2498 snr = star->SNR;
2499 hfd = star->HFD;
2500 err = star->GetError();
2501 }
2502 else if (info)
2503 {
2504 if (Star::WasFound(static_cast<Star::FindResult>(info->starError)))
2505 {
2506 mass = info->starMass;
2507 snr = info->starSNR;
2508 hfd = info->starHFD;
2509 }
2510 err = info->starError;
2511 status = info->status;
2512 }
2513
2514 if (mass)
2515 {
2516 ev << NV("StarMass", mass, 0)
2517 << NV("SNR", snr, 2)
2518 << NV("HFD", hfd, 2);
2519 }
2520
2521 if (err)
2522 ev << NV("ErrorCode", err);
2523
2524 if (!status.IsEmpty())
2525 ev << NV("Status", status);
2526
2527 do_notify(m_eventServerClients, ev);
2528 }
2529
NotifyLoopingStopped()2530 void EventServer::NotifyLoopingStopped()
2531 {
2532 SIMPLE_NOTIFY("LoopingExposuresStopped");
2533 }
2534
NotifyStarSelected(const PHD_Point & pt)2535 void EventServer::NotifyStarSelected(const PHD_Point& pt)
2536 {
2537 SIMPLE_NOTIFY_EV(ev_star_selected(pt));
2538 }
2539
NotifyStarLost(const FrameDroppedInfo & info)2540 void EventServer::NotifyStarLost(const FrameDroppedInfo& info)
2541 {
2542 if (m_eventServerClients.empty())
2543 return;
2544
2545 Ev ev("StarLost");
2546
2547 ev << NV("Frame", info.frameNumber)
2548 << NV("Time", info.time, 3)
2549 << NV("StarMass", info.starMass, 0)
2550 << NV("SNR", info.starSNR, 2)
2551 << NV("HFD", info.starHFD, 2)
2552 << NV("AvgDist", info.avgDist, 2);
2553
2554 if (info.starError)
2555 ev << NV("ErrorCode", info.starError);
2556
2557 if (!info.status.IsEmpty())
2558 ev << NV("Status", info.status);
2559
2560 do_notify(m_eventServerClients, ev);
2561 }
2562
NotifyGuidingStarted()2563 void EventServer::NotifyGuidingStarted()
2564 {
2565 SIMPLE_NOTIFY_EV(ev_start_guiding());
2566 }
2567
NotifyGuidingStopped()2568 void EventServer::NotifyGuidingStopped()
2569 {
2570 SIMPLE_NOTIFY("GuidingStopped");
2571 }
2572
NotifyPaused()2573 void EventServer::NotifyPaused()
2574 {
2575 SIMPLE_NOTIFY_EV(ev_paused());
2576 }
2577
NotifyResumed()2578 void EventServer::NotifyResumed()
2579 {
2580 SIMPLE_NOTIFY("Resumed");
2581 }
2582
NotifyGuideStep(const GuideStepInfo & step)2583 void EventServer::NotifyGuideStep(const GuideStepInfo& step)
2584 {
2585 if (m_eventServerClients.empty())
2586 return;
2587
2588 Ev ev("GuideStep");
2589
2590 ev << NV("Frame", step.frameNumber)
2591 << NV("Time", step.time, 3)
2592 << NVMount(step.mount)
2593 << NV("dx", step.cameraOffset.X, 3)
2594 << NV("dy", step.cameraOffset.Y, 3)
2595 << NV("RADistanceRaw", step.mountOffset.X, 3)
2596 << NV("DECDistanceRaw", step.mountOffset.Y, 3)
2597 << NV("RADistanceGuide", step.guideDistanceRA, 3)
2598 << NV("DECDistanceGuide", step.guideDistanceDec, 3);
2599
2600 if (step.durationRA > 0)
2601 {
2602 ev << NV("RADuration", step.durationRA)
2603 << NV("RADirection", step.mount->DirectionStr((GUIDE_DIRECTION)step.directionRA));
2604 }
2605
2606 if (step.durationDec > 0)
2607 {
2608 ev << NV("DECDuration", step.durationDec)
2609 << NV("DECDirection", step.mount->DirectionStr((GUIDE_DIRECTION)step.directionDec));
2610 }
2611
2612 if (step.mount->IsStepGuider())
2613 {
2614 ev << NV("Pos", step.aoPos);
2615 }
2616
2617 ev << NV("StarMass", step.starMass, 0)
2618 << NV("SNR", step.starSNR, 2)
2619 << NV("HFD", step.starHFD, 2)
2620 << NV("AvgDist", step.avgDist, 2);
2621
2622 if (step.starError)
2623 ev << NV("ErrorCode", step.starError);
2624
2625 if (step.raLimited)
2626 ev << NV("RALimited", true);
2627
2628 if (step.decLimited)
2629 ev << NV("DecLimited", true);
2630
2631 do_notify(m_eventServerClients, ev);
2632 }
2633
NotifyGuidingDithered(double dx,double dy)2634 void EventServer::NotifyGuidingDithered(double dx, double dy)
2635 {
2636 if (m_eventServerClients.empty())
2637 return;
2638
2639 Ev ev("GuidingDithered");
2640 ev << NV("dx", dx, 3) << NV("dy", dy, 3);
2641
2642 do_notify(m_eventServerClients, ev);
2643 }
2644
NotifySetLockPosition(const PHD_Point & xy)2645 void EventServer::NotifySetLockPosition(const PHD_Point& xy)
2646 {
2647 if (m_eventServerClients.empty())
2648 return;
2649
2650 do_notify(m_eventServerClients, ev_set_lock_position(xy));
2651 }
2652
NotifyLockPositionLost()2653 void EventServer::NotifyLockPositionLost()
2654 {
2655 SIMPLE_NOTIFY("LockPositionLost");
2656 }
2657
NotifyLockShiftLimitReached()2658 void EventServer::NotifyLockShiftLimitReached()
2659 {
2660 SIMPLE_NOTIFY("LockPositionShiftLimitReached");
2661 }
2662
NotifyAppState()2663 void EventServer::NotifyAppState()
2664 {
2665 if (m_eventServerClients.empty())
2666 return;
2667
2668 do_notify(m_eventServerClients, ev_app_state());
2669 }
2670
NotifySettleBegin()2671 void EventServer::NotifySettleBegin()
2672 {
2673 SIMPLE_NOTIFY("SettleBegin");
2674 }
2675
NotifySettling(double distance,double time,double settleTime,bool starLocked)2676 void EventServer::NotifySettling(double distance, double time, double settleTime, bool starLocked)
2677 {
2678 if (m_eventServerClients.empty())
2679 return;
2680
2681 Ev ev(ev_settling(distance, time, settleTime, starLocked));
2682
2683 Debug.Write(wxString::Format("evsrv: %s\n", ev.str()));
2684
2685 do_notify(m_eventServerClients, ev);
2686 }
2687
NotifySettleDone(const wxString & errorMsg,int settleFrames,int droppedFrames)2688 void EventServer::NotifySettleDone(const wxString& errorMsg, int settleFrames, int droppedFrames)
2689 {
2690 if (m_eventServerClients.empty())
2691 return;
2692
2693 Ev ev(ev_settle_done(errorMsg, settleFrames, droppedFrames));
2694
2695 Debug.Write(wxString::Format("evsrv: %s\n", ev.str()));
2696
2697 do_notify(m_eventServerClients, ev);
2698 }
2699
NotifyAlert(const wxString & msg,int type)2700 void EventServer::NotifyAlert(const wxString& msg, int type)
2701 {
2702 if (m_eventServerClients.empty())
2703 return;
2704
2705 Ev ev("Alert");
2706 ev << NV("Msg", msg);
2707
2708 wxString s;
2709 switch (type)
2710 {
2711 case wxICON_NONE:
2712 case wxICON_INFORMATION:
2713 default:
2714 s = "info";
2715 break;
2716 case wxICON_QUESTION:
2717 s = "question";
2718 break;
2719 case wxICON_WARNING:
2720 s = "warning";
2721 break;
2722 case wxICON_ERROR:
2723 s = "error";
2724 break;
2725 }
2726 ev << NV("Type", s);
2727
2728 do_notify(m_eventServerClients, ev);
2729 }
2730
2731 template<typename T>
NotifyGuidingParam(const EventServer::CliSockSet & clients,const wxString & name,T val)2732 static void NotifyGuidingParam(const EventServer::CliSockSet& clients, const wxString& name, T val)
2733 {
2734 if (clients.empty())
2735 return;
2736
2737 Ev ev("GuideParamChange");
2738 ev << NV("Name", name);
2739 ev << NV("Value", val);
2740
2741 do_notify(clients, ev);
2742 }
2743
NotifyGuidingParam(const wxString & name,double val)2744 void EventServer::NotifyGuidingParam(const wxString& name, double val)
2745 {
2746 ::NotifyGuidingParam(m_eventServerClients, name, val);
2747 }
2748
NotifyGuidingParam(const wxString & name,int val)2749 void EventServer::NotifyGuidingParam(const wxString& name, int val)
2750 {
2751 ::NotifyGuidingParam(m_eventServerClients, name, val);
2752 }
2753
NotifyGuidingParam(const wxString & name,bool val)2754 void EventServer::NotifyGuidingParam(const wxString& name, bool val)
2755 {
2756 ::NotifyGuidingParam(m_eventServerClients, name, val);
2757 }
2758
NotifyGuidingParam(const wxString & name,const wxString & val)2759 void EventServer::NotifyGuidingParam(const wxString& name, const wxString& val)
2760 {
2761 ::NotifyGuidingParam(m_eventServerClients, name, val);
2762 }
2763
NotifyConfigurationChange()2764 void EventServer::NotifyConfigurationChange()
2765 {
2766 if (m_configEventDebouncer == nullptr || m_configEventDebouncer->IsRunning())
2767 return;
2768
2769 Ev ev("ConfigurationChange");
2770 do_notify(m_eventServerClients, ev);
2771 m_configEventDebouncer->StartOnce(0);
2772 }
2773