// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #include "glass/networktables/NTMechanism2D.h" #include #include #include #include #include #include #include #include "glass/other/Mechanism2D.h" using namespace glass; // Convert "#RRGGBB" hex color to ImU32 color static void ConvertColor(std::string_view in, ImU32* out) { if (in.size() != 7 || in[0] != '#') { return; } if (auto v = wpi::parse_integer(wpi::drop_front(in), 16)) { ImU32 val = v.value(); *out = IM_COL32((val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff, 255); } } namespace { class NTMechanismObjectModel; class NTMechanismGroupImpl final { public: NTMechanismGroupImpl(nt::NetworkTableInstance inst, std::string_view path, std::string_view name) : m_inst{inst}, m_path{path}, m_name{name} {} const char* GetName() const { return m_name.c_str(); } void ForEachObject(wpi::function_ref func); void NTUpdate(const nt::Event& event, std::string_view name); protected: nt::NetworkTableInstance m_inst; std::string m_path; std::string m_name; std::vector> m_objects; }; class NTMechanismObjectModel final : public MechanismObjectModel { public: NTMechanismObjectModel(nt::NetworkTableInstance inst, std::string_view path, std::string_view name) : m_group{inst, path, name}, m_typeTopic{inst.GetTopic(fmt::format("{}/.type", path))}, m_colorTopic{inst.GetTopic(fmt::format("{}/color", path))}, m_weightTopic{inst.GetTopic(fmt::format("{}/weight", path))}, m_angleTopic{inst.GetTopic(fmt::format("{}/angle", path))}, m_lengthTopic{inst.GetTopic(fmt::format("{}/length", path))} {} const char* GetName() const final { return m_group.GetName(); } void ForEachObject( wpi::function_ref func) final { m_group.ForEachObject(func); } const char* GetType() const final { return m_typeValue.c_str(); } ImU32 GetColor() const final { return m_colorValue; } double GetWeight() const final { return m_weightValue; } frc::Rotation2d GetAngle() const final { return m_angleValue; } units::meter_t GetLength() const final { return m_lengthValue; } bool NTUpdate(const nt::Event& event, std::string_view name); private: NTMechanismGroupImpl m_group; nt::Topic m_typeTopic; nt::Topic m_colorTopic; nt::Topic m_weightTopic; nt::Topic m_angleTopic; nt::Topic m_lengthTopic; std::string m_typeValue; ImU32 m_colorValue = IM_COL32_WHITE; double m_weightValue = 1.0; frc::Rotation2d m_angleValue; units::meter_t m_lengthValue = 0.0_m; }; } // namespace void NTMechanismGroupImpl::ForEachObject( wpi::function_ref func) { for (auto&& obj : m_objects) { func(*obj); } } void NTMechanismGroupImpl::NTUpdate(const nt::Event& event, std::string_view name) { if (name.empty()) { return; } std::string_view childName; std::tie(name, childName) = wpi::split(name, '/'); if (childName.empty()) { return; } auto it = std::lower_bound( m_objects.begin(), m_objects.end(), name, [](const auto& e, std::string_view name) { return e->GetName() < name; }); bool match = it != m_objects.end() && (*it)->GetName() == name; if (event.GetTopicInfo()) { if (event.flags & nt::EventFlags::kPublish) { if (!match) { it = m_objects.emplace( it, std::make_unique( m_inst, fmt::format("{}/{}", m_path, name), name)); match = true; } } if (match) { if ((*it)->NTUpdate(event, childName)) { m_objects.erase(it); } } } else if (event.GetValueEventData()) { if (match) { (*it)->NTUpdate(event, childName); } } } bool NTMechanismObjectModel::NTUpdate(const nt::Event& event, std::string_view childName) { if (auto info = event.GetTopicInfo()) { if (info->topic == m_typeTopic.GetHandle()) { if (event.flags & nt::EventFlags::kUnpublish) { return true; } } else if (info->topic != m_colorTopic.GetHandle() && info->topic != m_weightTopic.GetHandle() && info->topic != m_angleTopic.GetHandle() && info->topic != m_lengthTopic.GetHandle()) { m_group.NTUpdate(event, childName); } } else if (auto valueData = event.GetValueEventData()) { if (valueData->topic == m_typeTopic.GetHandle()) { if (valueData->value && valueData->value.IsString()) { m_typeValue = valueData->value.GetString(); } } else if (valueData->topic == m_colorTopic.GetHandle()) { if (valueData->value && valueData->value.IsString()) { ConvertColor(valueData->value.GetString(), &m_colorValue); } } else if (valueData->topic == m_weightTopic.GetHandle()) { if (valueData->value && valueData->value.IsDouble()) { m_weightValue = valueData->value.GetDouble(); } } else if (valueData->topic == m_angleTopic.GetHandle()) { if (valueData->value && valueData->value.IsDouble()) { m_angleValue = units::degree_t{valueData->value.GetDouble()}; } } else if (valueData->topic == m_lengthTopic.GetHandle()) { if (valueData->value && valueData->value.IsDouble()) { m_lengthValue = units::meter_t{valueData->value.GetDouble()}; } } else { m_group.NTUpdate(event, childName); } } return false; } class NTMechanism2DModel::RootModel final : public MechanismRootModel { public: RootModel(nt::NetworkTableInstance inst, std::string_view path, std::string_view name) : m_group{inst, path, name}, m_xTopic{inst.GetTopic(fmt::format("{}/x", path))}, m_yTopic{inst.GetTopic(fmt::format("{}/y", path))} {} const char* GetName() const final { return m_group.GetName(); } void ForEachObject( wpi::function_ref func) final { m_group.ForEachObject(func); } bool NTUpdate(const nt::Event& event, std::string_view childName); frc::Translation2d GetPosition() const override { return m_pos; }; private: NTMechanismGroupImpl m_group; nt::Topic m_xTopic; nt::Topic m_yTopic; frc::Translation2d m_pos; }; bool NTMechanism2DModel::RootModel::NTUpdate(const nt::Event& event, std::string_view childName) { if (auto info = event.GetTopicInfo()) { if (info->topic == m_xTopic.GetHandle() || info->topic == m_yTopic.GetHandle()) { if (event.flags & nt::EventFlags::kUnpublish) { return true; } } else { m_group.NTUpdate(event, childName); } } else if (auto valueData = event.GetValueEventData()) { if (valueData->topic == m_xTopic.GetHandle()) { if (valueData->value && valueData->value.IsDouble()) { m_pos = frc::Translation2d{units::meter_t{valueData->value.GetDouble()}, m_pos.Y()}; } } else if (valueData->topic == m_yTopic.GetHandle()) { if (valueData->value && valueData->value.IsDouble()) { m_pos = frc::Translation2d{ m_pos.X(), units::meter_t{valueData->value.GetDouble()}}; } } else { m_group.NTUpdate(event, childName); } } return false; } NTMechanism2DModel::NTMechanism2DModel(std::string_view path) : NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {} NTMechanism2DModel::NTMechanism2DModel(nt::NetworkTableInstance inst, std::string_view path) : m_inst{inst}, m_path{fmt::format("{}/", path)}, m_tableSub{inst, {{m_path}}, {{nt::PubSubOption::SendAll(true), nt::PubSubOption::Periodic(0.05)}}}, m_nameTopic{m_inst.GetTopic(fmt::format("{}/.name", path))}, m_dimensionsTopic{m_inst.GetTopic(fmt::format("{}/dims", path))}, m_bgColorTopic{m_inst.GetTopic(fmt::format("{}/backgroundColor", path))}, m_poller{m_inst}, m_dimensionsValue{1_m, 1_m} { m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic | nt::EventFlags::kValueAll | nt::EventFlags::kImmediate); } NTMechanism2DModel::~NTMechanism2DModel() = default; void NTMechanism2DModel::Update() { for (auto&& event : m_poller.ReadQueue()) { if (auto info = event.GetTopicInfo()) { auto name = wpi::drop_front(info->name, m_path.size()); if (name.empty() || name[0] == '.') { continue; } std::string_view childName; std::tie(name, childName) = wpi::split(name, '/'); if (childName.empty()) { continue; } auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name, [](const auto& e, std::string_view name) { return e->GetName() < name; }); bool match = it != m_roots.end() && (*it)->GetName() == name; if (event.flags & nt::EventFlags::kPublish) { if (!match) { it = m_roots.emplace( it, std::make_unique( m_inst, fmt::format("{}{}", m_path, name), name)); match = true; } } if (match) { if ((*it)->NTUpdate(event, childName)) { m_roots.erase(it); } } } else if (auto valueData = event.GetValueEventData()) { if (valueData->topic == m_nameTopic.GetHandle()) { // .name if (valueData->value && valueData->value.IsString()) { m_nameValue = valueData->value.GetString(); } } else if (valueData->topic == m_dimensionsTopic.GetHandle()) { // dims if (valueData->value && valueData->value.IsDoubleArray()) { auto arr = valueData->value.GetDoubleArray(); if (arr.size() == 2) { m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]}, units::meter_t{arr[1]}}; } } } else if (valueData->topic == m_bgColorTopic.GetHandle()) { // backgroundColor if (valueData->value && valueData->value.IsString()) { ConvertColor(valueData->value.GetString(), &m_bgColorValue); } } else { auto fullName = nt::Topic{valueData->topic}.GetName(); auto name = wpi::drop_front(fullName, m_path.size()); if (name.empty() || name[0] == '.') { continue; } std::string_view childName; std::tie(name, childName) = wpi::split(name, '/'); if (childName.empty()) { continue; } auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name, [](const auto& e, std::string_view name) { return e->GetName() < name; }); if (it != m_roots.end() && (*it)->GetName() == name) { if ((*it)->NTUpdate(event, childName)) { m_roots.erase(it); } } } } } } bool NTMechanism2DModel::Exists() { return m_nameTopic.Exists(); } bool NTMechanism2DModel::IsReadOnly() { return false; } void NTMechanism2DModel::ForEachRoot( wpi::function_ref func) { for (auto&& obj : m_roots) { func(*obj); } }