001package jmri.server.json.loconet; 002 003import static jmri.server.json.loconet.JsonLoconetSlotUsage.LOCONET_SLOT_USAGE; 004 005import com.fasterxml.jackson.databind.JsonNode; 006import com.fasterxml.jackson.databind.ObjectMapper; 007import com.fasterxml.jackson.databind.node.ObjectNode; 008import java.util.List; 009import javax.servlet.http.HttpServletResponse; 010import jmri.InstanceManager; 011import jmri.jmrix.loconet.LnConstants; 012import jmri.jmrix.loconet.LocoNetSlot; 013import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 014import jmri.jmrix.loconet.SlotManager; 015import jmri.jmrix.loconet.SlotMapEntry.SlotType; 016import jmri.server.json.JsonException; 017import jmri.server.json.JsonHttpService; 018import jmri.server.json.JsonRequest; 019 020/** 021 * Reports real LocoNet loco-slot table usage (used/free/total) for the 022 * active connection's command station. 023 * 024 * Read-only -- iterates the SlotManager's already in-memory slot array, 025 * sending no new LocoNet traffic of its own. "total" reflects the 026 * connected command station's real per-model capacity (from the same 027 * LnCommandStationType data JMRI already uses to auto-detect the 028 * connection), not JMRI's generic upper bound, which is sized for the 029 * largest command station JMRI knows about and would overstate real 030 * capacity for anything smaller. 031 * 032 * @author Andrew Deak Copyright (C) 2026 033 */ 034public class JsonLoconetSlotUsageHttpService extends JsonHttpService { 035 036 public JsonLoconetSlotUsageHttpService(ObjectMapper mapper) { 037 super(mapper); 038 } 039 040 @Override 041 public JsonNode doGet(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 042 List<LocoNetSystemConnectionMemo> memos = InstanceManager.getList(LocoNetSystemConnectionMemo.class); 043 if (memos.isEmpty()) { 044 throw new JsonException(503, "No LocoNet connection is available", request.id); 045 } 046 SlotManager sm = memos.get(0).getSlotManager(); 047 if (sm == null) { 048 throw new JsonException(503, "No LocoNet slot manager is available", request.id); 049 } 050 int total = 0; 051 int used = 0; 052 for (int i = 0; i < sm.getNumSlots(); i++) { 053 LocoNetSlot s = sm.slot(i); 054 if (s.getSlotType() == SlotType.LOCO) { 055 total++; 056 // An Idle slot (dispatched or released, address still 057 // resident but not actively driven) is capacity the command 058 // station will reclaim for the next engine added. Only 059 // In-Use/Common hold a live, actively-driven address that 060 // isn't available for a different loco. 061 int status = s.slotStatus() & LnConstants.LOCOSTAT_MASK; 062 if (status == LnConstants.LOCO_IN_USE || status == LnConstants.LOCO_COMMON) { 063 used++; 064 } 065 } 066 } 067 ObjectNode dataNode = mapper.createObjectNode(); 068 dataNode.put("commandStation", 069 sm.getCommandStationType() != null ? sm.getCommandStationType().getName() : null); 070 dataNode.put("used", used); 071 dataNode.put("total", total); 072 dataNode.put("free", total - used); 073 return message(LOCONET_SLOT_USAGE, dataNode, request.id); 074 } 075 076 @Override 077 public JsonNode doPost(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 078 // read-only service -- POST behaves the same as GET 079 return doGet(type, name, data, request); 080 } 081 082 @Override 083 public JsonNode doGetList(String type, JsonNode data, JsonRequest request) throws JsonException { 084 // single-object service, no list distinction -- same as GET 085 return doGet(type, "", data, request); 086 } 087 088 @Override 089 public JsonNode doSchema(String type, boolean server, JsonRequest request) throws JsonException { 090 // An unrecognized type must throw, not silently return this 091 // service's own schema regardless of what was asked for -- matches 092 // the pattern used by every other JsonHttpService (e.g. 093 // JsonIdTagHttpService). 094 if (LOCONET_SLOT_USAGE.equals(type)) { 095 return doSchema(type, server, 096 "jmri/server/json/loconet/loconetSlotUsage-server.json", 097 "jmri/server/json/loconet/loconetSlotUsage-client.json", 098 request.id); 099 } 100 throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 101 Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), request.id); 102 } 103}