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, &params, &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