/* * This file is part of the mountdaemon_10micron project. * Copyright 2026 Edward V. Emelianov . * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // base functions for sending/receiving data in stellarium protocol #include #include #include #include #include #include "angles.h" #include "mount.h" #include "stellarium.h" #define BUFLEN 256 // running flag static volatile bool isrunning = false; static pthread_t mainthread; //read: 0x14 0x0 0x0 0x0 0x5b 0x5a 0x2e 0xc6 0x8c 0x23 0x5 0x0 0x23 0x9 0xe5 0xaf 0x23 0x2e 0x34 0xed // command: goto 16h29 24.45 -26d25 55.62 /* LITTLE-ENDIAN!!! from client: LENGTH (2 bytes, integer): length of the message TYPE (2 bytes, integer): 0 TIME (8 bytes, integer): current time on the server computer in microseconds since 1970.01.01 UT. Currently unused. RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) a value of 0x100000000 = 0x0 means 24h=0h, a value of 0x80000000 means 12h DEC (4 bytes, signed integer): declination of the telescope (J2000) a value of -0x40000000 means -90degrees, a value of 0x0 means 0degrees, a value of 0x40000000 means 90degrees to client: LENGTH (2 bytes, integer): length of the message TYPE (2 bytes, integer): 0 TIME (8 bytes, integer): current time on the server computer in microseconds since 1970.01.01 UT. Currently unused. RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) a value of 0x100000000 = 0x0 means 24h=0h, a value of 0x80000000 means 12h DEC (4 bytes, signed integer): declination of the telescope (J2000) a value of -0x40000000 means -90degrees, a value of 0x0 means 0degrees, a value of 0x40000000 means 90degrees STATUS (4 bytes, signed integer): status of the telescope, currently unused. status=0 means ok, status<0 means some error */ #define DEG2DEC(degr) ((int32_t)(degr / 90. * ((double)0x40000000))) #define DEG2RA(degr) ((uint32_t)(degr / 180. * ((double)0x80000000))) #define HRS2RA(degr) ((uint32_t)(degr / 12. * ((double)0x80000000))) #define DEC2DEG(i32) (((double)i32)*90./((double)0x40000000)) #define RA2DEG(u32) (((double)u32)*180. /((double)0x80000000)) #define RA2HRS(u32) (((double)u32)*12. /((double)0x80000000)) typedef struct __attribute__((__packed__)){ uint16_t len; uint16_t type; uint64_t time; uint32_t ra; int32_t dec; } indata; typedef struct __attribute__((__packed__)){ uint16_t len; uint16_t type; uint64_t time; uint32_t ra; int32_t dec; int32_t status; } outdata; /** * @brief proc_data - process data received from Stellarium * @param data - raw data * @param len - its length * @return true if all OK */ static bool proc_data(uint8_t *data, ssize_t len){ FNAME(); if(len != sizeof(indata)){ WARNX("Bad data size: got %zd instead of %zd!", len, sizeof(indata)); return false; } indata *dat = (indata*)data; uint16_t L, T; uint32_t ra; int32_t dec; #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ L = le16toh(dat->len); T = le16toh(dat->type); ra = le32toh(dat->ra); dec = (int32_t)le32toh((uint32_t)dat->dec); #else L = dat->len; T = dat->type; ra = dat->ra; dec = dat->dec; #endif DBG("got message with len %u & type %u", L, T); if(L != len){ WARNX("Length of message != msg->len"); return false; } if(T){ WARNX("Wrong message type"); return false; } // convert RA/DEC to degrees double tagRA = RA2DEG(ra), tagDec = DEC2DEG(dec); DBG("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec); LOGMSG("(stellarium) RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec); #if 0 // check RA/DEC horizCrds_t hnow; // without refraction polarCrds_t p2000, pnow; p2000.ra = DEG2RAD(tagRA); p2000.dec = DEG2RAD(tagDec); // now J2000 obs Jnow if(!get_ObsPlace(NULL, &p2000, &pnow, &hnow)){ WARNX("Can't convert coordinates to Jnow"); return false; } tagRA = RAD2DEG(pnow.ra - pnow.eo); tagDec = RAD2DEG(pnow.dec); #endif return (mount_setInpRA(tagRA) && mount_setInpDec(tagDec) && mount_setInpMJD(ERFA_DJM00)); } /** * Send data to user * @param data - data to send * @param dlen - data length * @param sockfd - socket fd for sending data * @return false if client disconnected */ float send_data(uint8_t *data, size_t dlen, int sockfd){ ssize_t sent = send(sockfd, data, dlen, MSG_NOSIGNAL); if(sent != (ssize_t)dlen){ if(sent == -1 && errno != EINTR){ WARN("Disconnected?"); return false; } WARN("write()"); } return true; } /** * main socket service procedure */ static void *handle_socket(void *sockd){ FNAME(); if(!isrunning) return NULL; outdata dout; int sock = *(int*)sockd; dout.len = htole16(sizeof(outdata)); dout.type = 0; while(isrunning){ // get coordinates double RA = 0., Decl = 0.; if((dout.status = mount_getcoords(&RA, &Decl)) == MNT_S_ERROR){ WARNX("Error: can't get coordinates"); sleep(1); continue; } //DBG("got : %g/%g", RA, Decl); dout.ra = htole32(HRS2RA(RA)); dout.dec = (int32_t)htole32(DEG2DEC(Decl)); if(!send_data((uint8_t*)&dout, sizeof(outdata), sock)) break; //DBG("sent ra = %g, dec = %g", RA2HRS(dout.ra), DEC2DEG(dout.dec)); if(!sl_canread(sock)){ sleep(1); continue; } // fill incoming buffer uint8_t buff[BUFLEN]; ssize_t rd = read(sock, buff, BUFLEN-1); if(rd <= 0){ // error or disconnect DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); WARNX("Client disconnected?"); break; } buff[rd] = 0; DBG("read %zd (%s)", rd, buff); if(!proc_data(buff, rd)) dout.status = -1; else{ DBG("sent OK"); dout.status = 0; } } close(sock); return NULL; } // Main loop thread: wait connections over socket and create one thread for each static void* start(void *F){ if(isrunning){ WARNX("already running"); return NULL; } if(!F){ WARNX("start(): No arg"); return NULL; } int sockfd = *((int*)F); if(sockfd < 0){ WARNX("start(): sockfd=%d", sockfd); return NULL; } DBG("Start main loop, sockfd=%d", sockfd); isrunning = true; // Main loop while(isrunning){ socklen_t size = sizeof(struct sockaddr_in); struct sockaddr_in myaddr; int newsock; DBG("ACCEPT"); newsock = accept(sockfd, (struct sockaddr*)&myaddr, &size); if(newsock <= 0){ if(errno == EAGAIN){ usleep(1000); continue; // nothing available } WARN("accept()"); LOGWARN("Stellarium socket error in accept()"); sleep(1); continue; } struct sockaddr_in peer; socklen_t peer_len = sizeof(peer); if(getpeername(newsock, (struct sockaddr*)&peer, &peer_len) == -1){ LOGWARN("Stellarium socket error in getpeername()"); WARN("getpeername()"); close(newsock); continue; } int sockport = -1; if(getsockname(newsock, (struct sockaddr*)&peer, &peer_len) == 0){ sockport = ntohs(peer.sin_port); } char *peerIP = inet_ntoa(peer.sin_addr); LOGMSG("Got connection from %s @ %d", peerIP, sockport); DBG("Peer's IP address is: %s (@port %d)\n", peerIP, sockport); /*if(strcmp(peerIP, ACCEPT_IP) && strcmp(peerIP, "127.0.0.1")){ WARNX("Wrong IP"); close(newsock); continue; }*/ pthread_t rthrd; if(pthread_create(&rthrd, NULL, handle_socket, (void*)&newsock)){ WARN("Can't create socket thread"); LOGERR("Stellarium socket: error creating listen thread"); }else{ DBG("Thread created, detouch"); pthread_detach(rthrd); // don't care about thread state } } close(sockfd); return NULL; } bool stellarium_start(int sockfd){ int fd = sockfd; if(pthread_create(&mainthread, NULL, start, (void*)&fd)){ WARN("pthread_create()"); return false; } DBG("Stellarium server started"); return true; } void stellarium_stop(){ if(!isrunning) return; isrunning = false; pthread_join(mainthread, NULL); }