diff --git a/src/indi_client.cc b/src/indi_client.cc index 0b0d89d..eec5495 100644 --- a/src/indi_client.cc +++ b/src/indi_client.cc @@ -35,76 +35,61 @@ const std::vector& ObsIndiClient::indiFrame() const return rgba_frame; } -std::list> ObsIndiClient::indiCCDs() const +std::list ObsIndiClient::indiCCDs() const { + std::list ccds; + for (auto it = devices.begin(); it != devices.end(); it++) { + if (it->second.isCCD) + ccds.push_back(it->second.device_name); + } return ccds; } -std::pair ObsIndiClient::indiCurrentCCD() const +std::string ObsIndiClient::indiCurrentCCD() const { return current_ccd; } bool ObsIndiClient::connectServer() { - bool res = INDI::BaseClient::connectServer(); + devices.clear(); dummyFillFrame(); - return res; + return INDI::BaseClient::connectServer(); } bool ObsIndiClient::disconnectServer(int exit_code) { - ccds.clear(); - current_ccd = std::make_pair("", ""); + devices.clear(); + current_ccd = ""; dummyFillFrame(); return INDI::BaseClient::disconnectServer(exit_code); } -void ObsIndiClient::selectCCD(const std::string &ccddev) -{ - auto ccd = ccddev_decode(ccddev); - selectCCD(ccd); -} - -void ObsIndiClient::selectCCD(const std::pair &ccd) +void ObsIndiClient::selectCCD(const std::string &ccd) { if (current_ccd == ccd) return; - auto property_name = current_ccd.first; - auto device_name = current_ccd.second; - if (std::find(ccds.begin(), ccds.end(), current_ccd) != ccds.end()) { + if (current_ccd != "" && devices.find(current_ccd) != devices.end()) { + auto property_name = devices[current_ccd].property_name; + auto device_name = devices[current_ccd].device_name; std::cout << "Disable blob data on " << property_name << ":" << device_name << std::endl; setBLOBMode(B_NEVER, device_name.c_str(), property_name.c_str()); } current_ccd = ccd; - property_name = current_ccd.first; - device_name = current_ccd.second; - if (std::find(ccds.begin(), ccds.end(), current_ccd) != ccds.end()) { - std::cout << "Enable blob data on " << property_name << ":" << device_name << std::endl; - setBLOBMode(B_ALSO, device_name.c_str(), property_name.c_str()); + if (current_ccd != "" && devices.find(current_ccd) != devices.end()) { + if (devices[current_ccd].isCCD) { + auto property_name = devices[current_ccd].property_name; + auto device_name = devices[current_ccd].device_name; + std::cout << "Enable blob data on " << property_name << ":" << device_name << std::endl; + setBLOBMode(B_ALSO, device_name.c_str(), property_name.c_str()); + } } dummyFillFrame(); } -std::pair ObsIndiClient::ccddev_decode(const std::string& ccddev) -{ - size_t pos = ccddev.find(":"); - std::pair newccd; - if (pos == ccddev.npos) - newccd = std::make_pair("", ""); - else - newccd = std::make_pair(ccddev.substr(0, pos), ccddev.substr(pos + 1)); - return newccd; -} - -std::string ObsIndiClient::ccddev_encode(const std::pair &ccd) -{ - return ccd.first + ":" + ccd.second; -} - uint8_t ObsIndiClient::fitsGetPixel(const std::vector &data, size_t w, size_t h, int bitpix, int x, int y, int plane) const { int bpp = 1; @@ -161,9 +146,9 @@ void ObsIndiClient::indiFillFrameJpeg(const uint8_t *data, size_t size) jpeg_start_decompress(&cinfo); int components = cinfo.output_components; // Should be 3 for RGB - if (components != 3) + if (components != 3 && components != 1) { - std::cerr << "Expected 3 components (RGB), got " << components << std::endl; + std::cerr << "Expected 1 or 3 components (RGB), got " << components << std::endl; jpeg_destroy_decompress(&cinfo); return; } @@ -187,10 +172,18 @@ void ObsIndiClient::indiFillFrameJpeg(const uint8_t *data, size_t size) int srcpos = 0; for (int x = 0; x < width; x++) { - rgba[dstpos++] = sl[srcpos++]; - rgba[dstpos++] = sl[srcpos++]; - rgba[dstpos++] = sl[srcpos++]; - rgba[dstpos++] = 255; + if (components == 3) { + rgba[dstpos++] = sl[srcpos++]; + rgba[dstpos++] = sl[srcpos++]; + rgba[dstpos++] = sl[srcpos++]; + rgba[dstpos++] = 255; + } else if (components == 1) { + uint8_t c = sl[srcpos++]; + rgba[dstpos++] = c; + rgba[dstpos++] = c; + rgba[dstpos++] = c; + rgba[dstpos++] = 255; + } } } @@ -206,6 +199,7 @@ void ObsIndiClient::indiFillFrameFITS(uint8_t *data, size_t size) if (fits_open_memfile(&fptr, "mem://blob", READONLY, (void**)(&data), &size, 0, nullptr, &status) != 0) { blog(LOG_INFO, "Can not read fits, skipping"); + fits_report_error(stderr, status); return; } @@ -298,10 +292,47 @@ void ObsIndiClient::indiFillFrame(IBLOB *indiBlob) indiFillFrameJpeg(blob, size); skipped = false; } - else - { - if (!skipped) + else if (!strcmp(indiBlob->format, ".stream")) { + switch(devices[current_ccd].video_method) + { + case ObsIndiClient::Device::METHOD_CAPTURE: { + switch (devices[current_ccd].capture_format) { + case ObsIndiClient::Device::FORMAT_FITS: { + indiFillFrameFITS(blob, size); + skipped = false; + break; + } + default: { + if (!skipped) { + blog(LOG_INFO, "Unsupported format, skipping"); + } + skipped = true; + break; + } + } + break; + } + case ObsIndiClient::Device::METHOD_STREAM : { + switch (devices[current_ccd].stream_format) { + case ObsIndiClient::Device::FORMAT_MJPEG: { + indiFillFrameJpeg(blob, size); + skipped = false; + break; + } + case ObsIndiClient::Device::FORMAT_RAW: { + if (!skipped) + blog(LOG_INFO, "Unsupported format RAW, skipping"); + skipped = true; + break; + } + } + break; + } + } + } else { + if (!skipped) { blog(LOG_INFO, "Unknown blob format %s, skipping", indiBlob->format); + } skipped = true; return; } @@ -319,42 +350,128 @@ void ObsIndiClient::dummyFillFrame() void ObsIndiClient::newDevice(INDI::BaseDevice baseDevice) { - std::string name(baseDevice.getDeviceName()); - devices.push_back(name); - std::cout << "New device: " << name << std::endl; + std::string device_name(baseDevice.getDeviceName()); + std::cout << "New device: " << device_name << std::endl; + Device dev; + dev.device_name = device_name; + dev.isCCD = false; + devices[device_name] = dev; } void ObsIndiClient::removeDevice(INDI::BaseDevice baseDevice) { - std::string name(baseDevice.getDeviceName()); - auto it = std::find(devices.begin(), devices.end(), name); + std::string device_name(baseDevice.getDeviceName()); + auto it = devices.find(device_name); if (it != devices.end()) devices.erase(it); - std::cout << "Remove device: " << name << std::endl; + std::cout << "Remove device: " << device_name << std::endl; + if (current_ccd == device_name) + current_ccd = ""; } -void ObsIndiClient::newProperty(INDI::Property property) +void ObsIndiClient::handleProperty(INDI::Property property) { std::string device_name = property.getDeviceName(); std::string property_name = property.getName(); auto ptype = property.getType(); - std::cout << "New property: " << property_name << " for device " << device_name << " (" << ptype << ")" << std::endl; - if (ptype != INDI_BLOB) - return; - - INDI::BaseDevice dev = getDevice(device_name.c_str()); - if (!(dev.getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE)) - return; - - auto ccd = std::make_pair(property_name, device_name); - ccds.push_back(ccd); - if (ccd == current_ccd) - { - std::cout << "Enable blob data on " << property_name << ":" << device_name << std::endl; - setBLOBMode(B_ALSO, device_name.c_str(), property_name.c_str()); + switch (ptype) { + case INDI_BLOB: { + std::cout << "blob property: " << property_name + << " for device " << device_name + << " current_ccd = " << current_ccd + << std::endl; + if (current_ccd == device_name) { + devices[device_name].property_name = property_name; + devices[device_name].isCCD = true; + std::cout << "Enable blob data on " << property_name << ":" << device_name << std::endl; + setBLOBMode(B_ALSO, device_name.c_str(), property_name.c_str()); + } else { + INDI::BaseDevice dev = getDevice(device_name.c_str()); + if (!(dev.getDriverInterface() & INDI::BaseDevice::CCD_INTERFACE)) + break; + devices[device_name].property_name = property_name; + devices[device_name].isCCD = true; + } + break; + } + case INDI_NUMBER: { + //std::cout << "number property: " << property_name << " for device " << device_name << std::endl; + INumberVectorProperty *numberProp = property.getNumber(); + for (int i = 0; i < numberProp->nnp; i++) + { + //std::cout << " " << numberProp->np[i].name << " = " << numberProp->np[i].value << std::endl; + std::string name = numberProp->np[i].name; + int value = numberProp->np[i].value; + if (property_name == "CCD_FRAME") { + if (name == "WIDTH") + devices[device_name].capture_width = value; + else if (name == "HEIGHT") + devices[device_name].capture_height = value; + } + else if (property_name == "CCD_STREAM_FRAME") { + if (name == "WIDTH") + devices[device_name].stream_width = value; + else if (name == "HEIGHT") + devices[device_name].stream_height = value; + } + } + break; + } + case INDI_TEXT : { + //std::cout << "text property: " << property_name << " for device " << device_name << std::endl; + ITextVectorProperty *textProp = property.getText(); + /*for (int i = 0; i < textProp->ntp; i++) + { + std::cout << " " << textProp->tp[i].name << " = " << textProp->tp[i].text << std::endl; + }*/ + break; + } + case INDI_SWITCH : { + //std::cout << "switch property: " << property_name << " for device " << device_name << std::endl; + ISwitchVectorProperty *switchProp = property.getSwitch(); + for (int i = 0; i < switchProp->nsp; i++) + { + std::string name = switchProp->sp[i].name; + int value = switchProp->sp[i].s; + //std::cout << " " << name << " = " << value << std::endl; + if (property_name == "CCD_TRANSFER_FORMAT") { + if (name == "FORMAT_FITS" && value) + devices[device_name].capture_format = ObsIndiClient::Device::FORMAT_FITS; + else if (name == "FORMAT_NATIVE" && value) + devices[device_name].capture_format = ObsIndiClient::Device::FORMAT_NATIVE; + else if (name == "FORMAT_XISF" && value) + devices[device_name].capture_format = ObsIndiClient::Device::FORMAT_XISF; + } else if (property_name == "CCD_VIDEO_STREAM") { + // TODO: some drivers can send stream 1 frame after streaming off. So if we receive frame exactly + // after stream off, drop it + if (name == "STREAM_ON" && value) + devices[device_name].video_method = ObsIndiClient::Device::METHOD_STREAM; + else if (name == "STREAM_OFF" && value) + devices[device_name].video_method = ObsIndiClient::Device::METHOD_CAPTURE; + } else if (property_name == "CCD_STREAM_ENCODER") { + if (name == "RAW" && value) { + devices[device_name].stream_format = ObsIndiClient::Device::FORMAT_RAW; + } else if (name == "MJPEG" && value) { + devices[device_name].stream_format = ObsIndiClient::Device::FORMAT_MJPEG; + } + } + } + break; + } + case INDI_LIGHT : { + //std::cout << "light property: " << property_name << " for device " << device_name << std::endl; + break; + } + default: + break; } } +void ObsIndiClient::newProperty(INDI::Property property) +{ + handleProperty(property); +} + void ObsIndiClient::updateProperty(INDI::Property property) { // Called when a property is updated @@ -363,29 +480,22 @@ void ObsIndiClient::updateProperty(INDI::Property property) auto prop = std::make_pair(property_name, device_name); - if (prop == current_ccd) + if (device_name == current_ccd && devices[current_ccd].property_name == property_name && devices[current_ccd].isCCD) { - // std::cout << "CCD updated: " << property.getName() << " for device " << property.getDeviceName() << std::endl; auto blobProp = property.getBLOB(); for (auto blob : *blobProp) processBLOB(&blob); + } else { + handleProperty(property); } } void ObsIndiClient::removeProperty(INDI::Property property) { std::string device_name = property.getDeviceName(); - - // Called when a property is removed std::string property_name = property.getName(); std::cout << "Property removed: " << property_name << " for device " << device_name << std::endl; - auto ccd = std::make_pair(property_name, device_name); - auto it = std::find(ccds.begin(), ccds.end(), ccd); - if (it != ccds.end()) - { - ccds.erase(it); - } } void ObsIndiClient::newMessage(INDI::BaseDevice dp, int messageID) diff --git a/src/indi_client.hh b/src/indi_client.hh index 9d7d501..a963e63 100644 --- a/src/indi_client.hh +++ b/src/indi_client.hh @@ -18,34 +18,66 @@ #include #include #include +#include #include +#include #include #include class ObsIndiClient : public INDI::BaseClient { +public: + class Device { + public: + bool isCCD = false; + bool blobEnabled = false; + + enum video_method_e { + METHOD_CAPTURE = 0, + METHOD_STREAM = 1, + } video_method = METHOD_CAPTURE; + + enum capture_format_e { + FORMAT_FITS = 0, + FORMAT_NATIVE = 1, + FORMAT_XISF = 2, + } capture_format = FORMAT_NATIVE; + + enum stream_format_e { + FORMAT_MJPEG = 0, + FORMAT_RAW = 1, + } stream_format = FORMAT_RAW; + std::string raw_format; + + size_t capture_width = 0, capture_height = 0; + size_t stream_width = 0, stream_height = 0; + std::string device_name; + std::string property_name; + public: + bool operator == (const Device& other) + { + return device_name == other.device_name; + } + }; private: uint8_t fitsGetPixel(const std::vector& data, size_t w, size_t h, int bitpix, int x, int y, int plane) const; void indiFillFrameJpeg(const uint8_t *data, size_t size); void indiFillFrameFITS(uint8_t *data, size_t size); + void handleProperty(INDI::Property property); public: ObsIndiClient(std::mutex &mutex) : frame_mutex(mutex) {} bool connectServer() override; bool disconnectServer(int exit_code = 0) override; - std::pair ccddev_decode(const std::string& ccddev); - std::string ccddev_encode(const std::pair &ccd); - int indiFrameWidth() const; int indiFrameHeight() const; const std::vector& indiFrame() const; - std::list> indiCCDs() const; - std::pair indiCurrentCCD() const; - void selectCCD(const std::string &ccddev); - void selectCCD(const std::pair &ccd); + std::list indiCCDs() const; + std::string indiCurrentCCD() const; + void selectCCD(const std::string &ccd); void indiFillFrame(IBLOB *indiBlob); void dummyFillFrame(); @@ -60,9 +92,9 @@ public: private: std::mutex &frame_mutex; - std::list devices; - size_t width, height; + size_t width = 0; + size_t height = 0; std::vector rgba_frame; - std::pair current_ccd; - std::list> ccds; + std::string current_ccd; + std::map devices; }; diff --git a/src/plugin.cc b/src/plugin.cc index ab4d1bb..e46d8d0 100644 --- a/src/plugin.cc +++ b/src/plugin.cc @@ -150,8 +150,7 @@ static obs_properties_t *indilib_source_get_properties(void *data) if (s->connected) { usleep(100000); for (auto ccd : s->indiClient->indiCCDs()) { - auto ccddev = s->indiClient->ccddev_encode(ccd); - obs_property_list_add_string(ccd_list, ccddev.c_str(), ccddev.c_str()); + obs_property_list_add_string(ccd_list, ccd.c_str(), ccd.c_str()); } } } @@ -174,8 +173,7 @@ static obs_properties_t *indilib_source_get_properties(void *data) if (s->connected) { usleep(100000); for (auto ccd : s->indiClient->indiCCDs()) { - auto ccddev = s->indiClient->ccddev_encode(ccd); - obs_property_list_add_string(ccd_list, ccddev.c_str(), ccddev.c_str()); + obs_property_list_add_string(ccd_list, ccd.c_str(), ccd.c_str()); } } } @@ -195,17 +193,13 @@ static obs_properties_t *indilib_source_get_properties(void *data) [](void *data, obs_properties_t *props, obs_property_t *property, obs_data_t *settings) -> bool { IndiObsSource *s = reinterpret_cast(data); - std::string ccddev = obs_data_get_string(settings, "ccd"); - auto ccd = s->indiClient->ccddev_decode(ccddev); - if (ccd != s->indiClient->indiCurrentCCD()) { - s->indiClient->selectCCD(ccd); - } + std::string ccd = obs_data_get_string(settings, "ccd"); + s->indiClient->selectCCD(ccd); return true; }, s); for (auto ccd : s->indiClient->indiCCDs()) { - auto ccddev = s->indiClient->ccddev_encode(ccd); - obs_property_list_add_string(list, ccddev.c_str(), ccddev.c_str()); + obs_property_list_add_string(list, ccd.c_str(), ccd.c_str()); } return props;