Files
secs-gem/include/secsgem/gem/store/e84_ports.hpp
T
raphael 77197b9c1e e84: per-port FSM via E84PortStore
E84 (Parallel I/O) is fundamentally per-load-port: each port has its
own ten-wire handshake with the AMHS.  Earlier revisions modeled it
as a single equipment-wide FSM; this commit refactors to a per-port
store, so multi-LP tools can run independent handshakes in parallel.

Public API change in EquipmentDataModel:
  E84StateMachine e84;   -> removed
  E84PortStore    e84_ports;  // create(port_id), get(port_id), ...

Convenience pass-throughs: E84PortStore::on_signal_change auto-creates
the port on first use (ergonomic for demos); applications should call
create() explicitly with their full port set.

The two existing callsites (test_gem300_scenario, test_e87_wire_scenarios)
are updated.  The multi-LP test now demonstrates the actual win:
interleaved LP1 load + LP2 unload handshakes that reach their
respective Ready states without sequencing, and an ES on LP1 that
does NOT affect LP2 — exactly the failure mode the previous design
couldn't catch.

Five new dedicated tests in test_e84_ports.cpp for the store itself.

COMPLIANCE.md §4i updated: row now reflects per-port design.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 11:50:18 +02:00

88 lines
2.6 KiB
C++

#pragma once
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "secsgem/gem/e84_state.hpp"
namespace secsgem::gem {
// E84 (Parallel I/O) is per-load-port: each port has its own ten-wire
// handshake with the AMHS. Earlier revisions of this codebase modeled
// E84 as a single equipment-wide FSM; multi-LP tools need one FSM per
// port. E84PortStore keys per-port FSMs by port_id (1-based, matching
// LoadPortStore).
class E84PortStore {
public:
using StateChangeHandler =
std::function<void(uint8_t port_id, E84State from, E84State to,
E84Signal trigger)>;
E84PortStore() = default;
E84PortStore(const E84PortStore&) = delete;
E84PortStore& operator=(const E84PortStore&) = delete;
E84PortStore(E84PortStore&&) = delete;
E84PortStore& operator=(E84PortStore&&) = delete;
void set_state_change_handler(StateChangeHandler h) { on_change_ = std::move(h); }
bool create(uint8_t port_id) {
if (ports_.count(port_id)) return false;
auto fsm = std::make_unique<E84StateMachine>();
const uint8_t pid = port_id;
fsm->set_state_change_handler(
[this, pid](E84State f, E84State t, E84Signal sig) {
if (on_change_) on_change_(pid, f, t, sig);
});
ports_.emplace(port_id, std::move(fsm));
return true;
}
bool has(uint8_t port_id) const { return ports_.count(port_id) > 0; }
E84StateMachine* get(uint8_t port_id) {
auto it = ports_.find(port_id);
return it == ports_.end() ? nullptr : it->second.get();
}
const E84StateMachine* get(uint8_t port_id) const {
auto it = ports_.find(port_id);
return it == ports_.end() ? nullptr : it->second.get();
}
// Convenience pass-throughs that auto-create the port on first use.
// Real applications should call create() explicitly with their
// expected port set, but the implicit create keeps quick demos
// ergonomic.
bool on_signal_change(uint8_t port_id, E84Signal s, bool value) {
if (!ports_.count(port_id)) create(port_id);
auto* fsm = get(port_id);
if (!fsm) return false;
fsm->on_signal_change(s, value);
return true;
}
bool reset(uint8_t port_id) {
auto* fsm = get(port_id);
if (!fsm) return false;
fsm->reset();
return true;
}
std::size_t size() const { return ports_.size(); }
std::vector<uint8_t> ids() const {
std::vector<uint8_t> out;
out.reserve(ports_.size());
for (const auto& kv : ports_) out.push_back(kv.first);
return out;
}
private:
std::map<uint8_t, std::unique_ptr<E84StateMachine>> ports_;
StateChangeHandler on_change_;
};
} // namespace secsgem::gem