#pragma once #include #include #include #include #include #include #include namespace secsgem::gem { // ALCD bit-flag categories (E5 §10.3 / E30 §6.13). The lower 7 bits // of ALCD are a bitmap — an alarm may carry multiple categories at // once (e.g. an irrecoverable equipment-safety fault is 0x10 | 0x02). // Bit 7 is the alarm SET/CLEAR flag, applied at emit time and not // part of the category bitmap. enum class AlarmSeverity : uint8_t { PersonalSafety = 0x01, EquipmentSafety = 0x02, ParameterError = 0x04, // parameter control error ParameterWarning = 0x08, // parameter control warning Irrecoverable = 0x10, EquipmentStatus = 0x20, // equipment status warning Attention = 0x40, // attention flag (lowest-priority) }; inline constexpr uint8_t severity_mask = 0x7F; inline bool has_severity(uint8_t alcd, AlarmSeverity bit) { return (alcd & static_cast(bit)) != 0; } inline uint8_t severity_bits(uint8_t alcd) { return alcd & severity_mask; } struct Alarm { uint32_t id; std::string text; // Lower 7 bits of ALCD: severity bitmap (see AlarmSeverity). Bit 7 // marks set vs cleared and is applied at emit time. uint8_t severity_category; bool has(AlarmSeverity bit) const { return has_severity(severity_category, bit); } bool is_safety() const { return has(AlarmSeverity::PersonalSafety) || has(AlarmSeverity::EquipmentSafety); } }; enum class AlarmAck : uint8_t { Accept = 0, Error = 1, }; class AlarmRegistry { public: void add(Alarm a) { by_id_.insert_or_assign(a.id, std::move(a)); } std::optional get(uint32_t id) const { auto it = by_id_.find(id); if (it == by_id_.end()) return std::nullopt; return it->second; } std::vector all() const { std::vector out; out.reserve(by_id_.size()); for (const auto& [_, a] : by_id_) out.push_back(a); return out; } bool has(uint32_t id) const { return by_id_.count(id) > 0; } // S5F3 enable / disable. AlarmAck set_enabled(uint32_t id, bool enable) { if (!by_id_.count(id)) return AlarmAck::Error; if (enable) enabled_.insert(id); else enabled_.erase(id); return AlarmAck::Accept; } bool enabled(uint32_t id) const { return enabled_.count(id) > 0; } // Trigger set / clear; returns the ALCD byte to put on the wire (bit 7 // is the set flag, lower 7 carry the category). std::nullopt on unknown. std::optional set_active(uint32_t id) { auto it = by_id_.find(id); if (it == by_id_.end()) return std::nullopt; active_.insert(id); return static_cast((it->second.severity_category & 0x7F) | 0x80); } std::optional clear_active(uint32_t id) { auto it = by_id_.find(id); if (it == by_id_.end()) return std::nullopt; active_.erase(id); return static_cast(it->second.severity_category & 0x7F); } bool active(uint32_t id) const { return active_.count(id) > 0; } private: std::map by_id_; std::set enabled_; std::set active_; }; } // namespace secsgem::gem