From d44a43469a722e6381465358c0308c58174433ed Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 25 Oct 2024 14:02:00 +0200 Subject: [PATCH] feat: mock sfera communication and add example JP and SP (#323) --- .github/CODEOWNERS | 2 +- sfera-mock/README.md | 16 + sfera-mock/pom.xml | 7 +- .../sbb/sferamock/exchange/TenantService.java | 4 +- .../sferamock/messages/MessageListener.java | 23 - .../sbb/sferamock/messages/SferaHandler.java | 112 - .../messages/StaticSferaService.java | 93 - .../messages/common/SferaErrorCodes.java | 23 + .../messages/common/XmlDateHelper.java | 39 + .../sferamock/messages/common/XmlHelper.java | 44 + .../sferamock/messages/helper/XmlHelper.java | 43 - .../sferamock/messages/model/ClientId.java | 11 + .../sferamock/messages/model/CompanyCode.java | 7 + .../messages/model/HandshakeRejectReason.java | 5 + .../messages/model/OperationMode.java | 34 + .../messages/model/RequestContext.java | 22 + .../messages/model/SegmentIdentification.java | 7 + .../messages/model/TrainIdentification.java | 26 + .../services/JourneyProfileRepository.java | 40 + .../services/OperationModeSelector.java | 48 + .../services/RegistrationService.java | 82 + .../services/RequestContextRepository.java | 22 + .../services/SegmentProfileRepository.java | 41 + .../services/SferaApplicationService.java | 136 + .../sfera/IncomingMessageAdapter.java | 142 + .../sfera/MessageHeaderValidator.java | 40 + .../messages/sfera/ReplyPublisher.java | 82 + .../messages/sfera/SferaMessageCreator.java | 162 + .../messages/sfera/SferaMessagingConfig.java | 39 + .../sfera/SferaToInternalConverters.java | 51 + .../messages/sfera/SferaTopicHelper.java | 25 + .../src/main/resources/SFERA_3.0_custom.xsd | 6051 +++++++++++++++++ .../src/main/resources/application.yaml | 27 +- .../src/main/resources/jaxb-bindings.xjb | 2 +- ...2B_ReplyMessage_error_insufficientData.xml | 12 - ...RA_G2B_ReplyMessage_error_notAvailable.xml | 12 - ..._G2B_ReplyMessage_error_notImplemented.xml | 12 - ...SFERA_G2B_ReplyMessage_error_xmlSchema.xml | 12 - .../SFERA_G2B_ReplyMessage_handshake.xml | 10 - .../SFERA_G2B_Reply_JP_request_9232.xml | 1963 ------ .../SFERA_G2B_Reply_JP_request_9310.xml | 2407 ------- .../SFERA_G2B_Reply_JP_request_9315.xml | 2469 ------- .../SFERA_G2B_Reply_JP_request_9358.xml | 79 - .../SFERA_G2B_Reply_SP_request.xml | 717 -- .../SFERA_G2B_Reply_TC_request.xml | 20 - .../sfera_example_messages/SFERA_JP_4816.xml | 102 + .../sfera_example_messages/SFERA_JP_7839.xml | 50 + .../sfera_example_messages/SFERA_SP_1.xml | 57 + .../sfera_example_messages/SFERA_SP_2.xml | 312 + .../sfera_example_messages/SFERA_SP_3.xml | 144 + .../message-table.component.html | 32 + .../message-table.component.scss | 0 .../message-table/message-table.component.ts | 32 + .../sfera-observer.component.html | 73 +- .../sfera-observer.component.scss | 23 +- .../sfera-observer.component.ts | 336 +- .../app/sfera-observer/sfera-xml-creation.ts | 197 + .../app/simple-xml/simple-xml.component.ts | 18 +- webapp/src/environment/environment.model.ts | 1 + webapp/src/environment/environment.ts | 4 +- 60 files changed, 8551 insertions(+), 8051 deletions(-) delete mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/MessageListener.java delete mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/SferaHandler.java delete mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/StaticSferaService.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/SferaErrorCodes.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlDateHelper.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlHelper.java delete mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/helper/XmlHelper.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/ClientId.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/CompanyCode.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/HandshakeRejectReason.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/OperationMode.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/RequestContext.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/SegmentIdentification.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/TrainIdentification.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/OperationModeSelector.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RegistrationService.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RequestContextRepository.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/MessageHeaderValidator.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessagingConfig.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaToInternalConverters.java create mode 100644 sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaTopicHelper.java create mode 100644 sfera-mock/src/main/resources/SFERA_3.0_custom.xsd delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_insufficientData.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notAvailable.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notImplemented.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_xmlSchema.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_handshake.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9232.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9310.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9315.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9358.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_SP_request.xml delete mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_TC_request.xml create mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_4816.xml create mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_7839.xml create mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_1.xml create mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_2.xml create mode 100644 sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_3.xml create mode 100644 webapp/src/app/sfera-observer/message-table/message-table.component.html create mode 100644 webapp/src/app/sfera-observer/message-table/message-table.component.scss create mode 100644 webapp/src/app/sfera-observer/message-table/message-table.component.ts create mode 100644 webapp/src/app/sfera-observer/sfera-xml-creation.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 83dde013..b2d7284a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # default reviewers -* @mghilardelli @Grodien +* @mghilardelli @Grodien @rawi-coding diff --git a/sfera-mock/README.md b/sfera-mock/README.md index 963ddf58..0136b6de 100644 --- a/sfera-mock/README.md +++ b/sfera-mock/README.md @@ -5,3 +5,19 @@ - for SFERA e2e tests of mobile app ## Getting-Started + + +## Scenarios + +- Recipient: 0085 +- drivingMode: Read-Only +- architecture: BoardAdviceCalculation +- connectivity: Connected + +| Train number | Result | +|--------------|--------------------------| +| 4816 | Zürich HB - Aarau
| +| 7839 | Solothurn - Oberdorf SO | + +TODO: 29137 Lenzburg - Luzern https://miro.com/app/board/uXjVKK4zJFk=/?moveToWidget=3458764596975113381&cot=14 + diff --git a/sfera-mock/pom.xml b/sfera-mock/pom.xml index 3163057e..b26b045a 100644 --- a/sfera-mock/pom.xml +++ b/sfera-mock/pom.xml @@ -88,6 +88,11 @@ jaxb-runtime + + org.springframework + spring-oxm + + org.springframework.boot spring-boot-starter-test @@ -129,7 +134,7 @@ 3.2.0 - ${basedir}/src/main/resources/SFERA_2.01.xsd + ${basedir}/src/main/resources/SFERA_3.0_custom.xsd ${basedir}/src/main/resources/jaxb-bindings.xjb diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/exchange/TenantService.java b/sfera-mock/src/main/java/ch/sbb/sferamock/exchange/TenantService.java index 8e394454..c612bfb4 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/exchange/TenantService.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/exchange/TenantService.java @@ -11,7 +11,7 @@ @Service public class TenantService { - private static final Logger logger = LogManager.getLogger(TenantService.class); + private static final Logger log = LogManager.getLogger(TenantService.class); private final TenantConfig tenantConfig; @@ -25,7 +25,7 @@ public Tenant getByIssuerUri(String issuerUri) { ).findAny() .orElseThrow(() -> new IllegalArgumentException("unknown tenant")); - logger.info(String.format("Got tenant '%s' with issuer URI '%s'", tenant.getName(), tenant.getIssuerUri())); + log.info(String.format("Got tenant '%s' with issuer URI '%s'", tenant.getName(), tenant.getIssuerUri())); return tenant; } diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/MessageListener.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/MessageListener.java deleted file mode 100644 index 056fcbdf..00000000 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/MessageListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package ch.sbb.sferamock.messages; - -import java.util.function.Function; -import org.springframework.cloud.stream.binder.BinderHeaders; -import org.springframework.context.annotation.Bean; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.stereotype.Component; - -@Component -public class MessageListener { - - @Bean - public Function, Message> boardToGround(SferaHandler sferaHandler) { - return message -> { - String respone = sferaHandler.boardToGround(message); - if (respone == null) { - return null; - } - return MessageBuilder.withPayload(respone).setHeader(BinderHeaders.TARGET_DESTINATION, sferaHandler.replyTopic).build(); - }; - } -} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/SferaHandler.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/SferaHandler.java deleted file mode 100644 index f201685f..00000000 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/SferaHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -package ch.sbb.sferamock.messages; - -import static ch.sbb.sferamock.messages.helper.XmlHelper.objectToXml; -import static ch.sbb.sferamock.messages.helper.XmlHelper.xmlToObject; - -import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BReplyMessage; -import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GRequestMessage; -import com.solace.spring.cloud.stream.binder.messaging.SolaceHeaders; -import jakarta.xml.bind.JAXBException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.messaging.Message; -import org.springframework.stereotype.Component; - -@Component -public class SferaHandler { - - private static final Logger log = LoggerFactory.getLogger(SferaHandler.class); - - private final StaticSferaService staticSferaService; - public String replyTopic; - @Value("${spring.profiles.active}") - private String profile; - - public SferaHandler(StaticSferaService staticSferaService) { - this.staticSferaService = staticSferaService; - } - - public String boardToGround(Message message) { - String inputTopic = message.getHeaders().get(SolaceHeaders.DESTINATION).toString(); - SFERAG2BReplyMessage replyMessage; - - this.replyTopic = replyTopic(inputTopic); - - SFERAB2GRequestMessage sferab2GRequestMessage; - try { - sferab2GRequestMessage = xmlToObject(message.getPayload(), SFERAB2GRequestMessage.class); - log.info("B2G request received"); - replyMessage = request(sferab2GRequestMessage); - } catch (JAXBException e) { - log.error("Could not map xml to object", e); - replyMessage = staticSferaService.invalidXmlError(); - } - try { - return objectToXml(replyMessage); - } catch (JAXBException e) { - log.error("Could not map object to xml", e); - return null; - } - } - - private SFERAG2BReplyMessage request(SFERAB2GRequestMessage requestMessage) { - - if (requestMessage.getHandshakeRequest() != null) { - log.info("Send handshakeAck"); - return staticSferaService.handshake(); - // or HandshakeReject - // or Error - } else if (requestMessage.getB2GRequest() != null) { - return b2gRequest(requestMessage); - } - log.info("Send insufficient data"); - return staticSferaService.insufficientData(); - } - - private SFERAG2BReplyMessage b2gRequest(SFERAB2GRequestMessage requestMessage) { - if (requestMessage.getB2GRequest().getJPRequest() != null && !requestMessage.getB2GRequest().getJPRequest().isEmpty()) { - // assuming only one jp request - var jpRequest = requestMessage.getB2GRequest().getJPRequest().getFirst(); - var requestedTrainNumber = jpRequest.getTrainIdentification().getOTNID().getOperationalTrainNumber(); - var jpResult = staticSferaService.journeyProfile(requestedTrainNumber); - if (jpResult != null) { - log.info("Send JP for trainId={}", requestedTrainNumber); - return jpResult; - } else { - log.info("JP with trainId={} not available", requestedTrainNumber); - return staticSferaService.notAvailableError(); - } - // G2B_MessageResponse / result = “OK” no more recent JP - // or result = “ERROR” / dataFirstAvailable - // or result = “ERROR” / errorCode - - } - if (requestMessage.getB2GRequest().getSPRequest() != null && !requestMessage.getB2GRequest().getSPRequest().isEmpty()) { - log.info("Send static SP"); - return staticSferaService.segmentProfile(); - } - if (requestMessage.getB2GRequest().getTCRequest() != null && !requestMessage.getB2GRequest().getTCRequest().isEmpty()) { - log.info("Send static TC"); - return staticSferaService.trainCharcteristics(); - } - // combination of SP/TC/JP - // or C_DAS_C_AdviceRequest - // or PlaintextMessageRequest - // or ForceDrivingModeChangeRequest - // or PositionSpeedRequest - - log.info("b2g request not implemented"); - return staticSferaService.notImplementedError(); - } - - private String replyTopic(String topic) { - log.info("new message on topic={}", topic); - String[] topicParts = topic.split("/"); - String companyCode = topicParts[3]; - String trainIdentifier = topicParts[4]; - String clientId = topicParts[5]; - String env = this.profile.equals("local") ? "local/" : ""; - return env + "90940/2/G2B/" + companyCode + "/" + trainIdentifier + "/" + clientId; - } -} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/StaticSferaService.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/StaticSferaService.java deleted file mode 100644 index 17ea8151..00000000 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/StaticSferaService.java +++ /dev/null @@ -1,93 +0,0 @@ -package ch.sbb.sferamock.messages; - -import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BReplyMessage; -import ch.sbb.sferamock.messages.helper.XmlHelper; -import jakarta.xml.bind.JAXBException; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Service; - -@Service -public class StaticSferaService implements ApplicationRunner { - - private static final Logger log = LoggerFactory.getLogger(StaticSferaService.class); - - private static final String XML_RESOURCES_CLASSPATH = "classpath:sfera_example_messages/"; - private static final Map files = new HashMap<>(); - - static { - files.put(ResponseType.HANDSHAKE, "SFERA_G2B_ReplyMessage_handshake.xml"); - files.put(ResponseType.SP, "SFERA_G2B_Reply_SP_request.xml"); - files.put(ResponseType.TC, "SFERA_G2B_Reply_TC_request.xml"); - files.put(ResponseType.NOT_IMPLEMENTED, "SFERA_G2B_ReplyMessage_error_notImplemented.xml"); - files.put(ResponseType.XML_ERROR, "SFERA_G2B_ReplyMessage_error_xmlSchema.xml"); - files.put(ResponseType.NOT_AVAILABLE, "SFERA_G2B_ReplyMessage_error_notAvailable.xml"); - files.put(ResponseType.INSUFFICIENT_DATA, "SFERA_G2B_ReplyMessage_error_insufficientData.xml"); - files.put("9358", "SFERA_G2B_Reply_JP_request_9358.xml"); - files.put("9232", "SFERA_G2B_Reply_JP_request_9232.xml"); - files.put("9310", "SFERA_G2B_Reply_JP_request_9310.xml"); - files.put("9315", "SFERA_G2B_Reply_JP_request_9315.xml"); - } - - private final Map b2gReplies = new HashMap<>(); - - @Override - public void run(ApplicationArguments args) { - files.forEach((trainId, fileName) -> { - String filePath = XML_RESOURCES_CLASSPATH + fileName; - try { - this.b2gReplies.put(trainId, XmlHelper.xmlFileToObject(filePath, SFERAG2BReplyMessage.class)); - } catch (IOException | JAXBException e) { - log.error("failed to import static xml replies", e); - } - }); - log.info("imported {} static replies", this.b2gReplies.size()); - } - - public SFERAG2BReplyMessage journeyProfile(String trainId) { - return b2gReplies.get(trainId); - } - - public SFERAG2BReplyMessage handshake() { - return b2gReplies.get(ResponseType.HANDSHAKE); - } - - public SFERAG2BReplyMessage segmentProfile() { - return b2gReplies.get(ResponseType.SP); - } - - public SFERAG2BReplyMessage trainCharcteristics() { - return b2gReplies.get(ResponseType.TC); - } - - public SFERAG2BReplyMessage invalidXmlError() { - return b2gReplies.get(ResponseType.XML_ERROR); - } - - public SFERAG2BReplyMessage notImplementedError() { - return b2gReplies.get(ResponseType.NOT_IMPLEMENTED); - } - - public SFERAG2BReplyMessage notAvailableError() { - return b2gReplies.get(ResponseType.NOT_AVAILABLE); - } - - public SFERAG2BReplyMessage insufficientData() { - return b2gReplies.get(ResponseType.INSUFFICIENT_DATA); - } - - protected enum ResponseType { - HANDSHAKE, - SP, - TC, - NOT_IMPLEMENTED, - XML_ERROR, - NOT_AVAILABLE, - INSUFFICIENT_DATA, - } -} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/SferaErrorCodes.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/SferaErrorCodes.java new file mode 100644 index 00000000..dbff84df --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/SferaErrorCodes.java @@ -0,0 +1,23 @@ +package ch.sbb.sferamock.messages.common; + +public enum SferaErrorCodes { + + SFERA_XSD_VERSION_NOT_SUPPORTED("2"), + INTENDED_RECIPIENT_NOT_ACTUAL_RECIPIENT("7"), + XML_SCHEMA_VIOLATION("13"), + ACTION_NOT_AUTHORIZED_FOR_USER("46"), + DATA_TEMPORARILY_UNAVAILABLE("48"), + COULD_NOT_PROCESS_DATA("50"), + INCONSISTENT_DATA("53"); + + final String code; + + SferaErrorCodes(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlDateHelper.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlDateHelper.java new file mode 100644 index 00000000..b18ee33e --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlDateHelper.java @@ -0,0 +1,39 @@ +package ch.sbb.sferamock.messages.common; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +public final class XmlDateHelper { + + private XmlDateHelper() { + } + + public static XMLGregorianCalendar toGregorianCalender(LocalDateTime localDateTime) { + try { + return DatatypeFactory.newInstance().newXMLGregorianCalendar( + localDateTime.getYear(), localDateTime.getMonth().getValue(), localDateTime.getDayOfMonth(), + localDateTime.getHour(), localDateTime.getMinute(), localDateTime.getSecond(), + DatatypeConstants.FIELD_UNDEFINED, 0); + } catch (DatatypeConfigurationException e) { + throw new RuntimeException(e); + } + } + + public static XMLGregorianCalendar toGregorianCalender(LocalDate localDate) { + try { + return DatatypeFactory.newInstance().newXMLGregorianCalendarDate( + localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth(), + DatatypeConstants.FIELD_UNDEFINED); + } catch (DatatypeConfigurationException e) { + throw new RuntimeException(e); + } + } + + public static LocalDate toLocalDate(XMLGregorianCalendar calendar) { + return LocalDate.of(calendar.getYear(), calendar.getMonth(), calendar.getDay()); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlHelper.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlHelper.java new file mode 100644 index 00000000..deee9efb --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/common/XmlHelper.java @@ -0,0 +1,44 @@ +package ch.sbb.sferamock.messages.common; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; + +@Component +public class XmlHelper { + + private static final Logger log = LoggerFactory.getLogger(XmlHelper.class); + private final Jaxb2Marshaller jaxb2Marshaller; + + public XmlHelper(Jaxb2Marshaller jaxb2Marshaller) { + this.jaxb2Marshaller = jaxb2Marshaller; + } + + public String toString(Object object) { + try { + StreamResult streamResult = new StreamResult(new StringWriter()); + jaxb2Marshaller.marshal(object, streamResult); + return streamResult.getWriter().toString(); + } catch (Exception e) { + log.warn("Exception {} while marshalling an object of {} to a xml string", e.getLocalizedMessage(), object.getClass()); + return object.toString(); + } + } + + public Object xmlToObject(String filePath) throws IOException { + File file = ResourceUtils.getFile(filePath); + InputStream in = new FileInputStream(file); + String xmlPayload = new String(in.readAllBytes()); + return jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(xmlPayload))); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/helper/XmlHelper.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/helper/XmlHelper.java deleted file mode 100644 index 266f4c7d..00000000 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/helper/XmlHelper.java +++ /dev/null @@ -1,43 +0,0 @@ -package ch.sbb.sferamock.messages.helper; - -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import jakarta.xml.bind.Marshaller; -import jakarta.xml.bind.Unmarshaller; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.io.StringWriter; -import org.springframework.util.ResourceUtils; - -public final class XmlHelper { - - private XmlHelper() { - } - - public static String objectToXml(T object) throws JAXBException { - JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass()); - Marshaller marshaller = jaxbContext.createMarshaller(); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - - StringWriter writer = new StringWriter(); - marshaller.marshal(object, writer); - return writer.toString(); - } - - public static T xmlToObject(String xmlPayload, Class clazz) throws JAXBException { - JAXBContext jaxbContext = JAXBContext.newInstance(clazz); - Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - StringReader reader = new StringReader(xmlPayload); - return clazz.cast(unmarshaller.unmarshal(reader)); - } - - public static T xmlFileToObject(String filePath, Class clazz) throws IOException, JAXBException { - File file = ResourceUtils.getFile(filePath); - InputStream in = new FileInputStream(file); - String xmlPayload = new String(in.readAllBytes()); - return xmlToObject(xmlPayload, clazz); - } -} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/ClientId.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/ClientId.java new file mode 100644 index 00000000..ba56fa54 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/ClientId.java @@ -0,0 +1,11 @@ +package ch.sbb.sferamock.messages.model; + +import lombok.NonNull; + +public record ClientId(@NonNull String value) implements Comparable { + + @Override + public int compareTo(ClientId other) { + return value.compareTo(other.value); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/CompanyCode.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/CompanyCode.java new file mode 100644 index 00000000..b8370236 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/CompanyCode.java @@ -0,0 +1,7 @@ +package ch.sbb.sferamock.messages.model; + +import lombok.NonNull; + +public record CompanyCode(@NonNull String value) { + +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/HandshakeRejectReason.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/HandshakeRejectReason.java new file mode 100644 index 00000000..6674ba2a --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/HandshakeRejectReason.java @@ -0,0 +1,5 @@ +package ch.sbb.sferamock.messages.model; + +public enum HandshakeRejectReason { + architectureNotSupported +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/OperationMode.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/OperationMode.java new file mode 100644 index 00000000..0c7916de --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/OperationMode.java @@ -0,0 +1,34 @@ +package ch.sbb.sferamock.messages.model; + +import java.util.List; + +public record OperationMode(DrivingMode drivingMode, + Connectivity connectivity, + Architecture architecture) { + + public static OperationMode preloading = new OperationMode(DrivingMode.inactive, Connectivity.standalone, Architecture.boardCalculation); + + public static OperationMode observer = new OperationMode(DrivingMode.readOnly, Connectivity.connected, Architecture.boardCalculation); + + public static OperationMode driver = new OperationMode(DrivingMode.dasNotConnectedToAtp, Connectivity.connected, Architecture.boardCalculation); + + public static OperationMode preloadingGroundCalculation = new OperationMode(DrivingMode.inactive, Connectivity.standalone, Architecture.groundCalculation); + + public static OperationMode observerGroundCalculation = new OperationMode(DrivingMode.readOnly, Connectivity.connected, Architecture.groundCalculation); + + public static OperationMode driverGroundCalculation = new OperationMode(DrivingMode.dasNotConnectedToAtp, Connectivity.connected, Architecture.groundCalculation); + + public static List validOperationModes = List.of(preloading, observer, driver); + + public static List wrongArchitectureOperationModes = List.of(preloadingGroundCalculation, observerGroundCalculation, driverGroundCalculation); + + public boolean sendJourneyProfileUpdates() { + return this.connectivity == Connectivity.connected && this.architecture == Architecture.boardCalculation; + } + + public enum DrivingMode {dasNotConnectedToAtp, readOnly, inactive, other} + + public enum Connectivity {connected, standalone} + + public enum Architecture {boardCalculation, groundCalculation} +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/RequestContext.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/RequestContext.java new file mode 100644 index 00000000..b5e402e5 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/RequestContext.java @@ -0,0 +1,22 @@ +package ch.sbb.sferamock.messages.model; + +import java.util.Optional; +import java.util.UUID; +import lombok.NonNull; + +public record RequestContext(@NonNull TrainIdentification tid, @NonNull ClientId clientId, @NonNull Optional incomingMessageId) { + + private static final int MINIMAL_TOPIC_ELEMENTS = 6; + + public static RequestContext fromTopic(String topic, Optional incomingMessageId) { + String[] elements = topic.split("/"); + var length = elements.length; + if (length < MINIMAL_TOPIC_ELEMENTS) { + throw new IllegalArgumentException("The topic must contain at least 6 elements. Topic name: " + topic); + } + + var tid = TrainIdentification.fromString(elements[length - 2], elements[length - 3]); + + return new RequestContext(tid, new ClientId(elements[length - 1]), incomingMessageId); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/SegmentIdentification.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/SegmentIdentification.java new file mode 100644 index 00000000..aae62e86 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/SegmentIdentification.java @@ -0,0 +1,7 @@ +package ch.sbb.sferamock.messages.model; + +import lombok.NonNull; + +public record SegmentIdentification(@NonNull String id, int majorVersion, int minorVersion, @NonNull CompanyCode zone) { + +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/TrainIdentification.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/TrainIdentification.java new file mode 100644 index 00000000..c6a72e67 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/model/TrainIdentification.java @@ -0,0 +1,26 @@ +package ch.sbb.sferamock.messages.model; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.regex.Pattern; +import lombok.NonNull; + +public record TrainIdentification(@NonNull CompanyCode companyCode, @NonNull String operationalNumber, @NonNull LocalDate date, @NonNull Optional additionalNumber) { + + private static final Pattern TID_PATTERN = Pattern.compile("([^_]+)_([0-9-]+)(?:_(.+))?"); + + public TrainIdentification(@NonNull CompanyCode companyCode, @NonNull String operationalNumber, @NonNull LocalDate date) { + this(companyCode, operationalNumber, date, Optional.empty()); + } + + public static TrainIdentification fromString(String tid, String companyCode) { + var matcher = TID_PATTERN.matcher(tid); + if (matcher.matches()) { + var operationalNumber = matcher.group(2); + var additionalNumber = matcher.group(3) == null ? null : matcher.group(3); + var date = matcher.group(1); + return new TrainIdentification(new CompanyCode(companyCode), operationalNumber, LocalDate.parse(date), Optional.ofNullable(additionalNumber)); + } + throw new IllegalArgumentException("Illegal train id string: " + tid); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java new file mode 100644 index 00000000..1bee3394 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java @@ -0,0 +1,40 @@ +package ch.sbb.sferamock.messages.services; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.JourneyProfile; +import ch.sbb.sferamock.messages.common.XmlHelper; +import ch.sbb.sferamock.messages.model.TrainIdentification; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Service; + +@Service +public class JourneyProfileRepository implements ApplicationRunner { + + private static final String XML_RESOURCES_CLASSPATH = "classpath:sfera_example_messages/"; + private final XmlHelper xmlHelper; + + Map journeyProfiles = new HashMap<>(); + + public JourneyProfileRepository(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + importJp("4816", "SFERA_JP_4816.xml"); + importJp("7839", "SFERA_JP_7839.xml"); + } + + public Optional getJourneyProfile(TrainIdentification trainIdentification) { + return Optional.ofNullable(journeyProfiles.get(trainIdentification.operationalNumber())); + } + + private void importJp(String operationalNumber, String path) throws IOException { + var journeyProfile = xmlHelper.xmlToObject(XML_RESOURCES_CLASSPATH + path); + journeyProfiles.put(operationalNumber, (JourneyProfile) journeyProfile); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/OperationModeSelector.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/OperationModeSelector.java new file mode 100644 index 00000000..f37220a6 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/OperationModeSelector.java @@ -0,0 +1,48 @@ +package ch.sbb.sferamock.messages.services; + +import static ch.sbb.sferamock.messages.model.OperationMode.driver; +import static ch.sbb.sferamock.messages.model.OperationMode.observer; +import static ch.sbb.sferamock.messages.model.OperationMode.preloading; +import static ch.sbb.sferamock.messages.model.OperationMode.validOperationModes; +import static ch.sbb.sferamock.messages.model.OperationMode.wrongArchitectureOperationModes; + +import ch.sbb.sferamock.messages.model.OperationMode; +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; + +@Service +public class OperationModeSelector { + + private static Optional getObserver(boolean validReporting) { + return validReporting + ? Optional.of(observer) + : Optional.empty(); + } + + public Optional selectOperationMode(List incomingOperationModes, boolean statusReportEnabled) { + + var validModes = incomingOperationModes.stream() + .filter(validOperationModes::contains) + .toList(); + + if (validModes.contains(driver) && validModes.contains(observer)) { + return getObserver(statusReportEnabled); + } + if (validModes.contains(driver)) { + return Optional.empty(); + } + if (validModes.contains(observer)) { + return getObserver(!statusReportEnabled); + } + if (validModes.contains(preloading)) { + return Optional.of(preloading); + } + return Optional.empty(); + } + + public boolean hasWrongArchitecture(List incomingOperationModes) { + return incomingOperationModes.stream() + .anyMatch(wrongArchitectureOperationModes::contains); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RegistrationService.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RegistrationService.java new file mode 100644 index 00000000..1320548d --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RegistrationService.java @@ -0,0 +1,82 @@ +package ch.sbb.sferamock.messages.services; + +import ch.sbb.sferamock.messages.model.ClientId; +import ch.sbb.sferamock.messages.model.OperationMode; +import ch.sbb.sferamock.messages.model.RequestContext; +import ch.sbb.sferamock.messages.model.TrainIdentification; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Service; + +@Service +public class RegistrationService { + + private static final Logger log = LogManager.getLogger(RegistrationService.class); + private final Map> activeTrains = new ConcurrentHashMap<>(); + private final Map registrationMap = new ConcurrentHashMap<>(); + + private static Set existingSetWith(Set clientIdentifiers, ClientId newClientId) { + clientIdentifiers.add(newClientId); + return clientIdentifiers; + } + + private static Set newSetWith(ClientId clientId) { + var result = new ConcurrentSkipListSet(); + result.add(clientId); + return result; + } + + public void register(RequestContext requestContext, OperationMode selectedMode) { + registerClientIdAndTrain(requestContext, selectedMode); + } + + private void registerClientIdAndTrain(RequestContext requestContext, OperationMode operationMode) { + var clientId = requestContext.clientId(); + var trainIdentification = requestContext.tid(); + log.info("Registering DAS Client {} with company {}, train {}, date {}", clientId, trainIdentification.companyCode(), trainIdentification.operationalNumber(), trainIdentification.date()); + var registration = new Registration(trainIdentification, operationMode); + registrationMap.put(clientId, registration); + + if (operationMode.sendJourneyProfileUpdates()) { + activeTrains.compute(trainIdentification, (key, clientIdentifiers) -> clientIdentifiers == null + ? newSetWith(clientId) + : existingSetWith(clientIdentifiers, clientId)); + } + } + + public void deregisterClient(ClientId clientId) { + deregisterTrainIfLastClient(clientId); + registrationMap.remove(clientId); + } + + private void deregisterTrainIfLastClient(ClientId clientId) { + if (registrationMap.containsKey(clientId)) { + var currentTrainIdentification = registrationMap.get(clientId).trainIdentification(); + activeTrains.compute(currentTrainIdentification, (tid, clientIdentifiers) -> { + if (clientIdentifiers.remove(clientId)) { + if (clientIdentifiers.isEmpty()) { + return null; + } + } + return clientIdentifiers; + }); + } + } + + public boolean isRegistered(ClientId clientId) { + return registrationMap.containsKey(clientId); + } + + public void reset() { + registrationMap.clear(); + activeTrains.clear(); + } + + public record Registration(TrainIdentification trainIdentification, OperationMode operationMode) { + + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RequestContextRepository.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RequestContextRepository.java new file mode 100644 index 00000000..51343c26 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/RequestContextRepository.java @@ -0,0 +1,22 @@ +package ch.sbb.sferamock.messages.services; + +import ch.sbb.sferamock.messages.model.RequestContext; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.stereotype.Component; + +@Component +class RequestContextRepository { + + private final Map requestMetadataMap = new ConcurrentHashMap<>(); + + public Optional getRequestContext(UUID correlationId) { + return Optional.ofNullable(requestMetadataMap.get(correlationId)); + } + + public void storeRequestContext(UUID correlationId, RequestContext value) { + requestMetadataMap.put(correlationId, value); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java new file mode 100644 index 00000000..585178a8 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java @@ -0,0 +1,41 @@ +package ch.sbb.sferamock.messages.services; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; +import ch.sbb.sferamock.messages.common.XmlHelper; +import ch.sbb.sferamock.messages.model.SegmentIdentification; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Service; + +@Service +public class SegmentProfileRepository implements ApplicationRunner { + + private static final String XML_RESOURCES_CLASSPATH = "classpath:sfera_example_messages/"; + private final XmlHelper xmlHelper; + + Map segmentProfiles = new HashMap<>(); + + public SegmentProfileRepository(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + importSp("1", "SFERA_SP_1.xml"); + importSp("2", "SFERA_SP_2.xml"); + importSp("3", "SFERA_SP_3.xml"); + } + + public Optional getSegmentProfile(SegmentIdentification spId) { + return Optional.ofNullable(segmentProfiles.get(spId.id())); + } + + private void importSp(String operationalNumber, String path) throws IOException { + var segmentProfile = xmlHelper.xmlToObject(XML_RESOURCES_CLASSPATH + path); + segmentProfiles.put(operationalNumber, (SegmentProfile) segmentProfile); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java new file mode 100644 index 00000000..a43b3cb2 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java @@ -0,0 +1,136 @@ +package ch.sbb.sferamock.messages.services; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.JourneyProfile; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SPZoneComplexType; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; +import ch.sbb.sferamock.messages.common.SferaErrorCodes; +import ch.sbb.sferamock.messages.model.HandshakeRejectReason; +import ch.sbb.sferamock.messages.model.OperationMode; +import ch.sbb.sferamock.messages.model.RequestContext; +import ch.sbb.sferamock.messages.model.SegmentIdentification; +import ch.sbb.sferamock.messages.model.TrainIdentification; +import ch.sbb.sferamock.messages.sfera.ReplyPublisher; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class SferaApplicationService { + + private static final Logger log = LoggerFactory.getLogger(SferaApplicationService.class); + private final ReplyPublisher replyPublisher; + private final RequestContextRepository requestContextRepository; + private final OperationModeSelector operationModeSelector; + private final RegistrationService registrationService; + private final JourneyProfileRepository journeyProfileRepository; + private final SegmentProfileRepository segmentProfileRepository; + + public SferaApplicationService(ReplyPublisher replyPublisher, RequestContextRepository requestContextRepository, OperationModeSelector operationModeSelector, + RegistrationService registrationService, JourneyProfileRepository journeyProfileRepository, SegmentProfileRepository segmentProfileRepository) { + this.replyPublisher = replyPublisher; + this.requestContextRepository = requestContextRepository; + this.operationModeSelector = operationModeSelector; + this.registrationService = registrationService; + this.journeyProfileRepository = journeyProfileRepository; + this.segmentProfileRepository = segmentProfileRepository; + } + + private static JourneyProfile unavailableJourneyProfile() { + var jp = new JourneyProfile(); + jp.setJPStatus("Unavailable"); + return jp; + } + + private static SegmentProfile invalidSp(SegmentIdentification segmentIdentification) { + var sp = new SegmentProfile(); + sp.setSPStatus("Invalid"); + sp.setSPID(segmentIdentification.id()); + sp.setSPVersionMajor(segmentIdentification.majorVersion()); + sp.setSPVersionMinor(segmentIdentification.minorVersion()); + var spZone = new SPZoneComplexType(); + spZone.setIMID(segmentIdentification.zone().value()); + sp.setSPZone(spZone); + return sp; + } + + public void processHandshakeRequest(List supportedOperationModes, boolean statusReportEnabled, RequestContext requestContext) { + var selectedMode = operationModeSelector.selectOperationMode(supportedOperationModes, statusReportEnabled); + + selectedMode.ifPresentOrElse(it -> { + registrationService.register(requestContext, it); + replyPublisher.publishHandshakeAcknowledge(it.connectivity(), it.architecture(), requestContext); + }, () -> errorOrReject(supportedOperationModes, requestContext)); + } + + public void processJourneyProfileRequest(TrainIdentification trainIdentification, RequestContext requestContext) { + if (!registrationService.isRegistered(requestContext.clientId())) { + publishErrorMessageUnregisteredClient(requestContext); + return; + } + if (!trainIdentification.equals(requestContext.tid())) { + log.warn("Inconsistent train identification: header {}, request {}", requestContext.tid(), trainIdentification); + replyPublisher.publishErrorMessage(SferaErrorCodes.INCONSISTENT_DATA, requestContext); + return; + } + + var correlationId = UUID.randomUUID(); + requestContextRepository.storeRequestContext(correlationId, requestContext); + var journeyProfile = journeyProfileRepository.getJourneyProfile(trainIdentification); + publishJourneyProfile(journeyProfile, correlationId, requestContext); + } + + public void processSegmentProfileRequest(List segmentIdentifications, RequestContext requestContext) { + if (!registrationService.isRegistered(requestContext.clientId())) { + publishErrorMessageUnregisteredClient(requestContext); + return; + } + + var correlationId = UUID.randomUUID(); + requestContextRepository.storeRequestContext(correlationId, requestContext); + + var segmentProfiles = segmentIdentifications.stream() + .map(segmentIdentification -> segmentProfileRepository.getSegmentProfile(segmentIdentification) + .orElse(invalidSp(segmentIdentification))) + .toList(); + publishSegmentProfile(segmentProfiles, correlationId, requestContext); + } + + private void publishJourneyProfile(Optional journeyProfile, UUID correlationId, RequestContext requestContext) { + requestContextRepository.getRequestContext(correlationId) + .ifPresentOrElse(it -> publishJourneyProfileResponse(journeyProfile, it), () -> replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext)); + } + + private void publishJourneyProfileResponse(Optional journeyProfile, RequestContext requestContext) { + var responseToPublish = journeyProfile.orElseGet(SferaApplicationService::unavailableJourneyProfile); + replyPublisher.publishJourneyProfile(responseToPublish, requestContext); + } + + private void publishSegmentProfile(List segmentProfiles, UUID correlationId, RequestContext requestContext) { + requestContextRepository.getRequestContext(correlationId) + .ifPresentOrElse(it -> publishSegmentProfileResponse(segmentProfiles, it), () -> replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext)); + } + + private void publishSegmentProfileResponse(List segmentProfiles, RequestContext requestContext) { + if (!segmentProfiles.isEmpty()) { + replyPublisher.publishSegmentProfile(segmentProfiles, requestContext); + } else { + replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext); + } + } + + private void publishErrorMessageUnregisteredClient(RequestContext requestContext) { + log.warn("Received a request from an unregistered client id {}", requestContext.clientId()); + replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext); + } + + private void errorOrReject(List dasOperatingModesSupported, RequestContext requestContext) { + if (operationModeSelector.hasWrongArchitecture(dasOperatingModesSupported)) { + replyPublisher.publishHandshakeReject(HandshakeRejectReason.architectureNotSupported, requestContext); + } else { + replyPublisher.publishErrorMessage(SferaErrorCodes.INCONSISTENT_DATA, requestContext); + } + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java new file mode 100644 index 00000000..74ca8bc9 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java @@ -0,0 +1,142 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.B2GRequest; +import ch.sbb.sferamock.adapters.sfera.model.v0201.JPRequest; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GEventMessage; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GReplyMessage; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GRequestMessage; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SPRequest; +import ch.sbb.sferamock.messages.services.SferaApplicationService; +import ch.sbb.sferamock.messages.common.XmlDateHelper; +import ch.sbb.sferamock.messages.common.XmlHelper; +import ch.sbb.sferamock.messages.model.CompanyCode; +import ch.sbb.sferamock.messages.model.RequestContext; +import ch.sbb.sferamock.messages.common.SferaErrorCodes; +import ch.sbb.sferamock.messages.model.TrainIdentification; +import ch.sbb.sferamock.messages.model.SegmentIdentification; +import com.solacesystems.jcsmp.impl.TopicImpl; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import javax.xml.transform.stream.StreamSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.Message; +import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import org.springframework.stereotype.Component; + +@Component +public class IncomingMessageAdapter { + + private static final Logger log = LoggerFactory.getLogger(IncomingMessageAdapter.class); + + private final XmlHelper xmlHelper; + private final Jaxb2Marshaller jaxb2Marshaller; + private final SferaApplicationService sferaApplicationService; + private final MessageHeaderValidator messageHeaderValidator; + private final ReplyPublisher replyPublisher; + + public IncomingMessageAdapter(XmlHelper xmlHelper, Jaxb2Marshaller jaxb2Marshaller, SferaApplicationService sferaApplicationService, MessageHeaderValidator messageHeaderValidator, + ReplyPublisher replyPublisher) { + this.xmlHelper = xmlHelper; + this.jaxb2Marshaller = jaxb2Marshaller; + this.sferaApplicationService = sferaApplicationService; + this.messageHeaderValidator = messageHeaderValidator; + this.replyPublisher = replyPublisher; + } + + public void processIncomingMessage(Message message) { + var messageHeaders = message.getHeaders(); + var topic = Objects.requireNonNull(messageHeaders.get("solace_destination", TopicImpl.class)).getName(); + Object payload; + String xmlString = null; + try { + xmlString = new String(message.getPayload(), StandardCharsets.UTF_8); + payload = unmarshallPayload(xmlString); + } catch (Exception e) { + var errorMessage = getRootMessage(e); + publishXmlValidationErrorMessage(xmlString, errorMessage, RequestContext.fromTopic(topic, Optional.empty())); + return; + } + switch (payload) { + case SFERAB2GRequestMessage request -> { + var requestContext = RequestContext.fromTopic(topic, Optional.of(UUID.fromString(request.getMessageHeader().getMessageID()))); + log.info("Received SFERAB2GRequestMessage {} on topic {}", xmlHelper.toString(request), topic); + var validationMessage = messageHeaderValidator.validate(request.getMessageHeader(), requestContext.tid().companyCode().value()); + if (validationMessage.isPresent()) { + log.warn("Reject Message with error in message header"); + replyPublisher.publishErrorMessage(validationMessage.get(), requestContext); + return; + } + if (request.getHandshakeRequest() != null) { + var handshakeRequest = request.getHandshakeRequest(); + sferaApplicationService.processHandshakeRequest( + SferaToInternalConverters.convertOperationModes(handshakeRequest.getDASOperatingModesSupported()), + nullSafeBoolean(handshakeRequest.isStatusReportsEnabled()), + requestContext); + } else { + B2GRequest b2GRequest = request.getB2GRequest(); + if (b2GRequest != null && b2GRequest.getJPRequest() != null && b2GRequest.getJPRequest().size() == 1) { + processJourneyProfileRequest(request.getB2GRequest().getJPRequest().get(0), requestContext); + return; + } + if (b2GRequest != null && b2GRequest.getSPRequest() != null && !b2GRequest.getSPRequest().isEmpty()) { + processSegmentProfileRequest(b2GRequest.getSPRequest(), requestContext); + return; + } + log.warn("A B2G Request that is not a handshake should currently have exactly one jp or sp request. Request is ignored."); + } + } + case SFERAB2GEventMessage event -> { + log.info("Received SFERAB2GEventMessage: {} on topic {}", + xmlHelper.toString(event), topic); + } + case SFERAB2GReplyMessage reply -> { + log.info("Received SFERAB2GReplyMessage: {} on topic {}", + xmlHelper.toString(reply), topic); + } + default -> log.error("Unknown xml message type received: {} xml string \"{}\"", payload.getClass(), xmlString); + } + } + + private Object unmarshallPayload(String xmlString) { + return jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(xmlString))); + } + + private boolean nullSafeBoolean(Boolean value) { + return value != null && value; + } + + private String getRootMessage(Exception exception) { + String message = exception.getMessage(); + Throwable cause = exception.getCause(); + while (cause != null) { + message = cause.getMessage(); + cause = cause.getCause(); + } + return message; + } + + private void publishXmlValidationErrorMessage(String message, String errorMessage, RequestContext requestContext) { + log.warn("Exception while unmarshalling message '{}': {}", message, errorMessage); + replyPublisher.publishErrorMessage(SferaErrorCodes.XML_SCHEMA_VIOLATION, requestContext); + } + + private void processJourneyProfileRequest(JPRequest jpRequest, RequestContext requestContext) { + var otnId = jpRequest.getTrainIdentification().getOTNID(); + var tid = new TrainIdentification(new CompanyCode(otnId.getCompany()), otnId.getOperationalTrainNumber(), XmlDateHelper.toLocalDate(otnId.getStartDate())); + sferaApplicationService.processJourneyProfileRequest(tid, requestContext); + } + + private void processSegmentProfileRequest(List spRequests, RequestContext requestContext) { + var segmentIdentifications = spRequests.stream().map(spRequest -> new SegmentIdentification( + spRequest.getSPID(), + spRequest.getSPVersionMajor() != null ? spRequest.getSPVersionMajor().intValue() : 0, + spRequest.getSPVersionMinor() != null ? spRequest.getSPVersionMinor().intValue() : 0, + new CompanyCode(spRequest.getSPZone().getIMID()))).toList(); + sferaApplicationService.processSegmentProfileRequest(segmentIdentifications, requestContext); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/MessageHeaderValidator.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/MessageHeaderValidator.java new file mode 100644 index 00000000..22556e8c --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/MessageHeaderValidator.java @@ -0,0 +1,40 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.MessageHeader; +import ch.sbb.sferamock.messages.common.SferaErrorCodes; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class MessageHeaderValidator { + + private static final Logger log = LoggerFactory.getLogger(MessageHeaderValidator.class); + + @Value("${sfera.company-code}") + String tmsCompanyCode; + + @Value("${sfera.sfera-version}") + String sferaVersion; + + public Optional validate(MessageHeader messageHeader, String companyCode) { + + if (!sferaVersion.contains(messageHeader.getSFERAVersion())) { + log.info("Error validating MessageHeader: wrong SFERA version {}", messageHeader.getSFERAVersion()); + return Optional.of(SferaErrorCodes.SFERA_XSD_VERSION_NOT_SUPPORTED); + } + if (!tmsCompanyCode.equals(messageHeader.getRecipient().getValue())) { + log.info("Error validating MessageHeader: wrong recipient {}", messageHeader.getRecipient().getValue()); + return Optional.of(SferaErrorCodes.INTENDED_RECIPIENT_NOT_ACTUAL_RECIPIENT); + } + + if (!companyCode.equals(messageHeader.getSender().getValue())) { + log.info("Error validating MessageHeader: wrong sender {}", messageHeader.getSender().getValue()); + return Optional.of(SferaErrorCodes.ACTION_NOT_AUTHORIZED_FOR_USER); + } + return Optional.empty(); + } + +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java new file mode 100644 index 00000000..8fc6f7ad --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java @@ -0,0 +1,82 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.JourneyProfile; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BReplyMessage; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; +import ch.sbb.sferamock.messages.common.SferaErrorCodes; +import ch.sbb.sferamock.messages.common.XmlHelper; +import ch.sbb.sferamock.messages.model.HandshakeRejectReason; +import ch.sbb.sferamock.messages.model.OperationMode; +import ch.sbb.sferamock.messages.model.RequestContext; +import java.util.List; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.http.MediaType; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Service; + +@Service +public class ReplyPublisher { + + private static final Logger log = LoggerFactory.getLogger(ReplyPublisher.class); + private static final String SOLACE_BINDER = "solace"; + private final XmlHelper xmlHelper; + private final StreamBridge streamBridge; + private final SferaMessageCreator sferaMessageCreator; + @Value("${spring.cloud.stream.bindings.publishG2BReply-out-0.destination}") + private String publishDestination; + + public ReplyPublisher(XmlHelper xmlHelper, StreamBridge streamBridge, SferaMessageCreator sferaMessageCreator) { + this.xmlHelper = xmlHelper; + this.streamBridge = streamBridge; + this.sferaMessageCreator = sferaMessageCreator; + } + + public void publishJourneyProfile(JourneyProfile journeyProfile, RequestContext requestContext) { + var header = sferaMessageCreator.createMessageHeader(UUID.randomUUID(), requestContext.tid(), requestContext.incomingMessageId()); + var reply = sferaMessageCreator.createJourneyProfileReplyMessage(journeyProfile, header); + publishReplyMessage(reply, requestContext); + } + + public void publishSegmentProfile(List segmentProfiles, RequestContext requestContext) { + var header = sferaMessageCreator.createMessageHeader(UUID.randomUUID(), requestContext.tid(), requestContext.incomingMessageId()); + var reply = sferaMessageCreator.createSegmentProfileReplyMessage(segmentProfiles, header); + publishReplyMessage(reply, requestContext); + } + + public void publishHandshakeAcknowledge(OperationMode.Connectivity connectivity, + OperationMode.Architecture architecture, + RequestContext requestContext) { + var header = sferaMessageCreator.createMessageHeader(UUID.randomUUID(), requestContext.tid(), requestContext.incomingMessageId()); + var ack = sferaMessageCreator.createSferaHandshakeAcknowledgement(connectivity, architecture); + var reply = sferaMessageCreator.createSferaReplyMessage(header, ack); + publishReplyMessage(reply, requestContext); + } + + public void publishHandshakeReject(HandshakeRejectReason rejectReason, RequestContext requestContext) { + var header = sferaMessageCreator.createMessageHeader(UUID.randomUUID(), requestContext.tid(), requestContext.incomingMessageId()); + var ack = sferaMessageCreator.createSferaHandshakeReject(rejectReason); + var reply = sferaMessageCreator.createSferaReplyMessage(header, ack); + publishReplyMessage(reply, requestContext); + } + + public void publishErrorMessage(SferaErrorCodes code, RequestContext requestContext) { + var replyMessageHeader = sferaMessageCreator.createOutgoingMessageHeader(UUID.randomUUID(), + requestContext.incomingMessageId(), + requestContext.tid()); + var replyMessage = sferaMessageCreator.createSferaReplyErrorMessage(replyMessageHeader, code.getCode()); + publishReplyMessage(replyMessage, requestContext); + } + + private void publishReplyMessage(SFERAG2BReplyMessage replyMessage, RequestContext requestContext) { + String topic = SferaTopicHelper.getG2BTopic(publishDestination, requestContext); + log.info("Publishing Reply Message: {} to topic {}", xmlHelper.toString(replyMessage), topic); + streamBridge.send(topic, SOLACE_BINDER, MessageBuilder + .withPayload(replyMessage) + .build(), + MediaType.APPLICATION_XML); + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java new file mode 100644 index 00000000..0ee30f26 --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java @@ -0,0 +1,162 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.B2GMessageResponse.Result; +import ch.sbb.sferamock.adapters.sfera.model.v0201.DASOperatingModeSelected; +import ch.sbb.sferamock.adapters.sfera.model.v0201.G2BError; +import ch.sbb.sferamock.adapters.sfera.model.v0201.G2BMessageResponse; +import ch.sbb.sferamock.adapters.sfera.model.v0201.G2BReplyPayload; +import ch.sbb.sferamock.adapters.sfera.model.v0201.HandshakeAcknowledgement; +import ch.sbb.sferamock.adapters.sfera.model.v0201.HandshakeReject; +import ch.sbb.sferamock.adapters.sfera.model.v0201.JourneyProfile; +import ch.sbb.sferamock.adapters.sfera.model.v0201.MessageHeader; +import ch.sbb.sferamock.adapters.sfera.model.v0201.OTNIDComplexType; +import ch.sbb.sferamock.adapters.sfera.model.v0201.Recipient; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BReplyMessage; +import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; +import ch.sbb.sferamock.adapters.sfera.model.v0201.Sender; +import ch.sbb.sferamock.adapters.sfera.model.v0201.TrainIdentificationComplexType; +import ch.sbb.sferamock.adapters.sfera.model.v0201.UnavailableDASOperatingModes; +import ch.sbb.sferamock.messages.common.XmlDateHelper; +import ch.sbb.sferamock.messages.model.HandshakeRejectReason; +import ch.sbb.sferamock.messages.model.OperationMode; +import ch.sbb.sferamock.messages.model.TrainIdentification; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class SferaMessageCreator { + + @Value("${sfera.company-code}") + String companyCode; + + @Value("${sfera.sfera-version}") + String sferaVersion; + + @Value("${sfera.source-device}") + String sourceDevice; + + private static TrainIdentificationComplexType createTrainIdentification(TrainIdentification tid) { + var result = new TrainIdentificationComplexType(); + var otnId = new OTNIDComplexType(); + otnId.setOperationalTrainNumber(tid.operationalNumber()); + otnId.setCompany(tid.companyCode().value()); + otnId.setStartDate(XmlDateHelper.toGregorianCalender(tid.date())); + result.setOTNID(otnId); + return result; + } + + public SFERAG2BReplyMessage createJourneyProfileReplyMessage(JourneyProfile journeyProfile, MessageHeader header) { + var result = new SFERAG2BReplyMessage(); + result.setMessageHeader(header); + var payload = new G2BReplyPayload(); + journeyProfile.setTrainIdentification(header.getTrainIdentification()); + payload.getJourneyProfile().add(journeyProfile); + result.setG2BReplyPayload(payload); + return result; + } + + public MessageHeader createOutgoingMessageHeader(UUID messageId, Optional correlationId, TrainIdentification tid) { + return createMessageHeader(messageId, tid, correlationId); + } + + public MessageHeader createMessageHeader(UUID messageId, TrainIdentification tid, Optional correlationId) { + var sender = new Sender(); + sender.setValue(companyCode); + var recipient = new Recipient(); + + var result = new MessageHeader(); + result.setSFERAVersion(sferaVersion); + result.setSourceDevice(sourceDevice); + result.setMessageID(messageId.toString()); + recipient.setValue(tid.companyCode().value()); + correlationId.map(UUID::toString).ifPresent(result::setCorrelationID); + result.setSender(sender); + result.setRecipient(recipient); + result.setTimestamp(XmlDateHelper.toGregorianCalender(LocalDateTime.now())); + result.setTrainIdentification(createTrainIdentification(tid)); + return result; + } + + public HandshakeAcknowledgement createSferaHandshakeAcknowledgement(OperationMode.Connectivity dasConnectivity, OperationMode.Architecture architecture) { + var result = new HandshakeAcknowledgement(); + DASOperatingModeSelected dasOperatingModeSelected = new DASOperatingModeSelected(); + dasOperatingModeSelected.setDASArchitecture(toSferaArchitecture(architecture)); + dasOperatingModeSelected.setDASConnectivity(toSferaConnectivity(dasConnectivity)); + result.setDASOperatingModeSelected(dasOperatingModeSelected); + return result; + } + + private UnavailableDASOperatingModes.DASArchitecture toSferaArchitecture(OperationMode.Architecture architecture) { + return switch (architecture) { + case groundCalculation -> UnavailableDASOperatingModes.DASArchitecture.GROUND_ADVICE_CALCULATION; + case boardCalculation -> UnavailableDASOperatingModes.DASArchitecture.BOARD_ADVICE_CALCULATION; + }; + } + + private UnavailableDASOperatingModes.DASConnectivity toSferaConnectivity(OperationMode.Connectivity connectivity) { + return switch (connectivity) { + case connected -> UnavailableDASOperatingModes.DASConnectivity.CONNECTED; + case standalone -> UnavailableDASOperatingModes.DASConnectivity.STANDALONE; + }; + } + + public HandshakeReject createSferaHandshakeReject(HandshakeRejectReason rejectReason) { + var result = new HandshakeReject(); + result.setHandshakeRejectReason(toSferaRejectReason(rejectReason)); + return result; + } + + private HandshakeReject.HandshakeRejectReason toSferaRejectReason(HandshakeRejectReason rejectReason) { + return switch (rejectReason) { + case architectureNotSupported -> HandshakeReject.HandshakeRejectReason.ARCHITECTURE_NOT_SUPPORTED; + }; + } + + public SFERAG2BReplyMessage createSferaReplyMessage(MessageHeader header, HandshakeAcknowledgement acknowledgement) { + var result = new SFERAG2BReplyMessage(); + result.setMessageHeader(header); + result.setHandshakeAcknowledgement(acknowledgement); + return result; + } + + public SFERAG2BReplyMessage createSferaReplyErrorMessage(MessageHeader header, String errorCode, Optional additionalInfo) { + var result = new SFERAG2BReplyMessage(); + result.setMessageHeader(header); + + var messageResponse = new G2BMessageResponse(); + G2BError g2BError = new G2BError(); + g2BError.setErrorCode(errorCode); + additionalInfo.ifPresent(g2BError::setAdditionalInfo); + messageResponse.getG2BError().add(g2BError); + messageResponse.setResult(Result.ERROR); + + var replyPayload = new G2BReplyPayload(); + replyPayload.setG2BMessageResponse(messageResponse); + result.setG2BReplyPayload(replyPayload); + return result; + } + + public SFERAG2BReplyMessage createSferaReplyErrorMessage(MessageHeader header, String errorCode) { + return createSferaReplyErrorMessage(header, errorCode, Optional.empty()); + } + + public SFERAG2BReplyMessage createSferaReplyMessage(MessageHeader messageHeader, HandshakeReject handshakeReject) { + var result = new SFERAG2BReplyMessage(); + result.setMessageHeader(messageHeader); + result.setHandshakeReject(handshakeReject); + return result; + } + + public SFERAG2BReplyMessage createSegmentProfileReplyMessage(List segmentProfiles, MessageHeader header) { + var result = new SFERAG2BReplyMessage(); + result.setMessageHeader(header); + var payload = new G2BReplyPayload(); + payload.getSegmentProfile().addAll(segmentProfiles); + result.setG2BReplyPayload(payload); + return result; + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessagingConfig.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessagingConfig.java new file mode 100644 index 00000000..5400dddc --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessagingConfig.java @@ -0,0 +1,39 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.ObjectFactory; +import jakarta.xml.bind.Marshaller; +import java.util.Map; +import java.util.function.Consumer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.messaging.Message; +import org.springframework.messaging.converter.MarshallingMessageConverter; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.oxm.jaxb.Jaxb2Marshaller; + +@Configuration +public class SferaMessagingConfig { + + @Bean + public Consumer> processB2GMessage(IncomingMessageAdapter incomingMessageAdapter) { + return incomingMessageAdapter::processIncomingMessage; + } + + @Bean + public Jaxb2Marshaller jaxb2Marshaller() { + var marshaller = new Jaxb2Marshaller(); + marshaller.setContextPath(ObjectFactory.class.getPackageName()); + marshaller.setSchemas(new ClassPathResource("SFERA_3.0_custom.xsd")); + marshaller.setMarshallerProperties(Map.of(Marshaller.JAXB_FRAGMENT, true)); // suppress xml prolog + return marshaller; + } + + @Bean + public MessageConverter xmlMessageConverter(Jaxb2Marshaller jaxb2Marshaller) { + return new MarshallingMessageConverter(jaxb2Marshaller) { + // requires subclassing, see ContentTypeConfiguration.isConverterEligible() + }; + } + +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaToInternalConverters.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaToInternalConverters.java new file mode 100644 index 00000000..f230a08c --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaToInternalConverters.java @@ -0,0 +1,51 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.adapters.sfera.model.v0201.B2GStatusReport; +import ch.sbb.sferamock.adapters.sfera.model.v0201.UnavailableDASOperatingModes; +import ch.sbb.sferamock.adapters.sfera.model.v0201.DASModesComplexType; + +import ch.sbb.sferamock.messages.model.OperationMode; +import java.util.List; + +public final class SferaToInternalConverters { + + private SferaToInternalConverters() { + } + + public static List convertOperationModes(List dasOperatingModesSupported) { + return dasOperatingModesSupported.stream() + .map(SferaToInternalConverters::convertOperationMode) + .toList(); + } + + private static OperationMode convertOperationMode(DASModesComplexType dasModesComplexType) { + return new OperationMode( + convertDrivingMode(dasModesComplexType.getDASDrivingMode()), + convertConnectivity(dasModesComplexType.getDASConnectivity()), + convertArchitecture(dasModesComplexType.getDASArchitecture()) + ); + } + + private static OperationMode.Architecture convertArchitecture(UnavailableDASOperatingModes.DASArchitecture dasArchitecture) { + return switch (dasArchitecture) { + case BOARD_ADVICE_CALCULATION -> OperationMode.Architecture.boardCalculation; + case GROUND_ADVICE_CALCULATION -> OperationMode.Architecture.groundCalculation; + }; + } + + private static OperationMode.Connectivity convertConnectivity(UnavailableDASOperatingModes.DASConnectivity dasConnectivity) { + return switch (dasConnectivity) { + case CONNECTED -> OperationMode.Connectivity.connected; + case STANDALONE -> OperationMode.Connectivity.standalone; + }; + } + + private static OperationMode.DrivingMode convertDrivingMode(B2GStatusReport.DASDrivingMode dasDrivingMode) { + return switch (dasDrivingMode) { + case READ_ONLY -> OperationMode.DrivingMode.readOnly; + case DAS_NOT_CONNECTED_TO_ATP -> OperationMode.DrivingMode.dasNotConnectedToAtp; + case INACTIVE -> OperationMode.DrivingMode.inactive; + default -> OperationMode.DrivingMode.other; + }; + } +} diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaTopicHelper.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaTopicHelper.java new file mode 100644 index 00000000..3178128e --- /dev/null +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaTopicHelper.java @@ -0,0 +1,25 @@ +package ch.sbb.sferamock.messages.sfera; + +import ch.sbb.sferamock.messages.model.RequestContext; +import ch.sbb.sferamock.messages.model.TrainIdentification; +import java.time.format.DateTimeFormatter; + +final class SferaTopicHelper { + + private SferaTopicHelper() { + } + + public static String getG2BTopic(String publishDestination, RequestContext requestContext) { + return String.format("%s%s/%s/%s", publishDestination, // publishDestination ends with a slash + requestContext.tid().companyCode().value(), + formatTrainIdentification(requestContext.tid()), + requestContext.clientId().value()); + } + + private static String formatTrainIdentification(TrainIdentification tid) { + return tid.additionalNumber().isPresent() + ? String.format("%s_%s_%s", tid.date().format(DateTimeFormatter.ISO_DATE), tid.operationalNumber(), + tid.additionalNumber().get()) + : String.format("%s_%s", tid.date().format(DateTimeFormatter.ISO_DATE), tid.operationalNumber()); + } +} diff --git a/sfera-mock/src/main/resources/SFERA_3.0_custom.xsd b/sfera-mock/src/main/resources/SFERA_3.0_custom.xsd new file mode 100644 index 00000000..66d00d00 --- /dev/null +++ b/sfera-mock/src/main/resources/SFERA_3.0_custom.xsd @@ -0,0 +1,6051 @@ + + + + + + + IRS90940 Board to Ground Event Message. In standard SFERA implementations via the Common Interface, the value "SF00" will be used for the MessageType. + + + + + + + + + + + + + + IRS90940 Board to Ground Reply Message. In standard SFERA implementations via the Common Interface, the value "SF02" will be used for the MessageType. + + + + + + + + + + + IRS90940 Board to Ground Request Message. In standard SFERA implementations via the Common Interface, the value "SF01" will be used for the MessageType. + + + + + + + + + + + + + + IRS90940 Ground to Board Event Message. In standard SFERA implementations via the Common Interface, the value "SF10" will be used for the MessageType. + + + + + + + + + + + IRS90940 Ground to Board Reply Message. In standard SFERA implementations via the Common Interface, the value "SF12" will be used for the MessageType. + + + + + + + + + + + + + + + IRS90940 Ground to Board Request Message. In standard SFERA implementations via the Common Interface, the value "SF11" will be used for the MessageType. + + + + + + + + + + + Additional Speed Restriction + + + + + + + + + Additional identifier for train number + + + + + Change in adhesion conditions + + + + + + + + + Explicit speed advice + + + + + + + + + + + + Identifies the Delta delay time against the booked schedule in minutes + + + + + Delay compared to the referenced Date/Time + + + + + Name of company who is responsible for allocation and maintenance of codes + + + + + Applicable Operational Train Number. In some cases this is different from the TrainIdentification, for example when the OTN changes during the trip. + + + + + + + + + + + + The effects of this signal element are to be taken into account only if this SP is in the SP_List. Even if the SP has already been passed, the SP shall be included in the SP_List. + + + + + + + + + + + + Information about ATO-Trackside Contact Information + + + + + + + + + + + Identifier of ATP System. If it is a number: ETCS NID_ +NTC from ERA "Assignment of Values to ETCS Variables", section 3.3 (ERA_ERTMS_0 +40001 v1.27. https://www.era.europa.eu/sites/default/files/activities/docs/ertms_040001_etcs_variables_values_en.pdf). If it is a name: from the ERA "List of Class B Systems", section 3.2 (ERA/ +TD/2011-11 v4.0, https://www.era.europa.eu/sites/default/files/activities/docs/ertms_ccs_class_b_systems_en.pdf) + + + + + + + + + + List of ATP systems supported in the Segment Profile. The DAS system can then find out what is supported. ATP System Consequences will be for Operational Train data containing its characteristics necessary to calculate its dynamic capabilities. + + + + + + + + + + + Change in ATP systems supported + + + + + + + + + + + + ATP systems supported at the start of the Segment Profile. + + + + + Average speed of the train + + + + + Speed range (in km/h) to avoid + + + + + + + + + [SUBSET-126] Speed Profile restriction to be applied to specific axle loads + + + + + + + + + + + + + + Description of error (board to ground). To be used only if the result of the message is "ERROR" + + + + + + + + + + Response to message received, board to ground + + + + + + + + + + + Payload of board to ground reply + + + + + + + + + + + + + + Request from board to ground + + + + + + + + + + + + + + + + + Status Report from Board to Ground + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Equipment on the track at a known position that communicates with the train to exchange different information. The train can recalibrate its positioning systems according to this balise. If the Balise has a NID_C different from the one of the SP, it can be expressed here. + + + + + + + + + + + + + + + + + + Network Specific Parameters in Braking Force Curve + + + + + Traction Type for Braking Force Curve + + + + + + + + + + Braking Force Curve for the train. Several curves may be defined for different traction types and ATP Systems. + + + + + Data point for the braking force curve (pair of speed and braking force) + + + + + + + + + + Explicit Speed Advice to On-Board unit. To be used in C-DAS-C operation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C-DAS-C advice request + + + + + + + + + + + Identifier of the C-DAS-C message which is currently being shown to the driver + + + + + + + + + This type of signal will be used in the case that the signal will present an aspect that is different from the aspect announced by a previous signal. + + + + + + + + + + Cant Deficiency Static Speed Profile + + + + + + + + Reason for Journey Profile Change + + + + + + + + + + + + + Gives an indication of the status or occupation of the piece of the segment + + + + + Network used for communication between driver and ground. The driver needs to know it because some procedures in case of an emergency depend on the network type. + + + + + + + + + + + + + Identifies a railway company (RU or IM) (UIC RICS Code: https://uic.org/rics) + + + + + Connection track + + + + + + + + + + + + + Information related to a contact + + + + + + + + + + + + List of contacts for a specific area + + + + + + + + + + + + + + It is the main part of identifier and is determined by the company that creates it. + + + + + + + + + + + + + Identifies a County or State by code (ISO 3166-1) + + + + + + + + + + Maximum current to be applied by the train. Expressed in A + + + + + + + + + + + Maximum current change + + + + + + + + + + + + Maximum current at the start of the Segment Profile + + + + + Curve change + + + + + + + + + + + + Curves in the Segment Profile + + + + + + + + + + + Curve at the start of the Segment Profile + + + + + Request from Ground to Board of DAS driving mode. If the Train Identification attribute is not given, the Board will send the reply according to the train for which the communication layer has been established. In the IM-RU setup between IM DAS-TS and RU DAS-TS, if the communication layer is established for multiple trains, the train identifier must be given. + + + + + + + + + + DAS Operating mode. Triplet of attributes (driving mode, architecture, connectivity) + + + + + Area in SP where some operating modes are unavailable + + + + + + + + + + + + + + Operating mode selected by the DAS-TS + + + + + + + + + Operating modes supported by the DAS-OB + + + + + Date and time of the measurement + + + + + Decisive Gradient Area. The decisive gradient is the steepest gradient in front of potential stopping points on a line like signals, level crossings and stations. It is relevant, if it goes downhill, for the calculation of either the minimum braking percentage or the speed limit for a train because in case of reduced braking capacity, the train might not be able to brake properly, therefore, the driver has to adjust the speed. If it goes uphill, it is relevant to calculate the towing load, for example, to make sure, with the available engine power, that the locomotive can get away after it stopped. + + + + + + + + + + + + + + Delay of the train + + + + + + Delay could be measured, delay can be expected (for example before departure), announcement that the delay will be published (upcoming) or can be unkown + + + + + + + This element identifies the reason for a delay (modified DelayReason), with a code according to UIC450-2 + + + + + Inform the IM/RU Backoffice of the reason for an additional delay encountered by a train. For cases where the train is interrupted, or at the points where the train ready message is due (origin station and changes in train characteristics or RU). + + + + + + + + + + + + + [SUBSET-126] Dynamic Brake Force Inhibition Area + + + + + Forecasted earliest time for end of interruption + + + + + Location where the signal stops to be applied. If not specified, the signal is applied indefinitely, until superseded by another signal. This may also be in a different SP: then the SP_ID has to be specified (otherwise the current SP is considered). + + + + + + + + + + Real-time energy data + + + + + + + + + + + + Estimated voltage of the traction system (V) + + + + + + + + + + + Estimated voltage change + + + + + + + + + + + + Temporary constraints on estimated voltage + + + + + + + + Estimated voltage at the start of the Segment Profile + + + + + Energy data expected in the next timeframe (5 to 15 minutes) + + + + + + + + + + + Force On-Board DAS to change its Driving Mode (mainly used to make a DAS-OB readonly) + + + + + + + + + + + + Request for ForceDrivingModeChange messages + + + + + + + + + + + Description of error (ground to board). To be used only if the result of the message is "ERROR" + + + + + + + + + + + + Payload of ground to board event + + + + + + + + + + + + + + Response to message received, ground to board + + + + + + + + + + + Payload of ground to board reply + + + + + + + + + + + + + + + + + + Request from ground to board + + + + + + + + + + + + + Data relevant to the whole JP + + + + + + + + + + Network-Specific Parameter for General JP Information + + + + + GNSS Dynamic Position + + + + + Average gradient in the Segment Profile + + + + + + + + + + + Change in average gradient + + + + + + + + + + + + Average gradient at the start of the Segment Profile + + + + + Steepest gradient in the Segment Profile + + + + + + + + + + + Change in steepest gradient + + + + + + + + + + + + Steepest gradient at the start of the Segment Profile + + + + + Handshake between board and ground for SFERA connection or disconnection + + + + + + + + + + + + + + + + + Rejection of Handshake from Ground to Board + + + + + + + + + + + + Handshake request from board to ground + + + + + + + + + + + + + ID of the Infrastructure Manager. It is a UIC RICS Code. + + + + + The link to the System Reference of the origin system + + + + + + + + + + + + + + + Date and Time when the Train was interrupted + + + + + The free text description of an interruption + + + + + To specifiy the probable duration of the interruption + + + + + + + + + + + Location of the interruption + + + + + Describes the interruption points with location and the reason for the interruption. It must be sent in all Status reports until the interruption has been lifted. + + + + + + + + + + To provide any additional information to the RU or next IM (e.g. contact person, next steps, etc) + + + + + + + + This element identifies the reason for an interruption of the train running, with a code according to UIC450-2 + + + + + Journey Profile for a specific trip + + + + + + + + + + + + + + + Context information related to Journey Profile to be displayed to drivers + + + + + + + + + + + + + + + + + + + + + Constraints on the NSP inside the JP context information + + + + + Journey Profile currently in use + + + + + + + + Journey Profile request. + + + + + + + + + + + + Free text, short name/vehicle keeper marking of the wagon keeper. Use ERA VKM Register: https://www.era.europa.eu/registers/vkm_en + + + + + + + + + + Reference Point on the side of the track indicating the passing of a certain distance, usually relative to a "zero" point of the line. + + + + + + + + + + + + + + Kilometre reference details + + + + + + + + + + + + + + Position of the last absolute point for the odometry. In ETCS odometry, this is the last known group of balises. + + + + + + + + + + + + identifier of the last ForceStatusChange request message sent by the board side of the communication. A ForceStatusChange Reply will only be sent if a ForceStatusChange order has been generated since the last request was sent. Otherwise, a simple "OK" message response is sent. + + + + + + + + Last Km reference point passed by the train + + + + + + + + Last signal passed by the train + + + + + + + + + + + + identifier of the last PlainTextMessage message received by the board side of the communication. One or more PlainTextMessage replies will only be sent if more recent advice is available. Otherwise, a simple "OK" message response is sent. + + + + + + + + identifier of the last StatusReport message received by the ground side of the communication. One or more StatusReport replies will only be sent if more recent data is available. Otherwise, a simple "OK" message response is sent. + + + + + + + + Forecasted latest time for end of interruption + + + + + Gives an indication if a train is entering of leaving your trainpath at a certain location + + + + + Length over buffers is expressed in cm. + + + + + + + + + + + Level crossing area, for driver information + + + + + + + + + + + + + + [SUBSET-126] Dynamic Brake Limitation Area + + + + + + + + + + + + Identification of line and track + + + + + + + + + + + Change of identification of line and track in the course of the Segment Profile + + + + + + + + + + + + Identification of line and track at the start of the Segment Profile + + + + + Reference to a subsidiary location + + + + + + + + + + + + this element identifies a location as a part of primary location e.g. a junction, a signal, a passing loop etc., It is unique when used in combination with a “LocationPrimaryCode” + + + + + + + + + + + + Code, Name and allocation company of Subsidiary Location + + + + + + + + + + + + To be completed in an official language of the Country using the ISO Unicode alphabet + + + + + Locomotive data + + + + + + + + + + + + Total weight of the locomotive (kg) + + + + + Low Adhesion Status Report + + + + + + + + Maximum speed (in km/h) + + + + + + + + Announcement for a downline maximum speed (in km/h). Note that if the distance to the downline MaxSpeed signal is known, it is recommended to use MaxSpeed together with StartSignalApplication. + + + + + + + + Maximum speed of the train possible at this track + + + + + Generated by the common Interface. This element will be filled by the Common Interface prior to sending. + + + + + Header of the IRS90940 message. Contains metadata related to the message + + + + + + + + + + + + + + + + + + + + + + Identification of the Message. This element will be filled by the Common Interface prior to sending. + + + + + This element identifies the message when transmitted via a Common Interface + + + + + + + + + + + + + To indicate the message type transmitted or referred to. For SFERA, the range SF00 to SF99 should be used in a standard implementation. The precise value for each message has been defined in the top element's annotation of the message. The list of SFERA Message types is the following: +SF00 SFERA_B2G_EventMessage +SF01 SFERA_B2G_RequestMessage +SF02 SFERA_B2G_ReplyMessage +SF10 SFERA_G2B_EventMessage +SF11 SFERA_G2B_RequestMessage +SF12 SFERA_G2B_ReplyMessage + + + + + + + + + + + Version of the Message Type + + + + + + + + + + Network-Specific Area + + + + + + + + + + + + + + + Network-Specific Characteristics + + + + + + + + + + + + + Network-Specific Temporary Constraint + + + + + Generic Network-Specific Parameter + + + + + + + + + + Network-Specific Point + + + + + + + + + + + + + + + Data for the next Segment Profile if it is in a different "Zone" + + + + + + + + + + + + + Next timing points for the train + + + + + + + + + + + + + + + ERTMS: Identity number of the country or region. From SUBSET-026 chap. 7, 7.5.1.86 + + + + + It is an area shown to drivers where they should not stop the train. It is typically the case in some of the tunnels, or some of the bridges, or some portions of them. + + + + + Change in the Network-Specific Characteristics + + + + + + + + + + + Start of the Network-Specific Characteristics + + + + + + + + + + Provides a possibility for differentiation between the objects: Train, Path, Case Reference and Path Request. + + + + + + + + + + + + + + + + Identifies the train for traffic management purposes by the Dispatcher, GSMR services, etc. + + + + + Other Specific Static Speed Profile + + + + + + + + + Operational Train Number (OTN) Identifier. The Company code for an RU must be used. + + + + + Contains the identification of the own train + + + + + + + + + + + Permitted Braking Distance Area + + + + + + + + + + + + + + + Network Specific Parameters in Signal Physical Characteristics + + + + + + + + + + Plaintext message + + + + + + + + + + + + Plaintext message request + + + + + + + + + + + Area alongside a railway track providing convenient access to trains + + + + + Position and speed of the train + + + + + + + + + + + + + + + + + + + + Request from Board to Ground of position and speed. If the Train Identification attribute is not given, the Ground will send the reply according to the train for which the communication layer has been established. In the IM-RU setup between IM DAS-TS and RU DAS-TS, if the communication layer is established for multiple trains, the train identifier must be given. + + + + + + + + + + Advice on minimising or maximising the power consumption + + + + + + + + Power Constraint signal + + + + + + + + Precise reason for a delay (string) + + + + + [SUBSET-126] Information concerning the most recent Timing Point considered as either reached or passed + + + + + + + + + + + + + Previous timing points passed by the train + + + + + + + + + + + + + + + Location Name in an offication language of the Country using the ISO Unicode alphabet + + + + + Rated voltage of the traction system (V) + + + + + + + + + + + Rated voltage change + + + + + + + + + + + + Rated voltage at the start of the Segment Profile + + + + + Receiver of the message. The company code (RICS) of the IM or RU shall be used. + + + + + + + + + + + + Expected maximal current for regeneration that can be returned to the overhead contact line + + + + + Contains information about the own train and the related trains, i.e. trains that share the same trainpath (infrastructure) as the own train. + + + + + + + + + + + + + Request for RelatedTrainInformation messages + + + + + + + + + + Free Form Text + + + + + Report of DAS Driving Mode + + + + + + + + + + + Segment Profile from which the JP is requested + + + + + Criteria for applicability related to rolling stock + + + + + + + + + + Only for trains in front of the own train, gives an indication if the train is going in the same direction as you (true) or the opposite direction (false). + + + + + Provides information about the occupation of a Segment Profile (or part of it) by the train. + + + + + + + + + + + + + + + + + + + + Status of the train in the segment + + + + + + + + + + + + Profile for a section of railway track + + + + + + + + + + + + + + + + + + + + + + + Maximum lateral deviation in Segment Profile (in metres). Defines the maximum allowed lateral deviation of of any middle point on the used track to straight lines connecting the virtual balises + + + + + + + + + Information about the Segment Profile + + + + + + + + + + + + + + + + Reference to a Segment Profile traversed by the journey + + + + + + + + + + + + + + + + + + + + + + The sender of the message. The company code (RICS) of the IM or RU shall be used. + + + + + + + + + + + + Session termination + + + + + + + + Request of Session Termination + + + + + + + + + + + Description of the impact on allowed speed, driving reactions of the driver, and traction type induced by permanent signals. The impact described in the SP is the one given by the least restrictive Aspect that can be seen by the driver passing on the specific SP. If the TMS anticipates that the train will meet the signal at an aspect different from the least restrictive aspect, or the signal is cancelled, use TemporarySignalConstraints under JP / SPList (if the signal can be modified or cancelled, the identifier is mandatory). + + + + + List of ATP systems to which the signal is applied (default: all ATP systems supported in the SP) + + + + + Signal identification. The identification may include the identifier of the physical signal of the track and the identifier of the single signal object that contains the specific information (and possibly application). For a single physical signal there may be several signal objects. The pair (signal_ID_Physical, signal_ID_Object) must be unique within an SP. the signal_ID_Object must be documented if 2 or more signal objects are related to the signal_ID_Physical. + + + + + + + + + + Network Specific Parameters in Signal + + + + + Constraints for the application of the signal + + + + + + + + + + + + + + + Function of the signal. The list of values is taken from EULynx SignalFunctionTypes (https://dataprep.eulynx.eu/2021-05/index.htm?goto=3:1:1:4:3762), with the addition of laneChange. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Information contained by the signal that impacts the driving profile + + + + + + + + + + + + + + + + + + Network-Specific Parameters in SignalInformation + + + + + Physical Characteristics of the Signal + + + + + + + + + + + + Train Categories to which the signal is applied + + + + + + Cant Deficiency train category to which the signal is applied + + + + + + + + Other train category to which the signal is applied + + + + + + + + + + + + Minimum and maximum train length to which the signal is applied. + + + + + + + + + Notable areas present in the Segment Profile (e.g. stations, platforms, tunnels...) + + + + + + + + + + + + + + + + + + + + + + + + Characteristics of the Segment Profile (e.g. gradient, curve, cant, voltage...) + + + + + + + + + + + + + + + + + + + Context information related to Journey Profile to be displayed to drivers + + + + + + + + + + + + + + + + + + Notable points present in the Segment Profile (e.g. timing points, signals, balises) + + + + + + + + + + + + + + + Segment Profile request. If version is not included, latest version is requested. + + + + + + + + + + + + + Identifier of the zone containing the Segment Profile. At least one of IM_ID (Infrastructure Manager ID) or NID_C (from ERTMS) must be provided. It is encouraged to systematically provide the IM_ID. If the SP is in a zone covered by a NID_C identifier from ERTMS, it should be provided, especially if conversion to SUBSET-126 is required. + + + + + Specific Static Speed Profile + + + + + Is only used in the operational phase and refers to the date where the single train will start the train journey + + + + + Location where the signal starts to be applied. If not specified, the signal is applied starting from the signal location. This may also be in a different SP: then the SP_ID has to be specified (otherwise the current SP is considered). + + + + + + + + + + Static Speed Profile. It is possible to make different SSPs for different ATP systems if needed. + + + + + + + + + + + + Change of Static Speed Profile + + + + + + + + + + + + Start of the Static Speed Profile + + + + + Sign on the side of the track at stopping distance from the next station, from which point the driver will start to brake to be at the permitted speed in the station. The station is identified by the Timing Point ID. + + + + + Network Specific Parameters in Status Report + + + + + Request for Status Report messages + + + + + + + + + + + Indicates if the train must stop before crossing the signal, for example if the driver must receive an order from the signalling responsible. + + + + + + + + + + + + Criteria for the applicability of a stopping point + + + + + + + + + + + + + + + The stopping point is applicable if this is a preceding SP + + + + + Stopping Point Information + + + + + + + + + + + + + + + + + + + + + + + If the Timing Point is a Stopping Point, point of the track where the head of trains respecting the specified stoppingPointApplicability parameters will stop. + + + + + + + + + + + + + + Stopping zone, common to all alternative TimingPoints for a track/platform. it could be applied in or out of stations (e.g. storage tracks). It is possible to reference a Stopping Zone from Timing Point Constraints. + + + + + Reference to a StoppingZone in the TimingPoint of the Segment Profile + + + + + This field can be used to inform the driver of the reason of a stop. It cannot be used with passing points. The driver may use this for passenger information and to inform the dispatcher on deviations. There will be no other effect on the DAS behavior apart from the information display. + + + + + Type of stop and purpose. Stop types can be: +- Commercial: a stop used by the RU to perform the commercial duties the train was planned for. For a passenger train, this will concern stops where passenger loading or unloading is done. For a freight train, this will concern stops where loading and unloading is done, or when wagons are either added or uncoupled from the train. +- Non Commercial Regulation: stops integrated for the purpose of guaranteeing the fluidity of traffic. +- Non Commercial RU: stops used by the RU to fulfil its specific needs. For example, a train driver relay, or a locomotive change could be characterised by this purpose; +- Non Commercial IM: stops necessary for the IM’s needs; +- Non Commercial Safety: stops used to perform a safety procedure; +- Other: any stop that cannot be categorised using one of the 5 other types. + + + + + + + + + + Superelevation values in the Segment Profile (Difference in rail height between the inside rail and the outside rail in a curve, in m) + + + + + + + + + + + Superelevation change + + + + + + + + + Superelevation at the start of the Segment Profile + + + + + Cant value (mm) + + + + + + + [SUBSET-126] Switch Off Eddy Current Brake for Service Brake Area + + + + + [SUBSET-126] Switch Off Eddy Current Brake for Emergency Brake Area + + + + + [SUBSET-126] Switch Off Magnetic Shoe Brake Area + + + + + [SUBSET-126] Switch Off Regenerative Brake Area + + + + + TAF/TAP Identifier. Daily Reference Train-ID of the train, according to the processes defined by TAF/TAP. + + + + + TAF/TAP Location + + + + + + + + + + + + + + + + + + NSPs for TAF/TAP location + + + + + TAF/TAP Location Identifier + + + + + Name of TAF/TAP location. Allows to list the name of the location in different languages. Only to be used in multilanguage systems, otherwise, use primary location name only. + + + + + Reference to a TAF/TAP location + + + + + + + + + + + + Features for Train Characteristics Change + + + + + + + + + + + + + + + List of train's characteristics, necessary to calculate its dynamic capabilities: length, weight, max speed etc. + + + + + Network Specific Parameters in TC Features + + + + + Train Characteristics request. If version is not included, latest version is requested. + + + + + + + + + + + + + Train Characteristics - Identifier of the RU operating the train (use UIC RICS Code: https://uic.org/rics) + + + + + Context information explaining background of temporary constraint + + + + + + + + + + + Temporary Constraints in the Segment Profile + + + + + + + + + + + + + + + + + + + + + + + + + + + Description of the impact on allowed speed, driving reactions of the driver, and traction type induced by temporary signals, permanent signals with an aspect different from the least restrictive aspect described in SP / SP_Points / Signal, or temporarily cancelled signals on the ground. + + + + + + + + + + + + + + Network Specific Parameters in Traction Force Curve + + + + + Traction type for Traction Force Curve + + + + + Refers to the timetable period in which the business will be carried out (Not used in SFERA. Only kept for consistency with data elements in TAF/TAP) + + + + + + + + + + + A location and stopping accuracy defined in the Segment Profile for which a type (Stopping or Passing Point) and specific time may be identified in the Journey Profile. This time may be an arrival time, a departure time, or in the case of a train not scheduled to stop at that location, the passing time. + + + + + + + + + + + + Distance of the element from the beginning of the segment profile (m) + + + + + + + + + + + Network Specific Parameters in Timing Point + + + + + + + + + + Constraints at specific Timing Points in the Segment Profile (arrival and departure times) + + + + + + + + + + + + + + + + + + + + Network Specific Parameters in Timing Point Constraints + + + + + [SUBSET-126] Estimated arrival time to, at least, the next Timing Point + + + + + + + + + + + + Reference to a Timing Point in the Segment Profile. It could be either the ID of a specific TimingPoint or a Stopping Zone. In the latter case, it is up to the DAS-OB to choose the exact Timing Point. + + + + + + + + + + + The total weight of the transportation unit on the freight wagon. This is the booked or actual weight of goods including packing and carrier's equipment + + + + + Reference to the ID of a TimingPoint in the Segment Profile + + + + + Name of the Timing Point + + + + + + + + + Traction Force Curve for the train. Several curves may be defined for different traction types. + + + + + Data point for the traction force curve (pair of speed and traction force) + + + + + + + + + Identifies position of intermediate traction unit(s) in the train indicating after which wagon (specified by order number) the unit is placed. + + + + + + + + + + + Temporary Constraints on Total current for traction and HVAC consumption + + + + + Traction type transition signal + + + + + + + + + + + Traction type transition signal for bimodal trains + + + + + Traction type transition signal for electric trains + + + + + Contains the identification of a related train and the segments + + + + + + + + + + ATP system supported by the train. The DAS system can then find out what is supported. ATP System Consequences will be for Operational Train data containing its characteristics necessary to calculate its dynamic capabilities. Use the ERA "List of Class B Systems": ERA/ +TD/2011-11 v3.0. http://www.era.europa.eu/Document-Register/Documents/ERA_TD_2011-11%20v30.pdf + + + + + + + + + + Element containing the characteristics of the train + + + + + + + + + + + + + + + + Change in Train Characteristics + + + + + + + + + + + + + + Reference to a Train Characteristics element (sent separately). All TCs are taken from that element; some can be overridden locally (e.g. length, weight, max speed). The TCs will become effective starting from the mentioned location. + + + + + + + + + + + + + + + + + + + + Network Specific Parameters in Train Characteristics Reference + + + + + Identifies the Delta delay time of a train against the booked schedule as well as against the referenced time + + + + + + + Delay compared to the referenced Date/Time + + + + + + + + Identification of the train. Train identification can be TAF/TAP or OTN. At least one of the two should be used; TAF/TAP Identifier takes priority if both communicating parties support it. + + + + + + + + + + + + + + + + + + + Information about the current location and occupation of the train + + + + + + + + + + + + + + + + + + + + 0=Not Ready 1=Ready + + + + + + + + + + + + + + + + + + + + + + + It indicates date/time when the train will be ready for departure. IM contract will define if this element can be used. Only to be used if this time is in future (sent in advance). Purpose of this element is to short the train stay. + + + + + Trains that share the same trainpath as your train, which are behind you. These trains will use the same infrastructure after you. + + + + + + + + + + Trains that share the same trainpath as your train, which will use the same infrastructure before you. + + + + + + + + + + Tunnel + + + + + + + + + + + + + Operating modes that are unavailable. If all 3 attributes are included, the triplet is unavailable; of only 1 or 2 attributes are included, that mode is unavailable independently of the value of the rest of the attributes. + + + + + + + + + + Traction Type of the TrainCharacteristics of the train that has become completely unavailable (e.g. due to a failure of the pantograph) + + + + + + + + + + Stop in rear of an unprotected level crossing + + + + + + + + + + + + The variant shows a relationship between two identifiers referring to the same business case + + + + + + + + + + + + Vehicle relevant data for the vehicles (locos or wagons) within a running train + + + + + + + + + + + A VirtualBalise is a location in the Segment Profile for which the longitude and latitude is known. + + + + + + + + + + + + + + GNSS position of Virtual Balise + + + + + Wagon data + + + + + + + + + + + + + + Maximum allowed speed of the wagon according to the load and entry in the Rolling Stock Databases. In kmh + + + + + + + + + + + Identifies the position of a wagon within a train. Sequential number starting with the first wagon at the front of train as N°1. + + + + + + + + + + + The weight of an empty wagon according to the entry in the rolling stock database + + + + + Temporary constraints due to wind factors + + + + + + + + + Complex Type for ATP Systems + + + + + + + + Complex Type for Braking Force Curves + + + + + + + + + + + + + Complex Type for Current Limitation + + + + + + Complex Type for Curve + + + + + + + Complex type for DAS modes + + + + + + + + Complex type for error in response messages + + + + + + + + Complex Type for GNSS Dynamic Position + + + + + + + + + + + + Complex Type for GNSS Static Position + + + + + + + + Complex Type for Gradient + + + + + + + Complex Type for Line and Track Identifier + + + + + + + + Complex type for multilingual text + + + + + + + + Indication of the Railway or Customer Location + + + + + + + + + + + Complex Type for a list of Network-Specific Parameters + + + + + + + + + Complex Type for the Operational Train Number (OTN) Identifier + + + + + + + + + + + + + + + + + + Complex type containing the data for the identification of Segment Profiles + + + + + + + + + Complex type for the definition of a signal + + + + + + + + + + + + + + Complex Type for generic Segment Profile Area + + + + + + + + + Complex Type for generic Segment Profile Point + + + + + + + Complex Type for SP Zone. At least one of IM_ID (Infrastructure Manager ID) or NID_C (from ERTMS) must be provided. It is encouraged to systematically provide the IM_ID. If the SP is in a zone covered by a NID_C identifier from ERTMS, it should be provided, especially if conversion to SUBSET-126 is required. + + + + + + + + + Complex Type for Specific Static Speed Profile + + + + + + + + + + + Complex type for Static SPeed Profiles + + + + + + + + + + + Complex type for stopping zone + + + + + + + + + + Complex Type for TAF/TAP identifier. Is a modification of TAF/TAP CompositIdentifierOperationalType (ObjectType is always TR). + + + + + + + + + + Is only used in the operational phase and refers to the date where the single train will start the train journey + + + + + + + Complex type for Train Characteristics features + + + + + + + + + + + + + + + + + + + + + + + Braked weight percentage. Unit: Percent. Acceptable values 0-500. Reference: Introduction to ETCS Braking Curves, ERA_ERTMS_040026, Version : 1.4, Date : 14/06/16. https://www.era.europa.eu/sites/default/files/activities/docs/introduction_to_etcs_braking_curves_en.doc + + + + + + + + + + + + + + + + + + + + + Type for the identification of Train Characteristics. Contains the ID of TC, and the mandatory identification of the entity responsible (RU or VKM) for the data + + + + + + + + + + Complex type for Temporary Constraints + + + + + + + + + + Complex type containing the data for the identification of Timing Points + + + + + + + + + Complex Type for Total Current temporary constraints + + + + + + Complex Type for Traction Force Curves + + + + + + + + + + + + + Complex type for Traction Type + + + + + + + + + + + + + + + + Complex Type for train identification. Train identification can be TAP/TAF or OTN. At least one of the two should be used; TAP/TAF Identifier takes priority if both communicating parties support it. + + + + + + + + + Complex type for the "Train" object used in related train information. + + + + + + + + Start from 1, the first is the the nearest and 2 the one after that, etc + + + + + + Complex Type for voltage + + + + + + + + Simple Type for adhesion category + + + + + + + + + + + + + + + + + + + + + + From TAF/TAP: Identifies the RU, IM or other company involved in the Rail Transport Chain + + + + + + ISO 3166-1 alpha code (2 positions) + + + + + + + + + Simple Type for current (A) + + + + + + Reason for a delay or interruption. UIC Leaflet 450-2, Appendix C. +The first digit in the code has to following meaning: +1 Operational planning, Management (IM) +2 Infrastructure installations (IM) +3 Civil engineering causes (IM) +4 Causes of other IM (IM) +5 Commercial causes (RU) +6 Rolling stock (RU) +7 Causes of other RU (RU) +8 External causes +9 Secondary causes +New codes added: +23 Power supply equipment +58 Staff +68 Staff +90 Dangerous incidents, accidents and hazards +91 Track occupation caused by the lateness of the same train +92 Track occupation caused by the lateness of another train +93 Turn round +94 Connection +95 Further investigation needed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Time difference delay (+) or ahead of schedule (-) this shall be 1character + 4 Numeric + + + + + + + + Simple type for DAS devices + + + + + + + + Simple Type for distance attributes + + + + + + Simple Type for energy (kWh) + + + + + + Force Type + + + + + + Clear Text in ISO Unicode character set + + + + + + + + + Type for gradient direction (Downhill or Uphill) + + + + + + + + + Type for gradient values (Numeric value, mm by m) + + + + + + + + + + + + + Simple Type for Cant Deficiency Train Category (NC_CDTRAIN as defined in SUBSET-026). + + + + + + + + + + + + + + + + + + Simple Type for Train Category (NC_TRAIN as defined in SUBSET-026). + + + + + + + + + + Simple type for NID_C + + + + + + + + + + + + + + + + + + + + + Percentage Type + + + + + + + + + Simple Type for power (kW) + + + + + + Simple Type for power (W) + + + + + + + + + + + + + + + Speed Type + + + + + + + + + + + + + + + + + + String with 4 alphanumeric characters + + + + + + + + + Simple Type for TractionTypes + + + + + + + + + + + + + + + Simple type for train part (head, end or last pantograph). To be used for defining which part of the train has to clear a signal for application. + + + + + + + + + + Simple Type for date and time in UTC + + + + + + + + UUIDv4 (random) simpleType as defined in RFC4122 + + + + + + + + + Simple Type for voltage (V) + + + + + + In Kilograms + + + + + + + + + + Additional details + + + + + Type of C-DAS-C Advice + + + + + + + + + + + + + + + + + + + Aim to be as near as possible to the beginning of the arrival window + + + + + [SUBSET-126] Qualifier to indicate if the ALSP is to be applied to the front of the train (no train length delay) or to the end of the train (train length delay). + + + + + Altitude (m). Considering ETRS89 as reference. + + + + + Amount of guarded level crossings (to be used only if levelCrossingGuarding is true) + + + + + Distance of GPS antenna from the front of the train + + + + + How far (in m) after the signal the speed must be maintained. + + + + + Arrival time at the Timing Point + + + + + Acceptable time allowance to be earlier at the TP + + + + + Ascending or Descending + + + + + + + + + + + [SUBSET-126] Qualifier to indicate if a speed limit given for a profile element is to be applied until the front of the train (1, no train length delay) or the end of the train (0, train length delay) has left the element + + + + + [SUBSET-126] Value of the speed level restriction (km/h) + + + + + [SUBSET-126] ATO State, from Subset-126. Only relevant when in ATO mode. + + + + + + + + + + + + + + + + + [SUBSET-126] Identifier of the ATO-TS + + + + + [SUBSET-126] This variable defines if the ATO-OB has to manage the train doors closing (1) or not (0) + + + + + Amount of energy still available on-board for the self-propelled energy source (in kWh) + + + + + [SUBSET-126] Axle load from which the speed restriction is applicable + + + + + + + + + + + + + + + + + + + + + + [SUBSET-126] Identifier of the balise group + + + + + [SUBSET-126] Defines the relative position in a balise group + + + + + Braking Force (in N) + + + + + Delay between braking command and start of braking. + + + + + 1 = The device is able to receive C-DAS-C messages. 0 (Default) = The device is unable to receive C-DAS-C messages + + + + + [SUBSET-126] This variable defines if the doors are to be opened centralised (1) or by the passengers (0) + + + + + This attribute is to be used when the driver will be asked to stop at a stopping which is different from the usual one, and it needs to be brought to his attention. + + + + + Number of a Common Interface Instance for the same Company + + + + + Comfortable acceleration. Expressed in m/s^2 + + + + + Deceleration used for comfortable braking. Expressed in m/s^2 + + + + + Channel for communication network + + + + + Type of communication network. Standard possibilities are: "GSM-R", "FRMCS", "Public GSM", "Public WiFi" and "Private WiFi". + + + + + Description of connection track + + + + + Type of connection track + + + + + + + + + + + + + + + + + + + + + + Identifier to be used to reach the contact on the communication platform defined in ContactType (e.g. telephone number, GSM-R number, pseudo...) + + + + + Notes related to the contact + + + + + Role of the contact + + + + + Type of contact. Communication platform to be used to contact this person: e.g. telephone, GSM-R, social network, messaging app, etc. + + + + + ID of the message for which the current message is a reply + + + + + Direction of the curve (Left or Right) + + + + + + + + + + + + Radius of the curve (m) + + + + + DAS architecture: DAS-C (ground advice calculation) or DAS-O (board advice calculation) + + + + + + + + + + + Behaviour of DAS (Standalone or Connected) + + + + + + + + + + + + DAS connectivity (standalone or connected) + + + + + + + + + + + DAS driving mode (see enumeration) + + + + + + + + + + + + + + + + + DAS Status + + + + + + + + + + + + + + Date and time of first availability of the requested data + + + + + [SUBSET-126] Specifies some information specific for the TPThis variable defines if the daylight saving hour is applicable to calculate the local time + + + + + Delay of the train + + + + + Category of the delay. Delay could be measured, delay can be expected (for example before departure), announcement that the delay will be published (upcoming) or can be unkown + + + + + + + + + + + + + + Difference (in km/h) between maximum allowed track speed/most restrictive speed profile and optimal speed. +The special value 0 indicates "maximum allowed track speed/most restrictive speed profile". + + + + + Type of authorisation for departure (e.g. SMS) + + + + + Expected time of departure at Timing Point expressed in UTC + + + + + Description of the element + + + + + ID of the company to which the message is addressed. Must be a UIC RICS Code: : https://uic.org/rics + + + + + ID of the device to which the message is addressed. For any given company, this ID must be unique. Maximum 128 characters. For G2B messages, if this attribute is empty, the message is directed to all devices in the train. For B2G messages, this attribute can be ignored. + + + + + Direction of application on the Segment Profile (Nominal, Reverse or Both) + + + + + + + + + + + + Name of train type to be displayed to the driver (e.g. ‘TGV’ of ‘VIRM 6’). + + + + + [SUBSET-126] Distance to stop the train in rear of the End of Authority (in m) + + + + + [SUBSET-126] Distance (in m) from a Stopping Point to consider it as reached + + + + + The distanceBeforeSignal attribute gives the point from which to restart accelerating. Depending on the type of signalling, the position of this point will be defined by the visual identification by the driver, the position of a balise, or the time at which the driver will receive an updated information for in-cab signalling. This distance applies to the front of the train. + + + + + Distance (in m) until the Power Constraint is to be applied + + + + + Distance (in m) from the actual Transition Point + + + + + [SUBSET-126] ATO: Driver identifier number as defined in SUBSET-027 v300 §4.2.3.7. + + + + + [SUBSET-126] Maximum Brake Force (kN) to be applied by the Dynamic Brake + + + + + Lower bound of time window for train departure. Only to be used if departure advice is sent. If earliestDepartureTime is not specified, the advice should be obeyed at any time earlier than latestDepartureTime. If both attributes are not specified, the advice should be obeyed immediately. + + + + + Earliest departure time desired, expressed in UTC. + + + + + References the kilometre at the end of the message, preferably linked with a kilometer sign near the track + + + + + References the kilometre where the optimalSpeed changes next, preferably linked with a kilometer sign near the track + + + + + References the (national) line number at the end of the message + + + + + References the (national) line number where the optimalSpeed changes next + + + + + [SUBSET-126] Distance of the element's end location from the beginning of the Segment Profile (m) + + + + + How far (in m) after the considered signal the speed advice must cease to be respected. + + + + + References the name of the end signal of the message, if the end of the message is at or near a signal + + + + + References the name of the signal where the optimalSpeed changes next, if this is at or near a signal + + + + + What part of the train must clear the signal application before the signal stops being applied + + + + + References the start station at the end of the message (long form) + + + + + References the station where the optimalSpeed changes next (long form) + + + + + References the start station at the end of the message (short form) + + + + + References the station where the optimalSpeed changes next (short form) + + + + + Ending date and time (in UTC) + + + + + End of the advice validity. Either both startValidity and endValidity or none of the two must be specified. Both parameters should refer to a future point in time and endValidity must be after startValidity. If either of the two indicates a past time (w.r.t. the clock of the On-Board Unit), which is possible in a situation where Central Unit and OBU are not synchronised, transmission delays etc., then the OBU should treat it as valid but past time indication. + + + + + Time at the end of the optimalSpeed + + + + + Maximum power of the engine. Expressed in W + + + + + Number of engines + + + + + Error Code + + + + + Number on 12 digits allocated to each rail vehicle to uniquely identify it from any other rail vehicle + + + + + + + + + + + Expected consumption current (A) + + + + + Expected regenerative current (A) + + + + + Forecasted arrival at the Timing Point + + + + + Forecasted departure from the Timing Point + + + + + Frequency of the traction system in Hz. If the value is 0, DC is used. + + + + + Precision of the GNSS position. Expressed in metres. + + + + + [SUBSET-126] A single gradient value applicable for the calculation of the Permitted Braking Distance + + + + + Direction of the gradient (Downhill or Uphill) + + + + + [SUBSET-126] Direction of the gradient for the Permitted Braking Distance + + + + + Gradient value (Numeric value, mm by m) + + + + + Reason for handshake rejection + + + + + + + + + + + + + + + + Direction in which the train is going. Expressed in degrees from North (0 = North, 90 = East, 180 = South, 270 = West). + + + + + Highest adhesion percentage for which this specific braking curve is applicable. + + + + + Horizontal offset (in m) from the center of the track to the middle of the object. Negative is left and positive is right. + + + + + Element identifier + + + + + Reason for JP Change + + + + + [SUBSET-126] ATO: JP SP Consistency Error, from Subset-126 (boolean) + + + + + [SUBSET-126] Status of the Journey Profile: "Valid": JP containing the data requested. "Unavailable": JP specifies that the requested part of the Journey Profile is currently not available yet (but all the previously sent JPs are still applicable). "Invalid": JP specifies that the TP identifier asserted in the JPReq does not belong to the preceding JP already sent to the ATO-OB. "Update": JP specifies that the Journey Profile has been updated by the TMS within the current visibility of the ATO-OB. + + + + + + + + + + + + + + Version of the Journey Proifile + + + + + KM reference point (in km) + + + + + ISO 639-1 language code + + + + + + + + + + Upper bound of time window for train departure. Only to be used if departure advice is sent. If latestDepartureTime is not specified, the advice should be obeyed at any time later than earliestDepartureTime. If both attributes are not specified, the advice should be obeyed immediately. + + + + + Latitude in signed degrees format (DDD.ddddd) in the WGS84 system. + + + + + Leave as soon as possible, taking into account also the other variables (boolean). + + + + + Description of level crossing + + + + + Determines if the balise is guarding any level crossing + + + + + Type of level crossing + + + + + Identification of the line. For example “115000” is the Line Identifier for the line from Strasbourg to Basel. + + + + + Name of the line describing the connection stations. For example “Ligne de Strasbourg à Bale." + + + + + Distance of the element from the beginning of the Segment Profile (m) + + + + + Error margin of the position of the head of the train. This margin subtracted from the location attribute gives the least downline position of the train possible at the moment of the measure. Expressed in m + + + + + Error margin of the position of the head of the train. This margin added to the location attribute gives the most downline position of the train possible at the moment of the measure. Expressed in m + + + + + + New codes added: +42 DIUM stations - Places of acceptance/delivery Station open into international traffic of goods (tariff point included in DIUM) – consignment acceptance/delivery station (loading points are excluded and covered by TypeCode 37). +43 Passengers cars public loading Is a type of physical location on the open access network where passengers can put their car on a carrying train +44 Passengers cars private loading Is a type of physical location outside the open access network where passengers can put their car on a carrying train +45 Sewage dump Place for cleaning purposes - disposal of the waste +46 Refuelling Point Location where refuelling takes place +47 Mains Supply Location where energy supply can be provided for the rolling stock e.g. preheating +48 Water Supply Location where water supply can be provided for the rolling stock +49 Compressed plant Train on a track with motion stabled with external air supply for braking systems +50 Indoor cleaning platform Cleaning point -interior +51 Car-wash plant Cleaning point -outdoor +52 Short dry-cleaning track Cleaning point +53 Pollution protective plate Track where floor that avoids pollution of the earth below +54 Sand-filling station Location where sand is filled +55 Repair track Location where a train/wagon/engine can be repaired +56 Signal box The location of a building containing signalling equipment +58 OSJD system based location + + + + + + + Not Defined + not used + + + + + Track + The track is a uniquely defined part of location + + + + + Private Siding + Tracks are not for open access + + + + + Border Point Code + Special code for the Border Points are allocated at the country border and the points between different IM networks. Location of these points sometimes are not geographically same with the station or yard. Therefore these points are "logical point" + + + + + Sorting Code + Destination station of the wagon has a code in order to provide shunting technology. + + + + + Vehicle Parking Points + All points (tracks) + + + + + Public Loading Places + Is a type of physical location on the open access network where consignor or consignee can load or unload wagons + + + + + Private Loading Places + Is a type of physical location outside the open access network where consignor or consignee can load or unload wagons + + + + + IM Path Tariff Point + Price Segment change between two IM Networks. + + + + + Depot / Maintenance workshop. + Place for overhaul or maintenance of the rolling stock. + + + + + + Switch/turnout + The location where two tracks meet or diverge. + + + + + Grade Crossing + The location where two tracks on the same level cross each other. + + + + + Section of the track + Section is inside of the location considered part of a track. + + + + + Twin track point + The spot where is end or start of the twinned track section. + + + + + Retarder (rail brake) + Trackside equipment to control the speed of the wagons running from the shunting hump. + + + + + Platform + The area next to the track which has been raised to make access to railway vehicles easier. + + + + + Railing + barrier Safety equipment used to prevent access to the track by people and animals. + + + + + Movable scotch block + Safety equipment across the track avoiding any unnecessary moving beyond that point. + + + + + Derailing stop / Trap points / Catch points + Safety equipment is on one rail avoiding any unnecessary moving beyond that point. + + + + + Loading equipment + Special equipment to facilitate the loading and unloading on the Public Loading Places. + + + + + Weighbridge + Special equipment is to facilitate the measure of the weight of the wagon. + + + + + Building + Those buildings where IM placed his staff for direct communication with RU staff or the IM buildings serve RU activities as well. + + + + + Level crossing + Place where rail and road crossing in level. on the same level (grade) + + + + + Bridge + Special built structure is over the road + + + + + Tunnel + Structure to to allow a railway line to pass under the surface. + + + + + Underpass + Undercrossing or underground passage under the railway track. (Not used by trains) + + + + + Block section + Block section outside of the location with primary code. In other words: a section on the open track between stations defined by signalling system. + + + + + Signal + A signal is a mechanical or electrical device erected beside a railway line to pass information relating to the state of the line ahead to train drivers/engineers. + + + + + Sign and board + Equipment to inform the board staff for train traffic and shunting. + + + + + Phase break + Border of the power supply systems (catenary). + + + + + Leap in kilometer + The section has deviation in length i. e. the section more or less than called. + + + + + Balise + A balise is an electronic beacon or transponder placed between the rails of a railway as part of an Automatic Train Protection (ATP) system. + + + + + Hot spot detector + Trackside equipment which detects hot wheels or axle-box on passing trains. + + + + + Flat wheel detector + Trackside equipment which detects flat spots on wheels on passing trains. + + + + + Dynamic wheel load + detector Special equipment is in trackside for inspect of the overloaded wagons. + + + + + Freight yard + A freight yard is commercial usage of a physical location which can be used as a sending or a destination station in freight orders of rail freight transports. The freight yard can have his own codification + + + + + Loading point + A loading point is a commercial usage of a physical location. Each loading point is assigned to a yard. + + + + + IM Network link + It allows to link two locations from different IM Networks + + + + + Reservation code + + + + + + Metastation + To mark a meta location that forms the link between different stations that are considered as equal (for the traveller) + + + + + CompanySpecificIdentifier + Company specific identifier of the primary location + + + + + + DIUM stations - Places of acceptance/delivery Station open into international traffic of goods (tariff point included in DIUM) – consignment acceptance/delivery station (loading points are excluded and covered by TypeCode 37). + + + + + + + Passengers cars public loading Is a type of physical location on the open access network where passengers can put their car on a carrying train + + + + + + + Passengers cars private loading Is a type of physical location outside the open access network where passengers can put their car on a carrying train + + + + + + + Sewage dump Place for cleaning purposes - disposal of the waste + + + + + + + Refuelling Point Location where refuelling takes place + + + + + + + Mains Supply Location where energy supply can be provided for the rolling stock e.g. preheating + + + + + + + Water Supply Location where water supply can be provided for the rolling stock + + + + + + + Compressed plant Train on a track with motion stabled with external air supply for braking systems + + + + + + + Indoor cleaning platform Cleaning point -interior + + + + + + + Car-wash plant Cleaning point -outdoor + + + + + + + Short dry-cleaning track Cleaning point + + + + + + + Pollution protective plate Track where floor that avoids pollution of the earth below + + + + + + + Sand-filling station Location where sand is filled + + + + + + + Repair track Location where a train/wagon/engine can be repaired + + + + + + + Signal box The location of a building containing signalling equipment + + + + + + Intermodal Terminal + Intermodal Terminal is a location which provides the space, equipment and operational environment under which the transfer of loading units (freight containers, swap bodies, semi-trailers or trailers) takes place + + + + + OSJD system based location + Location code used within OSJD + + + + + Location ENEE Code + Legacy ENEE code of the parent primary location + + + + + Test Loc + + + + + + Relation to Station + An indicator used to show that this location is a subsidiary of another location. + + + + + + + + Human readable text of the location information where the advice becomes valid (and/or ends), which will be displayed on the OBU. If no location is given the advice is immediately valid. It is recommended to fill the parameter location according to the following rules, depending on the advice instance being sent: +- stop advice: location is the station the train is stopping at (e.g. train approaching a scheduled stop) +- acceleration advice: location is the location (kilometre/landmark etc.) where the acceleration phase starts +- constant speed advice: location is the location (kilometre/landmark etc.) where the cruising phase starts and/or ends +- operational/electrical braking advice: location is the location (kilometre/landmark etc.) where the braking phase starts +- end of advice: location is null +- departure advice: location is the station the train is departing from + + + + + Longitude in signed degrees format (DDD.ddddd) in the WGS84 system. + + + + + Adhesion category aligned with the definition in EN15595 of the wheel-rail interface for the lower boundaries (definition and values taken from SUBSET-126). + + + + + Specifies if this is the main contact for the area + + + + + Flag indicating whether the stop is mandatory (1) or optional (0) + + + + + Maximum advised speed. Must be higher than "speed". + + + + + Maximum current value (A) + + + + + Maximum Cant Deficiency supported by the train (in mm) + + + + + Maximum number of cars + + + + + Maximum number of a certain type of rolling stock + + + + + Maximum power that can be regenerated. Expressed in W + + + + + Maximum force that can be Applied through regenerative braking. Expressed in N + + + + + Maximum Train Length for which stopping point is valid. Expressed in m + + + + + Maximal power that can be delivered form energy source available on-board (in kW) + + + + + Measured current taken from overhead contact line, negative during regeneration (in A) + + + + + Measured voltage at pantograph (in V) + + + + + Identifier of the SFERA message + + + + + String containing the message + + + + + Minimum advised speed. Must be lower than "speed". + + + + + [SUBSET-126] Minimum dwell time at given Stopping Point (in seconds) + + + + + Minimum number of cars + + + + + Minimum number of a certain type of rolling stock + + + + + Minimum time to be waited between two position requests by board + + + + + Minimum Train Length for which stopping point is valid. Expressed in m + + + + + Name of the element + + + + + Cant Deficiency Train Category (NC_CDTRAIN as defined in SUBSET-026). + + + + + Train Category (NC_TRAIN as defined in SUBSET-026). + + + + + New adhesion conditions + + + + + + + + + + + [SUBSET-126] Speed restriction to be applied if the axle load of the train ≥ axleLoadCategory + + + + + [SUBSET-126] ATO: skip next stopping point, from Subset-126 (boolean) + + + + + [SUBSET-126] Country identifier of the traction system. It identifies the information, additional to voltageValue, required to fully define the traction system. Use NID_CTRACTION numbers from ERA document ERA_ERTMS_040001. + + + + + [SUBSET-126] ETCS identity number uniquely defined for ERTMS/ETCS purposes. Values to be assigned. 24 binary positions. + + + + + + + + + + + [SUBSET-126] Operational train running number. NID_OPERATIONAL consists of up to 8 digits which are entered left adjusted into the data field, the leftmost digit is the digit to be entered first. In case the NID_OPERATIONAL is shorter than 8 digits, the remaining space is to be filled with special character “F”. + + + + + + + + + + + [SUBSET-126] Specifies if the ATO-OB has to manage the train doors opening and on which side the passenger exchange doors have to be opened + + + + + + + + + + + + + [SUBSET-126] ATO: Operational conditions fulfilment, from Subset-126 (boolean) + + + + + Optimal speed given to the +driver (km/h). It is recommended to fill the parameter optimalSpeed according to the +following rules, depending on the advice instance being sent: +- Stop Advice: optimalSpeed must be null +- Acceleration Advice: optimalSpeed is the target speed that shall be reached at the end of the acceleration phase +- Constant Speed Advice: optimalSpeed is the target speed for the cruising phase +- Coasting Advice: optimalSpeed is the target speed that shall be reached at the end of the coasting phase +- Operational/Electrical Braking Advice: optimalSpeed is the target speed that shall be reached at the end of the braking phase +- Departure Advice: optimalSpeed is either null or the target speed that shall be reached after departing at the end of the acceleration phase +There is a special value: 999 indicating "maximum allowed track speed" + + + + + Approved version of text (translation when 'false') + + + + + Operational Train Number (free text). For driver information. + + + + + [SUBSET-126] Whether the permitted braking distance is to be achieved with the Service Brake or Emergency Brake + + + + + + + + + + + The meaning depends on the type of advice instance being sent. Only valid with certain types of advice. +- acceleration advice: Percentage of the maximum acceleration +- operational braking advice: Percentage of the maximum operational braking +- electrical braking advice: Percentage of the maximum electrical braking + + + + + [SUBSET-126] Permitted Braking Distance value (in m) + + + + + The original planned departure time from the Timing Point expressed in UTC. Useful for driver information. + + + + + Flag indicating whether the stop is planned (1) or unplanned (0) + + + + + Distance from the head of the train of the last point of the rolling stock that has to clear a zone with a power constraint (lowered pantograph, main switch off…) for that power constraint to be lifted. Expressed in m. + + + + + Request for position/speed update by board + + + + + Instruction for Power Constraint + + + + + + + + + + + + + + + + + Change in power consumption: maximise, minimise or "no advice" ("no advice" cancels a previous advice) + + + + + + + + + + + + Precise reason of the delay + + + + + Is the level crossing protected + + + + + Maximum speed in the range of speed to be avoided + + + + + Minimum speed in the range of speed to be avoided + + + + + reasonCode offers coding for standard situations. If reasonCode is null the last reasonCode received is still valid. +It is possible to use only one of the parameters reasonCode and reasonText +or to send them in combination. Predefined reasonCodes are: +- followTrain: host train is following a conflicting (slower) train +- trainFollowing: host train is preceding a conflicting (faster) train +- merge2nd: conflicting (faster) train merging ahead and entering common route section first +- merge1st: conflicting (slower) train merging ahead, host train enters common route section first +- overtakeTrain: overtake conflicting (slower) train that has (nearly) reached the location of overtaking +- followTrainOvertakeTrain: follow, then overtake conflicting train that hasn’t yet reached the location of overtaking +- beingOvertaken: conflicting (faster) train about to overtake +- trainFollowingBeingOvertaken: conflicting (faster) train following and then overtaking +- trainCrossing: train crossing +- passTrain: passing a train on a single-track line +- beingPassed: host train being passed on a single-track line +- timeSupplementRequired: recovery time is required due to an upcoming temporary speed restriction +- energyOptimisation: optimisation of energy consumption +- endOfReason: the reason previously communicated is not valid anymore. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Human readable text with the reason why this advice message is sent (e.g. describing the conflict or conflict resolution). +It is possible to use only one of the parameters reasonCode and reasonText +or to send them in combination. + + + + + References the ID of the message, that defined the absolute speed, which is still valid + + + + + Request for related train information + + + + + Information on whether related train information is supported + + + + + [SUBSET-126] request to the train to stop with couplers relaxed + + + + + Percentage of power available due to engine problems + + + + + [SUBSET-126] Reporting time cycle for triggering a Status Report for data reporting purposes + + + + + Reason why the related train information is restricted (e.g. missing train location system, or train in another country) + + + + + Result of the message + + + + + + + + + + + Free-form text describing the rolling stock. Used for driver information. + + + + + Description of how the train accelerates. Coefficient used in order to calculate the resistance force (constant value). Expressed in N + + + + + Description of how the train accelerates if no traction or braking is applied. Coefficient used in order to calculate the resistance force (value to be multiplied with v). Expressed in N.s/m + + + + + Description of how the train accelerates if no traction or braking is applied.Coefficient used in order to calculate the resistance force (value to be multiplied with v^2). Expressed in N.s^2/m^2 + + + + + Rotating Mass Factor. The rotating mass of the train is equivalent to the rotational inertia of the train, transformed by gear ratio and wheel diameter respectively. + + + + + [SUBSET-126] ATO: Routing Error, from Subset-126 (boolean). This parameter can also be used by the DAS-OB to notify the DAS-TS that the train is not on the expected route. + + + + + Reason for session termination + + + + + + + + + + + + + + + + + set value by the driver for traction and braking + + + + + SFERA version. The format should be majorVersion.minorVersion (e.g.: 1.43). There should be backwards compatibility for messages with the same major SFERA version. + + + + + + + + + + Identifier of the signal object + + + + + Identifier of the physical signal on the track + + + + + Type of signal from which the physical characteristics of the signal can be deduced. This will be network specific, as no standardised solution has been identified by the SFERA project. Examples: +- CTC (Centralized Traffic Control Signal) +- Permissive (Permissive Signal) +- Permanent signal +- Maximum Sign +- Announcement Sign +- Disque +- Cantonnement BAL 3 aspects + + + + + [SUBSET-126] ATO: Slip/slide reported by TCMS/Train, from Subset-126 (boolean) + + + + + ID of the company sending the message. Must be a UIC RICS Code: : https://uic.org/rics + + + + + ID of the device sending the message. For any given company, this ID must be unique. Maximum 128 characters. + + + + + [SUBSET-126] Altitude (in m) at the beginning of the SP. Considering ETRS89 as reference. Starting at +-1000m. + + + + + Indicates whether the mentioned SP is before or after the current SP + + + + + + + + + + + Travelling direction of the Segment Profile (Nominal or Reverse). If the travelling direction is Reverse, all locations will apply from the end of the segment. Note that Signals and Stopping Points only apply in the nominal direction of the SP. + + + + + + + + + + + Gauge of the segment profile (in mm). Is needed to calculate rolling resistance. + + + + + Segment Profile Identifier + + + + + + + + + + Length of the section of railway covered by the Segment Profile (m) + + + + + Human-readable name of the Segment Profile. To be used only for debugging purposes. + + + + + [SUBSET-126] Status of the Segment Profile (Valid or Invalid). From SUBSET-125: 10.1.3.23 The Segment Profile shall indicate its status to the ATO-OB (Segment Profile Status): +a) Valid: specifies that the data sent correspond to a SPReq sent; +b) Invalid: the requested Segment Profile is not available in the ATO-TS. + + + + + + + + + + + Identifier of the Segment Profile version, major number. + + + + + Identifier of the Segment Profile version, minor number. + + + + + [SUBSET-126] Indicates the type of specific SSP category. + + + + + + + + + + + + speed (km/h) + + + + + [SUBSET-126] Qualifier to indicate if the Static Speed Profile is to be applied to the front of the train (1, no train length delay) or to the end of the train (0, train length delay). + + + + + [SUBSET-126] (If specific_SSP_Category = Cant_Deficiency_SSP) + “Cant Deficiency” SSP category for which a different value for the static line speed exists. Used together with V_DIFF to permit certain trains to go faster or lower than the “international basic static speed” given by SSP_Speed. + + + + + [SUBSET-126] (If specific_SSP_Category = Other_SSP_*) +It is the “other specific” SSP category for which a different value for the static line speed exists. Used together with SS126_V_DIFF to permit trains belonging to the corresponding “other international” train +category to go faster or lower than the “international basic static speed” given by SSP_Speed. + + + + + [SUBSET-126] Basic Static Speed Profile speed. + + + + + Visual reference of a speed limit threshold (e.g. a building along the line). This attribute shall be used only if the object is not already in the Segment Profile. + + + + + [SUBSET-126] Specifies if the element starts, ends, starts and ends or covers the whole concerning Segment Profile + + + + + + + + + + + + + References the kilometre at the beginning of the message, preferably linked with a kilometer sign near the track + + + + + References the (national) line number at the beginning of the message + + + + + [SUBSET-126] Distance of the element's start location from the beginning of the Segment Profile (m) + + + + + How far (in m) after the considered signal the speed advice must be respected. 0 = As soon as possible + + + + + References the name of the start signal of the message, if the start of the message is at or near a signal + + + + + What part of the train must clear the signal application before applying the signal + + + + + References the start station at the beginning of the message (long form) + + + + + References the start station at the beginning of the message (short form) + + + + + Starting date and time (in UTC) + + + + + Begin of the advice validity. Either both startValidity and endValidity or none of the two must be specified. Both parameters should refer to a future point in time and endValidity must be after startValidity. If either of the two indicates a past time (w.r.t. the clock of the On-Board Unit), which is possible in a situation where Central Unit and OBU are not synchronised, transmission delays etc., then the OBU should treat it as valid but past time indication. + + + + + If the Timing Point is a station: identifier of the track where the train will stop or pass through without stopping at the station. +Examples : “1” “14” “2M” “1bis” + + + + + Flag notifying whether the DAS is able to send Status Reports to ground. + + + + + [SUBSET-126] Required stopping tolerance (m) + + + + + Stopping time necessary to complete the procedure, only given if StopBeforePassingSignal is “yes”. The time given is the safety procedure time, and is independent from the commercial stop time. + + + + + Additional details about the stop type + + + + + Type of stop and purpose. Stop types can be: +- Commercial: a stop used by the RU to perform the commercial duties the train was planned for. For a passenger train, this will concern stops where passenger loading or unloading is done. For a freight train, this will concern stops where loading and unloading is done, or when wagons are either added or uncoupled from the train. +- Non Commercial Traffic Management: stops integrated for the purpose of guaranteeing the fluidity of traffic. +- Non Commercial RU: stops used by the RU to fulfil its specific needs. For example, a train driver relay, or a locomotive change could be characterised by this purpose; +- Non Commercial IM: stops necessary for the IM’s needs; +- Non Commercial Safety: stops used to perform a safety procedure; +- Other: any stop that cannot be categorised using one of the 5 other types. + + + + + + + + + + + + + + + Height difference (in m) between outside and inside rail in a curve. Negative values may be used when the outside rail is lower than the inside rail. + + + + + Abbreviation of TAF/TAP location. To be used in case the infrastructure manager defines a standard abbreviation for the locations within its network. + + + + + Type of TAF/TAP location. Examples: + Very important station + Important station + Station + Stopping location + Junction + + + + + Train Characteristics Identifier + + + + + Identifier of the Train Characteristics version, major number. + + + + + Identifier of the Train Characteristics version, minor number. + + + + + Human-readable identifier of document informing driver of temporary constraint + + + + + Reason for the temporary constraint. It is possible to choose one reason among the enumerations; the detailed data has to be put under the related element in the TemporaryConstraint choice. Exceptions are ATO_Inhibition_Zone, DAS_Inhibition_Zone and IM_SDAS_Only, where no additional data is needed. + + + + + + + + + + + + + + + + + + + Type of temporary signal constraints: Temporary Signal (the identifier must then be new, not used by another element), Existing Signal Aspect Modification (the identifier must point to a Signal described in SP / SP_Points if it is present) or Cancelled Signal (the identifier must point to a Signal described in SP / SP_Points). + + + + + + + + + + + + This TractionForceCurve is to be used if the estimated voltage of the considered portion of infrastructure is inferior to this value and superior to a lower estimatedVoltage of another TractionForceCurve (if it exists). + + + + + This TractionForceCurve is to be used if the maximum current value of the considered portion of infrastructure is inferior to this value and superior to a lower maxCurrentValue of another TractionForceCurve (if it exists). + + + + + [SUBSET-126] Minimum time the ATO-OB shall wait before repeating a request if there was no response + + + + + Timestamp. Must be in UTC. + + + + + Abbreviation of the Timing Point name + + + + + [SUBSET-126] (If TP_Pass_Stop_Depart = Stopped) +This qualifier specifies if the train has stopped accurately or not at the Operational Stopping Point. + + + + + + + + + + + + + [SUBSET-126] Defines if the TP location is applicable from the front, middle or rear of the train + + + + + + + + + + + + Timing Point Identifier + + + + + [SUBSET-126] Specifies some information specific for the TP + + + + + + + + + + + Time of arrival at Timing Point expressed in UTC. Has to be specified always except at departure stations. + + + + + [SUBSET-126] Qualifier to indicate if train has stopped at the TP, has departed from the TP or has passed the TP. + + + + + + + + + + + + The original planned latest arrival time at the Timing Point expressed in UTC. Useful for driver information. Not to be used for calculations. + + + + + TP_Priority is used to indicate the relative significance of the Timing Point in a Journey from the driver’s point of view. This information is intended to be used when choosing the information to display. It does not specify a priority in respecting the timing points when the DAS calculates the speed profile (the attributes TP_latestArrivalTime and arrivalWindow in the Journey Profile are the absolute time constraints on timing points). It has 5 possible values : +5: very important station, junction or network border +4: important station, junction or shunting area +3: station, junction or regional border +2: other stopping locations +1: all other timing points + + + + + + + + + + + [SUBSET-126] Specifies if the Timing Point is a Passing Point, an operational Stopping Point or a skipped Stopping Point + + + + + + + + + + + + Type of Timing Point. Examples: +Very important station +Important station +Station +Stopping location +Junction +Regional border +Connexion +Network border +Signal with detection +Grid or shunting area +Service installation +Service stopping location +Movable bridge +Other + + + + + Identification of track on a line (ex : “V1” or “Voie 1” or “Line 1”) + + + + + Traction Force (in N) + + + + + Delay between traction command and start of tractioning. Expressed in s + + + + + Code for the train category (freeform text, limited to 10 characters) + + + + + + + + + + [SUBSET-126] The variable defines if the train is requested to be held at the Stopping Point (1) or not (0) + + + + + The total length of a train. Expressed in m + + + + + The maximum possible speed of the train in km/h + + + + + [SUBSET-126] ATO: Train is moving, from Subset-126 (boolean) + + + + + The sum of all weights of wagons and traction units. Expressed in tonnes (t) + + + + + Tunnel Resistance Prediction coefficient a_e (for engines). Unit: kg.m^(2b_e-1). Typical value 1140. Reference: https://www.sciencedirect.com/science/article/pii/S2210970616300415 (5) + + + + + Tunnel Resistance Prediction coefficient a_w (for wagons). Unit: kg.m^(2b_w-1). Typical value 662. Reference: https://www.sciencedirect.com/science/article/pii/S2210970616300415 (5) + + + + + Tunnel Resistance Prediction coefficient b_e (for engines). No unit. Typical value 1.48. Reference: https://www.sciencedirect.com/science/article/pii/S2210970616300415 (5) + + + + + Tunnel Resistance Prediction coefficient b_w (for wagons). No unit. Typical value 1.75. Reference: https://www.sciencedirect.com/science/article/pii/S2210970616300415 (5) + + + + + [SUBSET-126] Category of the Tunnel (single or double track) + + + + + + + + + + + + Aerodynamic cross section of the tunnel in expressed in m^2. Used in predictive tunnel resistance equation developed by Z. Filipovic. See https://www.sciencedirect.com/science/article/pii/S2210970616300415 (5) + + + + + [SUBSET-126] ATO: Unable to stop at the next Stopping Point, from Subset-126 (boolean) + + + + + [SUBSET-126] Offset to add to the UTC time in order to calculate the local time. Unsigned value +Resolution: 15 min +0 = UTC - 14:00 +56 = UTC ± 0 +112 = UTC + 14:00 +113 - 127 = Spare + + + + + + [SUBSET-126] Absolute Positive Speed associated to a train category + + + + + Value of the element + + + + + Vertical offset (in m) from the top of the track to the middle of the object. + + + + + Voltage value (V) + + + + + Number of wagons + + + + + Wind heading, in degrees from North + + + + + Wind speed + + + + + XML Path to identify the location of the error + + + diff --git a/sfera-mock/src/main/resources/application.yaml b/sfera-mock/src/main/resources/application.yaml index 4d0fb2be..c2c4fbe9 100644 --- a/sfera-mock/src/main/resources/application.yaml +++ b/sfera-mock/src/main/resources/application.yaml @@ -1,3 +1,7 @@ +application: + topic: + sfera-custom-prefix: ${SFERA_CUSTOM_TOPIC_PREFIX:} + info: app: version: '@project.version@' @@ -22,16 +26,15 @@ spring: active: ${STAGE:local} cloud: function: - definition: boardToGround + definition: processB2GMessage stream: bindings: - boardToGround-out-0: - destination: default - solace: - bindings: - boardToGround-in-0: - consumer: - queue-additional-subscriptions: 90940/2/B2G/*/*/* + processB2GMessage-in-0: + destination: ${application.topic.sfera-custom-prefix}90940/2/B2G/> + binder: solace + publishG2BReply-out-0: + binder: solace + destination: ${application.topic.sfera-custom-prefix}90940/2/G2B/ binders: solace: type: solace @@ -46,6 +49,9 @@ spring: SSL_KEY_STORE_FORMAT: JKS SSL_KEY_STORE_PASSWORD: ${SOLACE_KEY_STORE_PASSWORD} default-binder: solace + default: + consumer: + maxAttempts: 1 springdoc: swagger-ui: @@ -73,3 +79,8 @@ auth: client-id: ${CLIENT_ID} client-secret: ${CLIENT_SECRET} scope: api://${SFERA_CLIENT_ID}/.default + +sfera: + company-code: '0085' + sfera-version: '2.01' + source-device: 'TMS' diff --git a/sfera-mock/src/main/resources/jaxb-bindings.xjb b/sfera-mock/src/main/resources/jaxb-bindings.xjb index 4079b8d6..739b5c62 100644 --- a/sfera-mock/src/main/resources/jaxb-bindings.xjb +++ b/sfera-mock/src/main/resources/jaxb-bindings.xjb @@ -4,7 +4,7 @@ jxb:extensionBindingPrefixes="xjc" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> - + diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_insufficientData.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_insufficientData.xml deleted file mode 100644 index 9a870f11..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_insufficientData.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - 0084 - 1084 - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notAvailable.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notAvailable.xml deleted file mode 100644 index 903f090d..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notAvailable.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - 0084 - 1084 - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notImplemented.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notImplemented.xml deleted file mode 100644 index 36c3c7e9..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_notImplemented.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - 0084 - 1084 - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_xmlSchema.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_xmlSchema.xml deleted file mode 100644 index c6000492..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_error_xmlSchema.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - 0084 - 1084 - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_handshake.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_handshake.xml deleted file mode 100644 index 5ecd4669..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_ReplyMessage_handshake.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - 0084 - 1084 - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9232.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9232.xml deleted file mode 100644 index 63fa0c53..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9232.xml +++ /dev/null @@ -1,1963 +0,0 @@ - - - - 0088 - 1088 - - - - - - 1088 - 9232 - - 2022-05-17 - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9310.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9310.xml deleted file mode 100644 index 6dd371fb..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9310.xml +++ /dev/null @@ -1,2407 +0,0 @@ - - - - 0088 - 1088 - - - - - - 1088 - 9310 - - 2022-05-17 - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9315.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9315.xml deleted file mode 100644 index 4ad083f1..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9315.xml +++ /dev/null @@ -1,2469 +0,0 @@ - - - - 0088 - 1088 - - - - - - 1088 - 9315 - - 2022-05-17 - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9358.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9358.xml deleted file mode 100644 index 7229c467..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_JP_request_9358.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 1084 - 9358 - 2022-01-04 - - - 0084 - 1084 - - - - - - 1084 - 9358 - 2022-01-04 - - - - - 0084 - - - - - - - - - - - - - - - - - - - - - - - - - - - 1084 - - - - - 0084 - - - - - - - - - - - 0084 - - - - - - - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_SP_request.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_SP_request.xml deleted file mode 100644 index 1710069b..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_SP_request.xml +++ /dev/null @@ -1,717 +0,0 @@ - - - - - - 1084 - 9358 - 2019-09-27 - - - 0084 - 1084 - - - - - 0084 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0084 - 429 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0084 - 429 - - - - - - - - - - - - - - - - - - - - - - - - 0088 - - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_TC_request.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_TC_request.xml deleted file mode 100644 index 8611e6c7..00000000 --- a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_G2B_Reply_TC_request.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - 1084 - 9358 - 2022-04-01 - - - 1084 - 0084 - - - - 1084 - - - - diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_4816.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_4816.xml new file mode 100644 index 00000000..43fde7a9 --- /dev/null +++ b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_4816.xml @@ -0,0 +1,102 @@ + + + + + 1085 + 4816 + 2022-01-04 + + + + + 0085 + + + + + + + + + + + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_7839.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_7839.xml new file mode 100644 index 00000000..00aae2f1 --- /dev/null +++ b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_JP_7839.xml @@ -0,0 +1,50 @@ + + + + + 1085 + 7839 + 2022-01-04 + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_1.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_1.xml new file mode 100644 index 00000000..fa6841ba --- /dev/null +++ b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_1.xml @@ -0,0 +1,57 @@ + + + + 0085 + + + + + + CH + 3000 + + + + + + + CH + 3001 + + + + + + + CH + 3000 + + + + + + + CH + 3001 + + + + + + + + + + + + + + + + + + diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_2.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_2.xml new file mode 100644 index 00000000..4e00003e --- /dev/null +++ b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_2.xml @@ -0,0 +1,312 @@ + + + + 0085 + + + + + + CH + 3002 + + + + + + + CH + 3003 + + + + + + + CH + 3004 + + + + + + + CH + 3005 + + + + + + + CH + 3006 + + + + + + + CH + 3007 + + + + + + + CH + 3008 + + + + + + + CH + 3009 + + + + + + + CH + 3010 + + + + + + + CH + 3011 + + + + + + + CH + 3012 + + + + + + + CH + 3013 + + + + + + + CH + 3014 + + + + + + + CH + 3015 + + + + + + + CH + 3002 + + + + + + + CH + 3003 + + + + + + + CH + 3004 + + + + + + + CH + 3005 + + + + + + + CH + 3006 + + + + + + + CH + 3007 + + + + + + + CH + 3008 + + + + + + + CH + 3009 + + + + + + + CH + 3010 + + + + + + + CH + 3011 + + + + + + + CH + 3012 + + + + + + + CH + 3013 + + + + + + + CH + 3014 + + + + + + + + + + CH + 3015 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_3.xml b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_3.xml new file mode 100644 index 00000000..d0cd5ec4 --- /dev/null +++ b/sfera-mock/src/main/resources/sfera_example_messages/SFERA_SP_3.xml @@ -0,0 +1,144 @@ + + + + 0085 + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + + CH + 0207 + + + + + + + + + + CH + 0207 + + + + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + CH + 0207 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webapp/src/app/sfera-observer/message-table/message-table.component.html b/webapp/src/app/sfera-observer/message-table/message-table.component.html new file mode 100644 index 00000000..83e51662 --- /dev/null +++ b/webapp/src/app/sfera-observer/message-table/message-table.component.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Direction{{ element.direction }}Topic{{ element.topic }}Type{{ element.type }}Info{{ element.info }}Xml-Message + +
+
diff --git a/webapp/src/app/sfera-observer/message-table/message-table.component.scss b/webapp/src/app/sfera-observer/message-table/message-table.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/webapp/src/app/sfera-observer/message-table/message-table.component.ts b/webapp/src/app/sfera-observer/message-table/message-table.component.ts new file mode 100644 index 00000000..65807e26 --- /dev/null +++ b/webapp/src/app/sfera-observer/message-table/message-table.component.ts @@ -0,0 +1,32 @@ +import { Component, Input } from '@angular/core'; +import { SbbTableDataSource, SbbTableModule } from '@sbb-esta/angular/table'; +import { SimpleXmlComponent } from "../../simple-xml/simple-xml.component"; + +export interface TableData { + direction: string, + topic: string, + type: string, + info: string, + message: string +} + +@Component({ + selector: 'app-message-table', + standalone: true, + imports: [SbbTableModule, SimpleXmlComponent], + templateUrl: './message-table.component.html', + styleUrl: './message-table.component.scss' +}) +export class MessageTableComponent { + + @Input() dataSource: SbbTableDataSource = new SbbTableDataSource([]); + + displayedColumns: string[] = [ + 'direction', + 'topic', + 'type', + 'info', + 'message', + ]; + +} diff --git a/webapp/src/app/sfera-observer/sfera-observer.component.html b/webapp/src/app/sfera-observer/sfera-observer.component.html index 0ea0f874..e5e15944 100644 --- a/webapp/src/app/sfera-observer/sfera-observer.component.html +++ b/webapp/src/app/sfera-observer/sfera-observer.component.html @@ -16,23 +16,70 @@

SFERA Communication

Client ID - local + Use a custom topic prefix + + @if (environmentControl.value) { + + Custom topic prefix + + + } } @else { - + + + -
-
-
B2G {{ b2gTopic }}
-
G2B {{ g2bTopic }}
-
- - @for (message of messages; track $index) { -
- -
+
+ @if (environmentControl.value) { +

Prefix: {{ customPrefixControl.value }}

} +

Company: {{ this.companyControl.value }}

+

Train operation: {{ this.dateControl.value + '_' + this.trainControl.value }}

+

Client ID: {{ this.clientIdControl.value }}

+ + + Send custom XML message to B2G-Topic + + XML String + + + + + + + Send predefined (wrong) XML messages to B2G-Topic + + + + + + + + + Send predefined (special) XML messages to B2G-Topic + + + + + + + } diff --git a/webapp/src/app/sfera-observer/sfera-observer.component.scss b/webapp/src/app/sfera-observer/sfera-observer.component.scss index 872ccadf..a9cf99b9 100644 --- a/webapp/src/app/sfera-observer/sfera-observer.component.scss +++ b/webapp/src/app/sfera-observer/sfera-observer.component.scss @@ -9,25 +9,6 @@ margin-bottom: sbb.pxToRem(16); } -.messages { - .title { - display: flex; - - div { - width: 50%; - } - } - - .message { - display: flex; - - &.left { - width: 50% - } - - &.right { - margin-left: auto; - width: 50% - } - } +.ml-3 { + margin-left: sbb.pxToRem(3); } diff --git a/webapp/src/app/sfera-observer/sfera-observer.component.ts b/webapp/src/app/sfera-observer/sfera-observer.component.ts index 6d61d9e7..baf9e483 100644 --- a/webapp/src/app/sfera-observer/sfera-observer.component.ts +++ b/webapp/src/app/sfera-observer/sfera-observer.component.ts @@ -11,6 +11,11 @@ import { CommonModule } from "@angular/common"; import { MqttConnectionState } from "ngx-mqtt"; import { OidcSecurityService } from "angular-auth-oidc-client"; import { SbbCheckboxModule } from "@sbb-esta/angular/checkbox"; +import { environment } from "../../environment/environment"; +import { MessageTableComponent, TableData } from "./message-table/message-table.component"; +import { SbbTableDataSource } from "@sbb-esta/angular/table"; +import { READONLY_MODE, SferaXmlCreation, SpRequestOptions } from "./sfera-xml-creation"; +import { SbbAccordionModule } from "@sbb-esta/angular/accordion"; @Component({ selector: 'app-sfera-observer', @@ -23,22 +28,29 @@ import { SbbCheckboxModule } from "@sbb-esta/angular/checkbox"; SbbButtonModule, SbbCheckboxModule, SimpleXmlComponent, + MessageTableComponent, + SbbAccordionModule, ], templateUrl: './sfera-observer.component.html', styleUrl: './sfera-observer.component.scss', }) export class SferaObserverComponent implements OnDestroy { - companyControl = new FormControl('1088', {nonNullable: true}); - trainControl = new FormControl('9232', {nonNullable: true}); + companyControl = new FormControl('1085', {nonNullable: true}); + trainControl = new FormControl('4816', {nonNullable: true}); dateControl = new FormControl(new Date().toISOString().split('T')[0], {nonNullable: true}); - clientIdControl = new FormControl('', {nonNullable: true}); - environmentControl = new FormControl(false, {nonNullable: true}); + clientIdControl = new FormControl(environment.mqttServiceOptions.clientId, {nonNullable: true}); + environmentControl = new FormControl(environment.customTopicPrefix.length > 0, {nonNullable: true}); + customPrefixControl = new FormControl(environment.customTopicPrefix, {nonNullable: true}); + xmlStringControl = new FormControl('', {nonNullable: true}); g2bTopic?: string; b2gTopic?: string; - messages: { message: string, topic: string }[] = []; + eventTopic?: string; + dataSource: SbbTableDataSource = new SbbTableDataSource([]); + data: TableData[] = []; g2bSubscription?: Subscription; b2gSubscription?: Subscription; + eventSubscription?: Subscription; protected readonly MqttConnectionState = MqttConnectionState; @@ -48,31 +60,331 @@ export class SferaObserverComponent implements OnDestroy { } async observe() { - const env = this.environmentControl.value ? 'local/' : ''; + const customTopicPrefix = this.environmentControl.value ? this.customPrefixControl.value : ''; const trainOperation = this.dateControl.value + '_' + this.trainControl.value; - this.g2bTopic = env + '90940/2/G2B/' + this.companyControl.value + '/' + trainOperation + '/' + this.clientIdControl.value; - this.b2gTopic = env + '90940/2/B2G/' + this.companyControl.value + '/' + trainOperation + '/' + this.clientIdControl.value + this.g2bTopic = customTopicPrefix + '90940/2/G2B/' + this.companyControl.value + '/' + trainOperation + '/' + this.clientIdControl.value; + this.b2gTopic = customTopicPrefix + '90940/2/B2G/' + this.companyControl.value + '/' + trainOperation + '/' + this.clientIdControl.value; + this.eventTopic = customTopicPrefix + '90940/2/event/' + this.companyControl.value + '/' + trainOperation; const exchangeToken = await firstValueFrom(this.authService.exchange(this.companyControl.value, trainOperation, 'read-only')); const username = await firstValueFrom(this.oidcSecurityService.getUserData().pipe(map((data) => data?.preferred_username))); await this.mqService.connect(username, exchangeToken); this.g2bSubscription = this.mqService.observe(this.g2bTopic) .subscribe(value => { - this.messages.push({message: value.payload.toString(), topic: 'g2b'}) + this.addData(value.payload.toString(), 'g2b'); }) - this.b2gSubscription = this.mqService.observe(this.b2gTopic).subscribe(value => { - this.messages.push({message: value.payload.toString(), topic: 'b2g'}) + this.b2gSubscription = this.mqService.observe(this.b2gTopic) + .subscribe(value => { + this.addData(value.payload.toString(), 'b2g'); + }) + this.eventSubscription = this.mqService.observe(this.eventTopic).subscribe(value => { + this.addData(value.payload.toString(), 'event'); }) } disconnect() { - this.messages = []; + this.data = []; + this.dataSource = new SbbTableDataSource([]); this.g2bSubscription?.unsubscribe(); this.b2gSubscription?.unsubscribe(); + this.eventSubscription?.unsubscribe(); this.mqService.disconnect(); } ngOnDestroy() { this.disconnect(); } + + addData(xml: string, topic: string) { + const document = this.toDom(xml); + const row = { + direction: topic == "b2g" ? "↑" : "↓", + topic: topic, + type: this.getType(document) || '', + info: this.getInfo(document) || '', + message: xml + } + + this.data.push(row); + this.dataSource = new SbbTableDataSource(this.data); + } + + toDom(xmlString: string) { + const parser = new DOMParser(); + return parser.parseFromString(xmlString, 'text/xml'); + } + + private getType(document: Document) { + return document.firstChild?.nodeName || ''; + } + + private getInfo(document: Document) { + + const type = this.getType(document); + + if (type == "SFERA_G2B_ReplyMessage") { + if (this.isHandshakeAcknowledgement(document)) { + return `HS-ACK: ${this.getSelectedArchitecture(document)}, ${this.getSelectedConnectivity(document)}`; + } else if (this.isHandshakeReject(document)) { + return `HS-REJECT: ${this.getRejectReason(document)}`; + } else if (this.isMessageResponse(document)) { + const result = this.getMessageResponseResult(document); + if (result == 'OK') { + return 'OK'; + } else if (result == 'ERROR') { + return `ERROR: ${this.getErrorCode(document)}`; + } + } else if (this.containsElement(document, 'JourneyProfile')) { + return `JP: ${this.getJourneyProfileStatus(document)}, #SP: ${this.getJourneyProfileNumberOfSPs(document)}`; + } else if (this.containsElement(document, 'SegmentProfile')) { + return this.getSegmentProfiles(document); + } + } else if (type == "SFERA_B2G_RequestMessage") { + if (this.isHandshakeRequest(document)) { + return `HS-REQUEST`; + } + + const requestTypes = ['JP_Request', 'SP_Request', 'TC_Request'] + const requestedTypes: string[] = []; + + for (const requestType of requestTypes) { + if (this.containsElement(document, requestType)) { + requestedTypes.push(requestType) + } + } + + return requestedTypes.join(", "); + } + return "unknown"; + } + + private isMessageResponse(document: Document) { + return document.getElementsByTagName("G2B_MessageResponse")?.length > 0; + } + + private getMessageResponseResult(document: Document) { + return document.getElementsByTagName("G2B_MessageResponse").item(0)?.getAttribute("result") || undefined; + } + + private getErrorCode(document: Document) { + return document.getElementsByTagName("G2B_Error").item(0)?.getAttribute("errorCode") || undefined; + } + + private isHandshakeReject(document: Document): boolean { + return document.getElementsByTagName("HandshakeReject")?.length > 0; + } + + private isHandshakeAcknowledgement(document: Document): boolean { + return document.getElementsByTagName("HandshakeAcknowledgement")?.length > 0; + } + + private isHandshakeRequest(document: Document) { + return document.getElementsByTagName("HandshakeRequest")?.length > 0; + } + + private getSelectedArchitecture(document: Document) { + return document.getElementsByTagName("DAS_OperatingModeSelected").item(0)?.getAttribute("DAS_architecture") || undefined; + } + + private getSelectedConnectivity(document: Document) { + return document.getElementsByTagName("DAS_OperatingModeSelected").item(0)?.getAttribute("DAS_connectivity") || undefined; + } + + private getRejectReason(document: Document) { + return document.getElementsByTagName("HandshakeReject").item(0)?.getAttribute("handshakeRejectReason") || undefined; + } + + private containsElement(document: Document, elementName: string) { + return document.getElementsByTagName(elementName)?.length > 0; + } + + private getJourneyProfileStatus(document: Document) { + return document.getElementsByTagName("JourneyProfile").item(0)?.getAttribute("JP_Status") || undefined; + } + + + private getJourneyProfileNumberOfSPs(document: Document) { + return document.getElementsByTagName("SegmentProfileList")?.length || undefined; + } + + private getSegmentProfiles(document: Document) { + const segmentProfiles = Array.from(document.getElementsByTagName("SegmentProfile")); + return segmentProfiles.map(segmentProfile => `SP ${this.getSegmentProfileId(segmentProfile)}: ${this.getSegmentProfileStatus(segmentProfile)}, Length: ${this.getSegmentProfileLength(segmentProfile)}`).join('\n') + } + + private getSegmentProfileStatus(element: Element) { + return element.getAttribute("SP_Status") || undefined; + } + + private getSegmentProfileLength(element: Element) { + return element.getAttribute("SP_Length") || undefined; + } + + private getSegmentProfileId(element: Element) { + return element.getAttribute("SP_ID"); + } + + sendXml() { + const xmlString = this.xmlStringControl.value; + this.mqService.publish(this.b2gTopic!, xmlString); + } + + sendHandshakeRequest() { + const handshakeRequest = SferaXmlCreation.createHandshakeRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + supportedModes: READONLY_MODE + }); + this.mqService.publish(this.b2gTopic!, handshakeRequest); + } + + sendJPRequest() { + const jpRequest = SferaXmlCreation.createRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + jpRequests: [{ + trainIdentification: { + company: this.companyControl.value, + operationalTrainNumber: this.trainControl.value, + startDate: this.dateControl.value + } + }] + }); + this.mqService.publish(this.b2gTopic!, jpRequest); + } + + sendSPRequest() { + const jpReplies = this.data.filter(row => row.type === 'SFERA_G2B_ReplyMessage' && row.info.includes('JP: Valid')) + if (jpReplies.length === 0) { + alert('No JP request sent') + return; + } + + const dom = this.toDom(jpReplies[jpReplies.length - 1].message) + + const segmentProfileLists = Array.from(dom.getElementsByTagName('SegmentProfileList')); + const segmentProfiles = segmentProfileLists.map(element => { + const spId = element.getAttribute('SP_ID'); + const imId = element.getElementsByTagName('IM_ID')[0].textContent; + const minorVersion = element.getAttribute('SP_VersionMinor'); + const majorVersion = element.getAttribute('SP_VersionMajor'); + + return { + spZone: { + imId: imId + }, + spId: spId, + majorVersion: majorVersion, + minorVersion: minorVersion + } as SpRequestOptions; + }); + + const spRequest = SferaXmlCreation.createRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + spRequests: segmentProfiles + }); + this.mqService.publish(this.b2gTopic!, spRequest); + } + + sendHSRWrongSferaVersion() { + const handshakeRequest = SferaXmlCreation.createHandshakeRequest({ + header: { + sferaVersion: '2.00', + sourceDevice: this.clientIdControl.value + }, + supportedModes: READONLY_MODE + }); + this.mqService.publish(this.b2gTopic!, handshakeRequest); + } + + sendHSRWrongConnectivity() { + const handshakeRequest = SferaXmlCreation.createHandshakeRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + supportedModes: [{ + drivingMode: 'Read-Only', connectivity: 'Standalone', architecture: 'BoardAdviceCalculation' + }] + }); + this.mqService.publish(this.b2gTopic!, handshakeRequest); + } + + sendHSRWrongArchitecture() { + const handshakeRequest = SferaXmlCreation.createHandshakeRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + supportedModes: [{ + drivingMode: 'Read-Only', connectivity: 'Connected', architecture: 'GroundAdviceCalculation' + }] + }); + this.mqService.publish(this.b2gTopic!, handshakeRequest); + } + + sendHSRDriverWithoutReadonly() { + const handshakeRequest = SferaXmlCreation.createHandshakeRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + supportedModes: [{ + drivingMode: 'DAS not connected to ATP', + connectivity: 'Connected', + architecture: 'BoardAdviceCalculation' + }] + }); + this.mqService.publish(this.b2gTopic!, handshakeRequest); + } + + sendHSRDriverAndReadOnly() { + const handshakeRequest = SferaXmlCreation.createHandshakeRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + statusReportsEnabled: true, + supportedModes: [{ + drivingMode: 'DAS not connected to ATP', + connectivity: 'Connected', + architecture: 'BoardAdviceCalculation' + }, { + drivingMode: 'Read-Only', connectivity: 'Connected', architecture: 'BoardAdviceCalculation' + }] + }); + this.mqService.publish(this.b2gTopic!, handshakeRequest); + } + + sendJPRequestWithWrongTrainnumber() { + const jpRequest = SferaXmlCreation.createRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + jpRequests: [{ + trainIdentification: { + company: this.companyControl.value, + operationalTrainNumber: '' + (parseInt(this.trainControl.value) * 2), + startDate: this.dateControl.value + } + }] + }); + this.mqService.publish(this.b2gTopic!, jpRequest); + } + + sendJPRequestWithWrongCompany() { + const jpRequest = SferaXmlCreation.createRequest({ + header: { + sourceDevice: this.clientIdControl.value + }, + jpRequests: [{ + trainIdentification: { + company: '' + (parseInt(this.companyControl.value) * 2), + operationalTrainNumber: this.trainControl.value, + startDate: this.dateControl.value + } + }] + }); + this.mqService.publish(this.b2gTopic!, jpRequest); + } } diff --git a/webapp/src/app/sfera-observer/sfera-xml-creation.ts b/webapp/src/app/sfera-observer/sfera-xml-creation.ts new file mode 100644 index 00000000..c6407392 --- /dev/null +++ b/webapp/src/app/sfera-observer/sfera-xml-creation.ts @@ -0,0 +1,197 @@ +export interface SferaHeaderOptions { + sferaVersion?: string, + messageId?: string, + timestamp?: string, + sourceDevice?: string, + sender?: string, + recipient?: string, +} + +export interface HandshakeRequestOptions { + header?: SferaHeaderOptions, + statusReportsEnabled?: boolean, + relatedTrainRequest?: 'None' | 'OwnTrain' | 'RelatedTrains' | 'OwnTrainAndRelatedTrains' | 'OwnTrainAndOrRelatedTrains', + supportedModes?: SupportedOperationModes[] +} + +export interface TrainIdentification { + startDate: string; + operationalTrainNumber: string; + company: string +} + +export interface SPZone { + imId?: string; + nidC?: string; +} + +export interface SegmentProfileIdentification { + spZone: SPZone; + spId: string; +} + +export interface JpRequestOptions { + trainIdentification: TrainIdentification; + requestFromSegmentProfile?: SegmentProfileIdentification + jpInUse?: string +} + +export interface SpRequestOptions { + spZone: SPZone; + spId: string; + majorVersion: string; + minorVersion: string; +} + +export interface RequestOptions { + header?: SferaHeaderOptions, + jpRequests?: JpRequestOptions[] + spRequests?: SpRequestOptions[] +} + +export interface SupportedOperationModes { + drivingMode: 'Read-Only' | 'Inactive' | 'DAS not connected to ATP', + architecture: 'BoardAdviceCalculation' | 'GroundAdviceCalculation', + connectivity: 'Standalone' | 'Connected', +} + +export const INACTIVE_MODE: SupportedOperationModes[] = [{ + drivingMode: 'Inactive', connectivity: 'Standalone', architecture: 'BoardAdviceCalculation' +}]; + +export const READONLY_MODE: SupportedOperationModes[] = [{ + drivingMode: 'Read-Only', connectivity: 'Connected', architecture: 'BoardAdviceCalculation' +}]; + +export const ACTIVE_MODE: SupportedOperationModes[] = [{ + drivingMode: 'DAS not connected to ATP', connectivity: 'Connected', architecture: 'BoardAdviceCalculation' +}, { + drivingMode: 'Read-Only', connectivity: 'Connected', architecture: 'BoardAdviceCalculation' +}]; + +export class SferaXmlCreation { + + static createHandshakeRequest(options?: HandshakeRequestOptions) { + let headerOptions = options?.header || this.defaultHeader(); + headerOptions = this.fillUndefindeHeaderFields(headerOptions); + + const relatedTrainRequestTag = options?.relatedTrainRequest + ? `relatedTrainRequest="${options!.relatedTrainRequest}"` + : ''; + + const statusReportsEnabledTag = options?.statusReportsEnabled + ? `statusReportsEnabled="${options!.statusReportsEnabled}"` + : ''; + + const supportedOperationModes = options?.supportedModes?.map(mode => { + return ``; + })?.join(""); + + return ` + + + ${headerOptions.sender} + ${headerOptions.recipient} + + + ${supportedOperationModes} + + + `; + } + + static createRequest(options: RequestOptions): string { + let headerOptions = options?.header || this.defaultHeader(); + headerOptions = this.fillUndefindeHeaderFields(headerOptions); + + const jpRequests = this.createJpRequest(options.jpRequests); + const spRequests = this.createSpRequest(options.spRequests); + + return ` + + + ${headerOptions.sender} + ${headerOptions.recipient} + + + ${jpRequests} + ${spRequests} + + + `; + } + + static createJpRequest(jpRequests: JpRequestOptions[] | undefined): string { + const strings = jpRequests?.map(jpRequest => { + const jpInUseElement = jpRequest?.jpInUse + ? `` + : ''; + + const requestFromSegmentProfileElement = jpRequest?.requestFromSegmentProfile + ? ` + + ${jpRequest.requestFromSegmentProfile.spZone.imId} + ${jpRequest.requestFromSegmentProfile.spZone.nidC} + + ` + : ''; + + return ` + + + ${jpRequest.trainIdentification.company} + ${jpRequest.trainIdentification.operationalTrainNumber} + ${jpRequest.trainIdentification.startDate} + + + ${requestFromSegmentProfileElement} + ${jpInUseElement} + + `; + + }); + return (strings || []).join(''); + } + + static createSpRequest(spRequests: SpRequestOptions[] | undefined): string { + const strings = spRequests?.map(spRequest => { + return ` + + ${spRequest.spZone.imId} + + + `; + }); + return (strings || []).join(''); + } + + private static defaultHeader(): SferaHeaderOptions { + return { + sferaVersion: '2.01', + messageId: crypto.randomUUID(), + timestamp: this.currentTimestampSferaFormat(), + sourceDevice: 'DAS', + sender: '1085', + recipient: '0085', + } + } + + static currentTimestampSferaFormat(date?: Date): string { + const millisecondsRemoveRegex = /\.[0-9]*/i; + return (date || new Date()).toISOString().replace(millisecondsRemoveRegex, ''); + } + + private static fillUndefindeHeaderFields(headerOptions: SferaHeaderOptions) { + headerOptions.sferaVersion = headerOptions.sferaVersion || '2.01'; + headerOptions.timestamp = headerOptions.timestamp || this.currentTimestampSferaFormat() + headerOptions.sender = headerOptions.sender || '1085'; + headerOptions.recipient = headerOptions.recipient || '0085'; + headerOptions.messageId = headerOptions.messageId || crypto.randomUUID(); + headerOptions.sourceDevice = headerOptions.sourceDevice || 'DAS'; + return headerOptions; + } + +} + diff --git a/webapp/src/app/simple-xml/simple-xml.component.ts b/webapp/src/app/simple-xml/simple-xml.component.ts index c6fad439..7ce6d537 100644 --- a/webapp/src/app/simple-xml/simple-xml.component.ts +++ b/webapp/src/app/simple-xml/simple-xml.component.ts @@ -11,7 +11,7 @@ export class SimpleXmlComponent implements AfterViewInit{ @Input() xml: Document | undefined; @Input() xmlString: string | undefined; @Input() collapsedText: string = '...'; - // todo @Input() collapsed: boolean = false; + @Input() collapsed: boolean = false; constructor(private el: ElementRef, private renderer: Renderer2) { } @@ -33,7 +33,7 @@ export class SimpleXmlComponent implements AfterViewInit{ const wrapperNode = this.renderer.createElement('span'); this.renderer.addClass(wrapperNode, 'simpleXML'); if (xml) { - this.showNode(wrapperNode, xml); + this.showNode(wrapperNode, xml, this.collapsed); this.renderer.appendChild(this.el.nativeElement, wrapperNode); const expanderHeaders = wrapperNode.querySelectorAll('.simpleXML-expanderHeader'); @@ -64,10 +64,10 @@ export class SimpleXmlComponent implements AfterViewInit{ } } - private showNode(parent: HTMLElement, xml: Node) { + private showNode(parent: HTMLElement, xml: Node, collapsed: boolean) { if (xml.nodeType == 9) { for (let i = 0; i < xml.childNodes.length; i++) { - this.showNode(parent, xml.childNodes[i]); + this.showNode(parent, xml.childNodes[i], collapsed); } return; } @@ -82,7 +82,8 @@ export class SimpleXmlComponent implements AfterViewInit{ const expanderSpan = this.makeSpan('', 'simpleXML-expander'); if (expandingNode) { - this.renderer.addClass(expanderSpan, 'simpleXML-expander-expanded'); + this.renderer.addClass(expanderSpan, 'simpleXML-expander-collapsed'); + this.renderer.addClass(expanderSpan, collapsed ? 'collapsed' : 'expanded'); } this.renderer.appendChild(expanderHeader, expanderSpan); @@ -110,13 +111,16 @@ export class SimpleXmlComponent implements AfterViewInit{ const ulElement = this.renderer.createElement('ul'); for (let i = 0; i < xml.childNodes.length; i++) { const liElement = this.renderer.createElement('li'); - this.showNode(liElement, xml.childNodes[i]); + this.showNode(liElement, xml.childNodes[i], collapsed); this.renderer.appendChild(ulElement, liElement); } + const collapsedTextStyle = collapsed ? 'inline' : 'none'; + const contentStyle = collapsed ? 'none' : ''; const collapsedTextSpan = this.makeSpan(this.collapsedText, 'simpleXML-collapsedText'); - collapsedTextSpan.setAttribute('style', 'display: none;'); + collapsedTextSpan.setAttribute('style', `display: ${collapsedTextStyle};`); ulElement.setAttribute('class', 'simpleXML-content'); + ulElement.setAttribute('style', `display: ${contentStyle};`); this.renderer.appendChild(parent, collapsedTextSpan); this.renderer.appendChild(parent, ulElement); diff --git a/webapp/src/environment/environment.model.ts b/webapp/src/environment/environment.model.ts index b7406950..78d64eee 100644 --- a/webapp/src/environment/environment.model.ts +++ b/webapp/src/environment/environment.model.ts @@ -5,6 +5,7 @@ export interface Environment { production: boolean; label: string; oauthProfile: string; + customTopicPrefix: string; backendUrl: string; authConfig:PassedInitialConfig ; mqttServiceOptions:IMqttServiceOptions; diff --git a/webapp/src/environment/environment.ts b/webapp/src/environment/environment.ts index 8982c51b..2dffd947 100644 --- a/webapp/src/environment/environment.ts +++ b/webapp/src/environment/environment.ts @@ -3,6 +3,7 @@ import { PassedInitialConfig } from "angular-auth-oidc-client"; import { IMqttServiceOptions } from "ngx-mqtt"; const backendUrl = 'https://sfera-mock.app.sbb.ch'; +const customTopicPrefix = ''; const authConfig: PassedInitialConfig = { config: { @@ -28,7 +29,7 @@ const mqttServiceOptions: IMqttServiceOptions = { clean: true, // Retain session connectTimeout: 4000, // Timeout period reconnectPeriod: 4000, // Reconnect period - clientId: 'webapp-' + crypto.randomUUID(), + clientId: crypto.randomUUID(), protocol: 'wss', connectOnCreate: false } @@ -37,6 +38,7 @@ export const environment: Environment = { production: false, label: 'dev', oauthProfile: 'azureAd', + customTopicPrefix, backendUrl, authConfig, mqttServiceOptions,