/*----------------------------------------------------------------------------*/ /* Copyright (c) 2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ /*----------------------------------------------------------------------------*/ #include "VisionStatus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace uv = wpi::uv; #define SERVICE "/service/camera" std::shared_ptr VisionStatus::GetInstance() { static auto visStatus = std::make_shared(private_init{}); return visStatus; } void VisionStatus::SetLoop(std::shared_ptr loop) { m_loop = std::move(loop); auto refreshTimer = wpi::uv::Timer::Create(m_loop); refreshTimer->timeout.connect([this] { RefreshCameraList(); }); refreshTimer->Unreference(); auto devEvents = wpi::uv::FsEvent::Create(m_loop); devEvents->fsEvent.connect([refreshTimer](const char* fn, int flags) { if (wpi::StringRef(fn).startswith("video")) refreshTimer->Start(wpi::uv::Timer::Time(200)); }); devEvents->Start("/dev"); devEvents->Unreference(); refreshTimer->Start(wpi::uv::Timer::Time(200)); } void VisionStatus::UpdateCameraList() { wpi::json j = {{"type", "cameraList"}, {"cameras", wpi::json::array()}}; auto& cams = j["cameras"]; for (const auto& caminfo : m_cameraInfo) { wpi::json cam = {{"dev", caminfo.info.dev}, {"path", caminfo.info.path}, {"name", caminfo.info.name}, {"otherPaths", wpi::json::array()}, {"modes", wpi::json::array()}}; auto& otherPaths = cam["otherPaths"]; for (const auto& path : caminfo.info.otherPaths) otherPaths.emplace_back(path); auto& modes = cam["modes"]; for (const auto& mode : caminfo.modes) { wpi::json jmode; wpi::StringRef pixelFormatStr; switch (mode.pixelFormat) { case cs::VideoMode::kMJPEG: pixelFormatStr = "mjpeg"; break; case cs::VideoMode::kYUYV: pixelFormatStr = "yuyv"; break; case cs::VideoMode::kRGB565: pixelFormatStr = "rgb565"; break; case cs::VideoMode::kBGR: pixelFormatStr = "bgr"; break; case cs::VideoMode::kGray: pixelFormatStr = "gray"; break; default: continue; } jmode.emplace("pixelFormat", pixelFormatStr); jmode.emplace("width", mode.width); jmode.emplace("height", mode.height); jmode.emplace("fps", mode.fps); modes.emplace_back(jmode); } cams.emplace_back(cam); } cameraList(j); } void VisionStatus::RefreshCameraList() { struct RefreshCameraWorkReq : public uv::WorkReq { std::vector cameraInfo; }; auto workReq = std::make_shared(); workReq->work.connect([r = workReq.get()] { CS_Status status = 0; for (auto&& caminfo : cs::EnumerateUsbCameras(&status)) { cs::UsbCamera camera{"usbcam", caminfo.dev}; r->cameraInfo.emplace_back(); r->cameraInfo.back().info = std::move(caminfo); r->cameraInfo.back().modes = camera.EnumerateVideoModes(); } }); workReq->afterWork.connect([ this, r = workReq.get() ] { m_cameraInfo = std::move(r->cameraInfo); UpdateCameraList(); }); uv::QueueWork(m_loop, workReq); } void VisionStatus::RunSvc(const char* cmd, std::function onFail) { struct SvcWorkReq : public uv::WorkReq { SvcWorkReq(const char* cmd_, std::function onFail_) : cmd(cmd_), onFail(onFail_) {} const char* cmd; std::function onFail; wpi::SmallString<128> err; }; auto workReq = std::make_shared(cmd, onFail); workReq->work.connect([r = workReq.get()] { int fd = open(SERVICE "/supervise/control", O_WRONLY | O_NDELAY); if (fd == -1) { wpi::raw_svector_ostream os(r->err); if (errno == ENXIO) os << "unable to control service: supervise not running"; else os << "unable to control service: " << std::strerror(errno); } else { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); if (write(fd, r->cmd, std::strlen(r->cmd)) == -1) { wpi::raw_svector_ostream os(r->err); os << "error writing command: " << std::strerror(errno); } close(fd); } }); workReq->afterWork.connect([r = workReq.get()] { if (r->onFail && !r->err.empty()) r->onFail(r->err.str()); }); uv::QueueWork(m_loop, workReq); } void VisionStatus::Up(std::function onFail) { RunSvc("u", onFail); UpdateStatus(); } void VisionStatus::Down(std::function onFail) { RunSvc("d", onFail); UpdateStatus(); } void VisionStatus::Terminate(std::function onFail) { RunSvc("t", onFail); UpdateStatus(); } void VisionStatus::Kill(std::function onFail) { RunSvc("k", onFail); UpdateStatus(); } void VisionStatus::UpdateStatus() { struct StatusWorkReq : public uv::WorkReq { bool enabled = false; wpi::SmallString<128> status; }; auto workReq = std::make_shared(); workReq->work.connect([r = workReq.get()] { wpi::raw_svector_ostream os(r->status); // check to make sure supervise is running int fd = open(SERVICE "/supervise/ok", O_WRONLY | O_NDELAY); if (fd == -1) { if (errno == ENXIO) os << "supervise not running"; else os << "unable to open supervise/ok: " << std::strerror(errno); return; } close(fd); // read the status data fd = open(SERVICE "/supervise/status", O_RDONLY | O_NDELAY); if (fd == -1) { os << "unable to open supervise/status: " << std::strerror(errno); return; } uint8_t status[18]; int nr = read(fd, status, sizeof status); close(fd); if (nr < static_cast(sizeof status)) { os << "unable to read supervise/status: "; if (nr == -1) os << std::strerror(errno); else os << "bad format"; return; } // decode the status data (based on daemontools svstat.c) uint32_t pid = (static_cast(status[15]) << 24) | (static_cast(status[14]) << 16) | (static_cast(status[13]) << 8) | (static_cast(status[12])); bool paused = status[16]; auto want = status[17]; uint64_t when = (static_cast(status[0]) << 56) | (static_cast(status[1]) << 48) | (static_cast(status[2]) << 40) | (static_cast(status[3]) << 32) | (static_cast(status[4]) << 24) | (static_cast(status[5]) << 16) | (static_cast(status[6]) << 8) | (static_cast(status[7])); // constant is from daemontools tai.h uint64_t now = 4611686018427387914ULL + static_cast(std::time(nullptr)); if (now >= when) when = now - when; else when = 0; // convert to status string if (pid) os << "up (pid " << pid << ") "; else os << "down "; os << when << " seconds"; if (pid && paused) os << ", paused"; if (!pid && want == 'u') os << ", want up"; if (pid && want == 'd') os << ", want down"; if (pid) r->enabled = true; }); workReq->afterWork.connect([this, r = workReq.get()] { wpi::json j = {{"type", "visionStatus"}, {"visionServiceEnabled", r->enabled}, {"visionServiceStatus", r->status.str()}}; update(j); }); uv::QueueWork(m_loop, workReq); } void VisionStatus::ConsoleLog(uv::Buffer& buf, size_t len) { wpi::json j = {{"type", "visionLog"}, {"data", wpi::StringRef(buf.base, len)}}; log(j); }