diff --git a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java index 4408f52ae0..339694769f 100644 --- a/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/cscore/CameraServerJNI.java @@ -89,6 +89,8 @@ public class CameraServerJNI { public static native boolean setSourcePixelFormat(int source, int pixelFormat); public static native boolean setSourceResolution(int source, int width, int height); public static native boolean setSourceFPS(int source, int fps); + public static native boolean setSourceConfigJson(int source, String config); + public static native String getSourceConfigJson(int source); public static native VideoMode[] enumerateSourceVideoModes(int source); public static native int[] enumerateSourceSinks(int source); public static native int copySource(int source); diff --git a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java index b7d6bd522c..cac344ced1 100644 --- a/cscore/src/main/java/edu/wpi/cscore/VideoSource.java +++ b/cscore/src/main/java/edu/wpi/cscore/VideoSource.java @@ -270,6 +270,45 @@ public class VideoSource implements AutoCloseable { return CameraServerJNI.setSourceFPS(m_handle, fps); } + /** + * Set video mode and properties from a JSON configuration string. + * + *

The format of the JSON input is: + * + *

+   * {
+   *     "pixel format": "MJPEG", "YUYV", etc
+   *     "width": video mode width
+   *     "height": video mode height
+   *     "fps": video mode fps
+   *     "brightness": percentage brightness
+   *     "white balance": "auto", "hold", or value
+   *     "exposure": "auto", "hold", or value
+   *     "properties": [
+   *         {
+   *             "name": property name
+   *             "value": property value
+   *         }
+   *     ]
+   * }
+   * 
+ * + * @param config configuration + * @return True if set successfully + */ + public boolean setConfigJson(String config) { + return CameraServerJNI.setSourceConfigJson(m_handle, config); + } + + /** + * Get a JSON configuration string. + * + * @return JSON configuration string + */ + public String getConfigJson() { + return CameraServerJNI.getSourceConfigJson(m_handle); + } + /** * Get the actual FPS. * diff --git a/cscore/src/main/native/cpp/MjpegServerImpl.cpp b/cscore/src/main/native/cpp/MjpegServerImpl.cpp index 40c4a36b04..73d312cdb3 100644 --- a/cscore/src/main/native/cpp/MjpegServerImpl.cpp +++ b/cscore/src/main/native/cpp/MjpegServerImpl.cpp @@ -67,7 +67,8 @@ static const char* startRootPage = "\n" "
\n" "

\n" - "Settings JSON\n" + "Settings JSON |\n" + "Source Config JSON\n" "

\n" "
\n"; static const char* endRootPage = "
"; @@ -340,7 +341,7 @@ void MjpegServerImpl::ConnThread::SendHTMLHeadTitle( // Send the root html file with controls for all the settable properties. void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os, SourceImpl& source, bool header) { - if (header) SendHeader(os, 200, "OK", "application/x-javascript"); + if (header) SendHeader(os, 200, "OK", "text/html"); SendHTMLHeadTitle(os); os << startRootPage; @@ -453,7 +454,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os, // Send a JSON file which is contains information about the source parameters. void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os, SourceImpl& source, bool header) { - if (header) SendHeader(os, 200, "OK", "application/x-javascript"); + if (header) SendHeader(os, 200, "OK", "application/json"); os << "{\n\"controls\": [\n"; wpi::SmallVector properties_vec; @@ -728,7 +729,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() { return; } - enum { kCommand, kStream, kGetSettings, kRootPage } kind; + enum { kCommand, kStream, kGetSettings, kGetSourceConfig, kRootPage } kind; wpi::StringRef parameters; size_t pos; @@ -748,6 +749,9 @@ void MjpegServerImpl::ConnThread::ProcessRequest() { } else if (req.find("GET /settings") != wpi::StringRef::npos && req.find(".json") != wpi::StringRef::npos) { kind = kGetSettings; + } else if (req.find("GET /config") != wpi::StringRef::npos && + req.find(".json") != wpi::StringRef::npos) { + kind = kGetSourceConfig; } else if (req.find("GET /input") != wpi::StringRef::npos && req.find(".json") != wpi::StringRef::npos) { kind = kGetSettings; @@ -806,6 +810,17 @@ void MjpegServerImpl::ConnThread::ProcessRequest() { else SendError(os, 404, "Resource not found"); break; + case kGetSourceConfig: + SDEBUG("request for JSON file"); + if (auto source = GetSource()) { + SendHeader(os, 200, "OK", "application/json"); + CS_Status status = CS_OK; + os << source->GetConfigJson(&status); + os.flush(); + } else { + SendError(os, 404, "Resource not found"); + } + break; case kRootPage: SDEBUG("request for root page"); SendHeader(os, 200, "OK", "text/html"); diff --git a/cscore/src/main/native/cpp/SourceImpl.cpp b/cscore/src/main/native/cpp/SourceImpl.cpp index cf8ef0dcfa..e2209570a7 100644 --- a/cscore/src/main/native/cpp/SourceImpl.cpp +++ b/cscore/src/main/native/cpp/SourceImpl.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include "Log.h" @@ -161,6 +162,272 @@ bool SourceImpl::SetFPS(int fps, CS_Status* status) { return SetVideoMode(mode, status); } +bool SourceImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) { + wpi::json j; + try { + j = wpi::json::parse(config); + } catch (wpi::json::parse_error e) { + SWARNING("SetConfigJson: parse error at byte " << e.byte << ": " + << e.what()); + *status = CS_PROPERTY_WRITE_FAILED; + return false; + } + return SetConfigJson(j, status); +} + +bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) { + VideoMode mode; + + // pixel format + if (config.count("pixel format") != 0) { + try { + auto str = config.at("pixel format").get(); + wpi::StringRef s(str); + if (s.equals_lower("mjpeg")) { + mode.pixelFormat = cs::VideoMode::kMJPEG; + } else if (s.equals_lower("yuyv")) { + mode.pixelFormat = cs::VideoMode::kYUYV; + } else if (s.equals_lower("rgb565")) { + mode.pixelFormat = cs::VideoMode::kRGB565; + } else if (s.equals_lower("bgr")) { + mode.pixelFormat = cs::VideoMode::kBGR; + } else if (s.equals_lower("gray")) { + mode.pixelFormat = cs::VideoMode::kGray; + } else { + SWARNING("SetConfigJson: could not understand pixel format value '" + << str << '\''); + } + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read pixel format: " << e.what()); + } + } + + // width + if (config.count("width") != 0) { + try { + mode.width = config.at("width").get(); + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read width: " << e.what()); + } + } + + // height + if (config.count("height") != 0) { + try { + mode.height = config.at("height").get(); + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read height: " << e.what()); + } + } + + // fps + if (config.count("fps") != 0) { + try { + mode.fps = config.at("fps").get(); + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read fps: " << e.what()); + } + } + + // if all of video mode is set, use SetVideoMode, otherwise piecemeal it + if (mode.pixelFormat != VideoMode::kUnknown && mode.width != 0 && + mode.height != 0 && mode.fps != 0) { + SINFO("SetConfigJson: setting video mode to pixelFormat " + << mode.pixelFormat << ", width " << mode.width << ", height " + << mode.height << ", fps " << mode.fps); + SetVideoMode(mode, status); + } else { + if (mode.pixelFormat != cs::VideoMode::kUnknown) { + SINFO("SetConfigJson: setting pixelFormat " << mode.pixelFormat); + SetPixelFormat(static_cast(mode.pixelFormat), + status); + } + if (mode.width != 0 && mode.height != 0) { + SINFO("SetConfigJson: setting width " << mode.width << ", height " + << mode.height); + SetResolution(mode.width, mode.height, status); + } + if (mode.fps != 0) { + SINFO("SetConfigJson: setting fps " << mode.fps); + SetFPS(mode.fps, status); + } + } + + // brightness + if (config.count("brightness") != 0) { + try { + int val = config.at("brightness").get(); + SINFO("SetConfigJson: setting brightness to " << val); + SetBrightness(val, status); + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read brightness: " << e.what()); + } + } + + // white balance + if (config.count("white balance") != 0) { + try { + auto& setting = config.at("white balance"); + if (setting.is_string()) { + auto str = setting.get(); + wpi::StringRef s(str); + if (s.equals_lower("auto")) { + SINFO("SetConfigJson: setting white balance to auto"); + SetWhiteBalanceAuto(status); + } else if (s.equals_lower("hold")) { + SINFO("SetConfigJson: setting white balance to hold current"); + SetWhiteBalanceHoldCurrent(status); + } else { + SWARNING("SetConfigJson: could not understand white balance value '" + << str << '\''); + } + } else { + int val = setting.get(); + SINFO("SetConfigJson: setting white balance to " << val); + SetWhiteBalanceManual(val, status); + } + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read white balance: " << e.what()); + } + } + + // exposure + if (config.count("exposure") != 0) { + try { + auto& setting = config.at("exposure"); + if (setting.is_string()) { + auto str = setting.get(); + wpi::StringRef s(str); + if (s.equals_lower("auto")) { + SINFO("SetConfigJson: setting exposure to auto"); + SetExposureAuto(status); + } else if (s.equals_lower("hold")) { + SINFO("SetConfigJson: setting exposure to hold current"); + SetExposureHoldCurrent(status); + } else { + SWARNING("SetConfigJson: could not understand exposure value '" + << str << '\''); + } + } else { + int val = setting.get(); + SINFO("SetConfigJson: setting exposure to " << val); + SetExposureManual(val, status); + } + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read exposure: " << e.what()); + } + } + + // properties + if (config.count("properties") != 0) { + for (auto&& prop : config.at("properties")) { + std::string name; + try { + name = prop.at("name").get(); + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read property name: " << e.what()); + continue; + } + int n = GetPropertyIndex(name); + try { + auto& v = prop.at("value"); + if (v.is_string()) { + std::string val = v.get(); + SINFO("SetConfigJson: setting property '" << name << "' to '" << val + << '\''); + SetStringProperty(n, val, status); + } else if (v.is_boolean()) { + bool val = v.get(); + SINFO("SetConfigJson: setting property '" << name << "' to " << val); + SetProperty(n, val, status); + } else { + int val = v.get(); + SINFO("SetConfigJson: setting property '" << name << "' to " << val); + SetProperty(n, val, status); + } + } catch (wpi::json::exception e) { + SWARNING("SetConfigJson: could not read property value: " << e.what()); + continue; + } + } + } + + return true; +} + +std::string SourceImpl::GetConfigJson(CS_Status* status) { + std::string rv; + wpi::raw_string_ostream os(rv); + GetConfigJsonObject(status).dump(os, 4); + os.flush(); + return rv; +} + +wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) { + wpi::json j; + + // pixel format + wpi::StringRef pixelFormat; + switch (m_mode.pixelFormat) { + case VideoMode::kMJPEG: + pixelFormat = "mjpeg"; + break; + case VideoMode::kYUYV: + pixelFormat = "yuyv"; + break; + case VideoMode::kRGB565: + pixelFormat = "rgb565"; + break; + case VideoMode::kBGR: + pixelFormat = "bgr"; + break; + case VideoMode::kGray: + pixelFormat = "gray"; + break; + default: + break; + } + if (!pixelFormat.empty()) j.emplace("pixel format", pixelFormat); + + // width + if (m_mode.width != 0) j.emplace("width", m_mode.width); + + // height + if (m_mode.height != 0) j.emplace("height", m_mode.height); + + // fps + if (m_mode.fps != 0) j.emplace("fps", m_mode.fps); + + // TODO: output brightness, white balance, and exposure? + + // properties + wpi::json props; + wpi::SmallVector propVec; + for (int p : EnumerateProperties(propVec, status)) { + wpi::json prop; + wpi::SmallString<128> strBuf; + prop.emplace("name", GetPropertyName(p, strBuf, status)); + switch (GetPropertyKind(p)) { + case CS_PROP_BOOLEAN: + prop.emplace("value", static_cast(GetProperty(p, status))); + break; + case CS_PROP_INTEGER: + case CS_PROP_ENUM: + prop.emplace("value", GetProperty(p, status)); + break; + case CS_PROP_STRING: + prop.emplace("value", GetStringProperty(p, strBuf, status)); + break; + default: + continue; + } + props.emplace_back(prop); + } + if (props.is_array()) j.emplace("properties", props); + + return j; +} + std::vector SourceImpl::EnumerateVideoModes( CS_Status* status) const { if (!m_properties_cached && !CacheProperties(status)) diff --git a/cscore/src/main/native/cpp/SourceImpl.h b/cscore/src/main/native/cpp/SourceImpl.h index 4a93018e07..d74d8faf6a 100644 --- a/cscore/src/main/native/cpp/SourceImpl.h +++ b/cscore/src/main/native/cpp/SourceImpl.h @@ -27,6 +27,10 @@ #include "PropertyContainer.h" #include "cscore_cpp.h" +namespace wpi { +class json; +} // namespace wpi + namespace cs { class Notifier; @@ -127,6 +131,11 @@ class SourceImpl : public PropertyContainer { virtual bool SetResolution(int width, int height, CS_Status* status); virtual bool SetFPS(int fps, CS_Status* status); + bool SetConfigJson(wpi::StringRef config, CS_Status* status); + virtual bool SetConfigJson(const wpi::json& config, CS_Status* status); + std::string GetConfigJson(CS_Status* status); + virtual wpi::json GetConfigJsonObject(CS_Status* status); + std::vector EnumerateVideoModes(CS_Status* status) const; std::unique_ptr AllocImage(VideoMode::PixelFormat pixelFormat, diff --git a/cscore/src/main/native/cpp/cscore_c.cpp b/cscore/src/main/native/cpp/cscore_c.cpp index 21106ec190..26d2c71075 100644 --- a/cscore/src/main/native/cpp/cscore_c.cpp +++ b/cscore/src/main/native/cpp/cscore_c.cpp @@ -170,6 +170,15 @@ CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) { return cs::SetSourceFPS(source, fps, status); } +CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config, + CS_Status* status) { + return cs::SetSourceConfigJson(source, config, status); +} + +char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status) { + return cs::ConvertToC(cs::GetSourceConfigJson(source, status)); +} + CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count, CS_Status* status) { auto vec = cs::EnumerateSourceVideoModes(source, status); diff --git a/cscore/src/main/native/cpp/cscore_cpp.cpp b/cscore/src/main/native/cpp/cscore_cpp.cpp index a4654c416a..c40e9e26c0 100644 --- a/cscore/src/main/native/cpp/cscore_cpp.cpp +++ b/cscore/src/main/native/cpp/cscore_cpp.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "Handle.h" #include "Instance.h" @@ -324,6 +325,44 @@ bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) { return data->source->SetFPS(fps, status); } +bool SetSourceConfigJson(CS_Source source, wpi::StringRef config, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->SetConfigJson(config, status); +} + +bool SetSourceConfigJson(CS_Source source, const wpi::json& config, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return false; + } + return data->source->SetConfigJson(config, status); +} + +std::string GetSourceConfigJson(CS_Source source, CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return std::string{}; + } + return data->source->GetConfigJson(status); +} + +wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data) { + *status = CS_INVALID_HANDLE; + return wpi::json{}; + } + return data->source->GetConfigJsonObject(status); +} + std::vector EnumerateSourceVideoModes(CS_Source source, CS_Status* status) { auto data = Instance::GetInstance().GetSource(source); diff --git a/cscore/src/main/native/cpp/cscore_oo.cpp b/cscore/src/main/native/cpp/cscore_oo.cpp index d41e07632a..cbb6b148ba 100644 --- a/cscore/src/main/native/cpp/cscore_oo.cpp +++ b/cscore/src/main/native/cpp/cscore_oo.cpp @@ -7,8 +7,15 @@ #include "cscore_oo.h" +#include + using namespace cs; +wpi::json VideoSource::GetConfigJsonObject() const { + m_status = 0; + return GetSourceConfigJsonObject(m_handle, &m_status); +} + std::vector VideoSource::EnumerateProperties() const { wpi::SmallVector handles_buf; CS_Status status = 0; diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp index 06c3c1e283..d5aac6b7c0 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -751,6 +751,36 @@ Java_edu_wpi_cscore_CameraServerJNI_setSourceFPS return val; } +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: setSourceConfigJson + * Signature: (ILjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_cscore_CameraServerJNI_setSourceConfigJson + (JNIEnv* env, jclass, jint source, jstring config) +{ + CS_Status status = 0; + auto val = cs::SetSourceConfigJson(source, JStringRef{env, config}, &status); + CheckStatus(env, status); + return val; +} + +/* + * Class: edu_wpi_cscore_CameraServerJNI + * Method: getSourceConfigJson + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL +Java_edu_wpi_cscore_CameraServerJNI_getSourceConfigJson + (JNIEnv* env, jclass, jint source) +{ + CS_Status status = 0; + auto val = cs::GetSourceConfigJson(source, &status); + CheckStatus(env, status); + return MakeJString(env, val); +} + /* * Class: edu_wpi_cscore_CameraServerJNI * Method: enumerateSourceVideoModes diff --git a/cscore/src/main/native/include/cscore_c.h b/cscore/src/main/native/include/cscore_c.h index ba728f8bf0..7b3b1e0413 100644 --- a/cscore/src/main/native/include/cscore_c.h +++ b/cscore/src/main/native/include/cscore_c.h @@ -289,6 +289,9 @@ CS_Bool CS_SetSourcePixelFormat(CS_Source source, CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height, CS_Status* status); CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status); +CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config, + CS_Status* status); +char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status); CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count, CS_Status* status); CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count, diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index fcce7e0279..1156ba64b2 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -25,6 +25,10 @@ namespace cv { class Mat; } // namespace cv +namespace wpi { +class json; +} // namespace wpi + /** CameraServer (cscore) namespace */ namespace cs { @@ -221,6 +225,12 @@ bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat, bool SetSourceResolution(CS_Source source, int width, int height, CS_Status* status); bool SetSourceFPS(CS_Source source, int fps, CS_Status* status); +bool SetSourceConfigJson(CS_Source source, wpi::StringRef config, + CS_Status* status); +bool SetSourceConfigJson(CS_Source source, const wpi::json& config, + CS_Status* status); +std::string GetSourceConfigJson(CS_Source source, CS_Status* status); +wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status); std::vector EnumerateSourceVideoModes(CS_Source source, CS_Status* status); wpi::ArrayRef EnumerateSourceSinks(CS_Source source, diff --git a/cscore/src/main/native/include/cscore_oo.h b/cscore/src/main/native/include/cscore_oo.h index 8eec60a0e0..ca23b5dab0 100644 --- a/cscore/src/main/native/include/cscore_oo.h +++ b/cscore/src/main/native/include/cscore_oo.h @@ -253,6 +253,56 @@ class VideoSource { */ bool SetFPS(int fps); + /** + * Set video mode and properties from a JSON configuration string. + * + * The format of the JSON input is: + * + *
+   * {
+   *     "pixel format": "MJPEG", "YUYV", etc
+   *     "width": video mode width
+   *     "height": video mode height
+   *     "fps": video mode fps
+   *     "brightness": percentage brightness
+   *     "white balance": "auto", "hold", or value
+   *     "exposure": "auto", "hold", or value
+   *     "properties": [
+   *         {
+   *             "name": property name
+   *             "value": property value
+   *         }
+   *     ]
+   * }
+   * 
+ * + * @param config configuration + * @return True if set successfully + */ + bool SetConfigJson(wpi::StringRef config); + + /** + * Set video mode and properties from a JSON configuration object. + * + * @param config configuration + * @return True if set successfully + */ + bool SetConfigJson(const wpi::json& config); + + /** + * Get a JSON configuration string. + * + * @return JSON configuration string + */ + std::string GetConfigJson() const; + + /** + * Get a JSON configuration object. + * + * @return JSON configuration object + */ + wpi::json GetConfigJsonObject() const; + /** * Get the actual FPS. * diff --git a/cscore/src/main/native/include/cscore_oo.inl b/cscore/src/main/native/include/cscore_oo.inl index 74f19b0af8..027ac12453 100644 --- a/cscore/src/main/native/include/cscore_oo.inl +++ b/cscore/src/main/native/include/cscore_oo.inl @@ -170,6 +170,21 @@ inline bool VideoSource::SetFPS(int fps) { return SetSourceFPS(m_handle, fps, &m_status); } +inline bool VideoSource::SetConfigJson(wpi::StringRef config) { + m_status = 0; + return SetSourceConfigJson(m_handle, config, &m_status); +} + +inline bool VideoSource::SetConfigJson(const wpi::json& config) { + m_status = 0; + return SetSourceConfigJson(m_handle, config, &m_status); +} + +inline std::string VideoSource::GetConfigJson() const { + m_status = 0; + return GetSourceConfigJson(m_handle, &m_status); +} + inline double VideoSource::GetActualFPS() const { m_status = 0; return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED, diff --git a/wpiutil/src/main/native/include/wpi/Logger.h b/wpiutil/src/main/native/include/wpi/Logger.h index 409354645f..20aa8ee3dc 100644 --- a/wpiutil/src/main/native/include/wpi/Logger.h +++ b/wpiutil/src/main/native/include/wpi/Logger.h @@ -58,7 +58,8 @@ class Logger { #define WPI_LOG(logger_inst, level, x) \ do { \ ::wpi::Logger& WPI_logger_ = logger_inst; \ - if (WPI_logger_.min_level() <= level && WPI_logger_.HasLogger()) { \ + if (WPI_logger_.min_level() <= static_cast(level) && \ + WPI_logger_.HasLogger()) { \ ::wpi::SmallString<128> log_buf_; \ ::wpi::raw_svector_ostream log_os_{log_buf_}; \ log_os_ << x; \