diff --git a/indi-asi/CMakeLists.txt b/indi-asi/CMakeLists.txt index abdc18d5c..d1f0782c2 100644 --- a/indi-asi/CMakeLists.txt +++ b/indi-asi/CMakeLists.txt @@ -38,6 +38,7 @@ endif() set(indi_asi_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/asi_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/asi_ccd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usb_utils.cpp ) add_executable(indi_asi_ccd ${indi_asi_SRCS}) @@ -50,6 +51,7 @@ endif() set(indi_asi_single_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/asi_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/asi_single_ccd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usb_utils.cpp ) add_executable(indi_asi_single_ccd ${indi_asi_single_SRCS}) @@ -124,16 +126,15 @@ install(TARGETS indi_asi_rotator RUNTIME DESTINATION bin) install(TARGETS asi_camera_test RUNTIME DESTINATION bin) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/indi_asi.xml DESTINATION ${INDI_DATA_DIR}) -########### get_asi_serials ########### -add_executable(get_asi_serials ${CMAKE_CURRENT_SOURCE_DIR}/get_asi_serials.cpp) +########### test-usb-utils ########### +add_executable(test-usb-utils ${CMAKE_CURRENT_SOURCE_DIR}/test-usb-utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/usb_utils.cpp) IF (APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework IOKit -framework CoreFoundation") -target_link_libraries(get_asi_serials ${ASI_LIBRARIES} ${LIBUSB_LIBRARIES}) +target_link_libraries(test-usb-utils ${LIBUSB_LIBRARIES}) ELSE() -target_link_libraries(get_asi_serials ${ASI_LIBRARIES} ${USB1_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} usb-1.0) +target_link_libraries(test-usb-utils ${INDI_LIBRARIES} ${USB1_LIBRARIES} usb-1.0) ENDIF() if (CMAKE_SYSTEM_NAME MATCHES "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm*") -target_link_libraries(get_asi_serials rt) +target_link_libraries(test-usb-utils rt) endif() - diff --git a/indi-asi/asi_base.cpp b/indi-asi/asi_base.cpp index 553ba10ff..78c0adaf6 100644 --- a/indi-asi/asi_base.cpp +++ b/indi-asi/asi_base.cpp @@ -22,6 +22,7 @@ #include "asi_base.h" #include "asi_helpers.h" +#include "usb_utils.h" #include "config.h" @@ -277,6 +278,7 @@ void ASIBase::workerExposure(const std::atomic_bool &isAboutToQuit, float durati ASIStopExposure(mCameraInfo.CameraID); ASICloseCamera(mCameraInfo.CameraID); + LOGF_INFO("Attempting USB reset for device %s...", mCameraInfo.Name); resetUSBDevice(); LOG_INFO("Reopening camera after reset..."); @@ -1633,143 +1635,18 @@ void ASIBase::addFITSKeywords(INDI::CCDChip *targetChip, std::vector -#include -#include - -int main() -{ - printf("=== ZWO Camera Serial Numbers ===\n\n"); - - // First get serials through ASI SDK - printf("Via ASI SDK:\n"); - int numDevices = ASIGetNumOfConnectedCameras(); - if(numDevices <= 0) - { - printf("No ZWO cameras detected via SDK.\n"); - } - else - { - ASI_CAMERA_INFO ASICameraInfo; - for(int i = 0; i < numDevices; i++) - { - ASIGetCameraProperty(&ASICameraInfo, i); - ASI_SN asi_sn; - if (ASIGetSerialNumber(i, &asi_sn) == ASI_SUCCESS) - { - printf("%s: ", ASICameraInfo.Name); - for (int j = 0; j < 8; ++j) - printf("%02x", asi_sn.id[j] & 0xff); - printf("\n"); - } - } - } - - // Now get serials through libusb - printf("\nVia LibUSB:\n"); - libusb_context *ctx = NULL; - libusb_device **list = NULL; - ssize_t count; - - int ret = libusb_init(&ctx); - if (ret < 0) - { - fprintf(stderr, "Failed to initialize libusb: %s\n", libusb_error_name(ret)); - return -1; - } - - count = libusb_get_device_list(ctx, &list); - if (count < 0) - { - fprintf(stderr, "Failed to get device list: %s\n", libusb_error_name(count)); - libusb_exit(ctx); - return -1; - } - - // ZWO's vendor ID is 0x03c3 - for (ssize_t i = 0; i < count; i++) - { - libusb_device *device = list[i]; - struct libusb_device_descriptor desc; - ret = libusb_get_device_descriptor(device, &desc); - if (ret < 0) - continue; - - if (desc.idVendor == 0x03c3) - { - libusb_device_handle *handle; - ret = libusb_open(device, &handle); - if (ret < 0) - continue; - - unsigned char string[256]; - - printf("Camera at Bus %d, Port %d:\n", - libusb_get_bus_number(device), - libusb_get_port_number(device)); - - if (desc.iProduct > 0) - { - ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, string, sizeof(string)); - if (ret > 0) - printf(" Product: %s\n", string); - } - - if (desc.iSerialNumber > 0) - { - ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, string, sizeof(string)); - if (ret > 0) - printf(" Serial: %s\n", string); - } - - libusb_close(handle); - } - } - - libusb_free_device_list(list, 1); - libusb_exit(ctx); - - return 0; -} diff --git a/indi-asi/test-usb-utils.cpp b/indi-asi/test-usb-utils.cpp new file mode 100644 index 000000000..812bd24d5 --- /dev/null +++ b/indi-asi/test-usb-utils.cpp @@ -0,0 +1,84 @@ +/* + SPDX-FileCopyrightText: 2025 Jasem Mutlaq + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "usb_utils.h" +#include +#include + +void printUsage(const char *progName) +{ + printf("Usage: %s [unbindWait powerSuspend rediscoverWait]\n", progName); + printf("Example: %s 0x03c3 \"ZWO ASI120MC-S\"\n", progName); + printf("Optional delays (microseconds):\n"); + printf(" unbindWait: delay after unbinding (default: 1000000)\n"); + printf(" powerSuspend: time in suspend state (default: 2000000)\n"); + printf(" rediscoverWait: time to wait for rediscovery (default: 5000000)\n"); +} + +void testDevice(uint16_t vendorId, const char *productName, int unbindWait, int powerSuspend, int rediscoverWait) +{ + printf("\nTesting device: VID=0x%04x Product='%s'\n", vendorId, productName); + printf("Using delays (microseconds):\n"); + printf(" Unbind wait: %d\n", unbindWait); + printf(" Power suspend: %d\n", powerSuspend); + printf(" Rediscover wait: %d\n", rediscoverWait); + + std::string path = USBUtils::findDevicePath(vendorId, productName, nullptr); + if (!path.empty()) + { + printf("Found device path: %s\n", path.c_str()); + + printf("Testing USB reset...\n"); + if (USBUtils::resetDevice(vendorId, productName, nullptr, unbindWait, powerSuspend, rediscoverWait)) + { + printf("USB reset successful\n"); + } + else + { + printf("USB reset failed\n"); + } + } + else + { + printf("Device not found\n"); + } +} + +int main(int argc, char *argv[]) +{ + if (argc != 3 && argc != 6) + { + printUsage(argv[0]); + return 1; + } + + uint16_t vendorId; + if (sscanf(argv[1], "0x%hx", &vendorId) != 1) + { + printf("Invalid vendor ID format. Use hex format like 0x03c3\n"); + return 1; + } + + int unbindWait = 1000000; // Default: 1 second + int powerSuspend = 2000000; // Default: 2 seconds + int rediscoverWait = 5000000; // Default: 5 seconds + + if (argc == 6) + { + unbindWait = atoi(argv[3]); + powerSuspend = atoi(argv[4]); + rediscoverWait = atoi(argv[5]); + + if (unbindWait <= 0 || powerSuspend <= 0 || rediscoverWait <= 0) + { + printf("Invalid delay values. All delays must be positive.\n"); + return 1; + } + } + + testDevice(vendorId, argv[2], unbindWait, powerSuspend, rediscoverWait); + return 0; +} diff --git a/indi-asi/usb_utils.cpp b/indi-asi/usb_utils.cpp new file mode 100644 index 000000000..ec3209a99 --- /dev/null +++ b/indi-asi/usb_utils.cpp @@ -0,0 +1,204 @@ +/* + SPDX-FileCopyrightText: 2025 Jasem Mutlaq + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "usb_utils.h" +#include +#include +#include +#include +#include +#include +#include + +namespace USBUtils +{ + +void log(const char *deviceName, const char *format, ...) +{ + char message[512]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + if (deviceName) + DEBUGFDEVICE(deviceName, INDI::Logger::DBG_DEBUG, "%s", message); + else + printf("%s\n", message); +} + +std::string findDevicePath(uint16_t vendorId, const char *productName, const char *deviceName) +{ + libusb_context *ctx = nullptr; + libusb_device **list = nullptr; + std::string result; + + int ret = libusb_init(&ctx); + if (ret < 0) + return result; + + ssize_t count = libusb_get_device_list(ctx, &list); + if (count < 0) + { + libusb_exit(ctx); + return result; + } + + for (ssize_t i = 0; i < count; i++) + { + libusb_device *device = list[i]; + struct libusb_device_descriptor desc; + ret = libusb_get_device_descriptor(device, &desc); + if (ret < 0) + continue; + + if (desc.idVendor == vendorId) + { + libusb_device_handle *handle; + ret = libusb_open(device, &handle); + if (ret < 0) + continue; + + unsigned char string[256]; + if (desc.iProduct > 0) + { + ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, string, sizeof(string)); + if (ret > 0) + { + // Check if libusb product string is included in the supplied product name + // This handles cases where supplied name might have manufacturer prefix + if (strstr(productName, (char *)string) != nullptr) + { + uint8_t bus = libusb_get_bus_number(device); + uint8_t ports[7]; + int port_count = libusb_get_port_numbers(device, ports, sizeof(ports)); + + if (port_count > 0) + { + char path[128]; + if (port_count == 1) + { + snprintf(path, sizeof(path), "%d-%d", bus, ports[0]); + } + else + { + snprintf(path, sizeof(path), "%d-%d", bus, ports[0]); + for (int p = 1; p < port_count; p++) + { + char temp[8]; + snprintf(temp, sizeof(temp), ".%d", ports[p]); + strcat(path, temp); + } + } + result = path; + log(deviceName, "Found device at path: %s", path); + } + } + } + } + libusb_close(handle); + if (!result.empty()) + break; + } + } + + libusb_free_device_list(list, 1); + libusb_exit(ctx); + return result; +} + +bool resetDevice(uint16_t vendorId, const char *productName, const char *deviceName, + int unbindWaitTime, int powerSuspendTime, int rediscoverWaitTime) +{ + std::string devicePath = findDevicePath(vendorId, productName, deviceName); + if (devicePath.empty()) + { + log(deviceName, "Failed to find device path for VID=0x%04x Product='%s'", vendorId, productName); + return false; + } + + // Check bind/unbind permissions + char unbind_path[512] = "/sys/bus/usb/drivers/usb/unbind"; + char bind_path[512] = "/sys/bus/usb/drivers/usb/bind"; + + log(deviceName, "Checking USB reset permissions:"); + if (access(unbind_path, W_OK) == 0) + log(deviceName, "Unbind path (%s): Writable", unbind_path); + else + log(deviceName, "Unbind path (%s): Not writable (error: %s)", unbind_path, strerror(errno)); + + if (access(bind_path, W_OK) == 0) + log(deviceName, "Bind path (%s): Writable", bind_path); + else + log(deviceName, "Bind path (%s): Not writable (error: %s)", bind_path, strerror(errno)); + + // First try to unbind the device + FILE *unbind_fp = fopen(unbind_path, "w"); + if (!unbind_fp) + { + log(deviceName, "Failed to open unbind file: %s", strerror(errno)); + return false; + } + + log(deviceName, "Unbinding device %s (wait: %d us)", devicePath.c_str(), unbindWaitTime); + fprintf(unbind_fp, "%s\n", devicePath.c_str()); + fclose(unbind_fp); + usleep(unbindWaitTime); + + // Try to reset the parent hub port + char parent_path[512]; + snprintf(parent_path, sizeof(parent_path), "/sys/bus/usb/devices/%s/..", devicePath.c_str()); + char real_parent[512]; + if (realpath(parent_path, real_parent)) + { + // Try port power control + char port_power[512]; + snprintf(port_power, sizeof(port_power), "%s/power/level", real_parent); + if (access(port_power, W_OK) == 0) + { + log(deviceName, "Cycling port power on %s (suspend: %d us)", real_parent, powerSuspendTime); + FILE *power_fp = fopen(port_power, "w"); + if (power_fp) + { + fprintf(power_fp, "suspend\n"); + fclose(power_fp); + usleep(powerSuspendTime); + + power_fp = fopen(port_power, "w"); + if (power_fp) + { + fprintf(power_fp, "on\n"); + fclose(power_fp); + } + } + } + else + { + log(deviceName, "No write access to power control: %s", port_power); + } + } + + // Now rebind the device + snprintf(bind_path, sizeof(bind_path), "/sys/bus/usb/drivers/usb/bind"); + FILE *bind_fp = fopen(bind_path, "w"); + if (!bind_fp) + { + log(deviceName, "Failed to open bind file: %s", strerror(errno)); + return false; + } + + log(deviceName, "Rebinding device %s", devicePath.c_str()); + fprintf(bind_fp, "%s\n", devicePath.c_str()); + fclose(bind_fp); + + // Wait for device to be rediscovered + log(deviceName, "Waiting for device to be rediscovered (wait: %d us)...", rediscoverWaitTime); + usleep(rediscoverWaitTime); + + return true; +} + +} diff --git a/indi-asi/usb_utils.h b/indi-asi/usb_utils.h new file mode 100644 index 000000000..e40588e9d --- /dev/null +++ b/indi-asi/usb_utils.h @@ -0,0 +1,30 @@ +/* + SPDX-FileCopyrightText: 2025 Jasem Mutlaq + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +namespace USBUtils +{ + +// Find USB device path by vendor ID and product name +// Returns empty string if device not found +std::string findDevicePath(uint16_t vendorId, const char *productName, const char *deviceName = nullptr); + +// Reset USB device by unbinding and rebinding +// Returns true if successful +// Delays are in microseconds: +// - unbindWaitTime: delay after unbinding (default: 1 second) +// - powerSuspendTime: time to stay in suspend state (default: 2 seconds) +// - rediscoverWaitTime: time to wait for device rediscovery (default: 5 seconds) +bool resetDevice(uint16_t vendorId, const char *productName, const char *deviceName = nullptr, + int unbindWaitTime = 1000000, int powerSuspendTime = 2000000, int rediscoverWaitTime = 5000000); + +// Internal logging function +void log(const char *deviceName, const char *format, ...); +}