diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..5801f2e --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,20 @@ +name: Build and test +on: [pull_request, workflow_call] + +jobs: + tests: + runs-on: ${{matrix.os}} + strategy: + matrix: + os: [ "ubuntu-latest", "windows-latest"] + version: [21, 25] + fail-fast: false + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v4 + with: + java-version: ${{matrix.version}} + distribution: 'temurin' + architecture: x64 + - name: Build with maven + run: mvn clean install diff --git a/README.md b/README.md index 0b5bbe9..9dc9261 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,44 @@ This is an attempt to port the original work https://github.com/ornl-epics/pvws to a Spring Boot-based web application. While the code is adapted to the web socket APIs of Spring Boot, most of the code is essentially unchanged. +Endpoints +========= + + +`HTTP`/`HTTPS` +------ +`/pvws` + +This shows a webpage with documentation and test utilities. + +`pvws/info` + +This returns JSON information with general server info. + +`pvws/summary` + +This returns JSON with a summary of all active web sockets. + +`pvws/socket` + +This returns JSON with details on all active web sockets and their PVs. + +`pvws/pool` + +This returns JSON with a listing of all PVs in the PV connection pool. + +`pvws/pvget` + +This returns a single value read from a PV in the same JSON format as a websocket message. + +Takes the fully qualified PV address as the `name` parameter. + +`WS`/`WSS` +---------- + +`pvws/pv` + +This is the main websocket connection endpoint. See `/pvws` for information on commands. + Requirements ------------ diff --git a/src/main/java/org/phoebus/pvws/EpicsConfiguration.java b/src/main/java/org/phoebus/pvws/EpicsConfiguration.java index 246773d..1c8bfd6 100644 --- a/src/main/java/org/phoebus/pvws/EpicsConfiguration.java +++ b/src/main/java/org/phoebus/pvws/EpicsConfiguration.java @@ -61,12 +61,13 @@ public class EpicsConfiguration { @Value("${PV_WRITE_SUPPORT:true}") private String pvWriteSupport; + @Value("${PV_READ_TIMEOUT:5000}") + private String pvReadTimeout; + @PostConstruct public void init() { logger.log(Level.INFO, "==========================================="); logger.log(Level.INFO, contextPath + " started"); - logger.log(Level.INFO, "Supported PV types: " + PVPool.getSupportedPrefixes()); - // Set default type in preferences before PVPool reads the preferences String default_type = System.getenv("PV_DEFAULT_TYPE"); if (default_type != null && !default_type.isEmpty()) { @@ -77,6 +78,7 @@ else if (pvDefaultType != null && !pvDefaultType.isEmpty()) { Preferences.userRoot().node("/org/phoebus/pv").put("default", pvDefaultType); } logger.log(Level.INFO, "PV_DEFAULT_TYPE=" + Preferences.userRoot().node("/org/phoebus/pv").get("default", null)); + logger.log(Level.INFO, "Supported PV types: " + PVPool.getSupportedPrefixes()); Preferences.userRoot().node("/org/phoebus/pv/pva").put("epics_pva_addr_list", epicsPvaAddrList); @@ -90,6 +92,7 @@ else if (pvDefaultType != null && !pvDefaultType.isEmpty()) { System.setProperty("PV_THROTTLE_MS", pvThrottleMs); System.setProperty("PV_ARRAY_THROTTLE_MS", pvArrayThrottleMs); System.setProperty("PV_WRITE_SUPPORT", pvWriteSupport); + System.setProperty("PV_READ_TIMEOUT", pvReadTimeout); // Configure JCA/CAJ to use environment vars, not java properties or preferences System.setProperty("jca.use_env", "true"); diff --git a/src/main/java/org/phoebus/pvws/controllers/PvwsRestController.java b/src/main/java/org/phoebus/pvws/controllers/PvwsRestController.java index b000db6..e00db6f 100644 --- a/src/main/java/org/phoebus/pvws/controllers/PvwsRestController.java +++ b/src/main/java/org/phoebus/pvws/controllers/PvwsRestController.java @@ -22,19 +22,25 @@ import org.epics.util.array.ListInteger; import org.epics.vtype.Array; import org.epics.vtype.VType; +import org.phoebus.core.vtypes.VTypeHelper; import org.phoebus.pv.PV; import org.phoebus.pv.PVPool; import org.phoebus.pv.RefCountMap; import org.phoebus.pvws.model.*; +import org.phoebus.pvws.ws.Vtype2Json; import org.phoebus.pvws.ws.WebSocket; import org.phoebus.pvws.ws.WebSocketPV; import org.phoebus.util.time.TimestampFormats; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -146,4 +152,30 @@ public InfoData info(@RequestParam(name = "env", defaultValue = "false") boolean } return infoData; } + + @GetMapping(value= "/pvget", produces = MediaType.APPLICATION_JSON_VALUE) + public String pvget(@RequestParam String name) { + CountDownLatch countDownLatch = new CountDownLatch(1); + AtomicReference value = new AtomicReference<>(null); + try { + int pvReadTimeout = Integer.parseInt(System.getProperty("PV_READ_TIMEOUT")); + PV pv = PVPool.getPV(name); + pv.onValueEvent().subscribe(vtype -> { + if (!VTypeHelper.isDisconnected(vtype)) { + value.set(pv.read()); + } + countDownLatch.countDown(); + }); + countDownLatch.await(pvReadTimeout, TimeUnit.MILLISECONDS); + PVPool.releasePV(pv); + if(value.get() == null){ + logger.info("PV " + name + " never connected."); + return null; + } + return Vtype2Json.toJson(name, value.get(), null, true, true); + } catch (Exception e) { + logger.warning("Exception when reading PV " + name + " Exception: " + e); + return null; + } + } } diff --git a/src/main/java/org/phoebus/pvws/ws/WebSocketPV.java b/src/main/java/org/phoebus/pvws/ws/WebSocketPV.java index d968e7d..0f48bdd 100644 --- a/src/main/java/org/phoebus/pvws/ws/WebSocketPV.java +++ b/src/main/java/org/phoebus/pvws/ws/WebSocketPV.java @@ -11,8 +11,6 @@ import org.epics.vtype.VType; import org.phoebus.pv.PV; import org.phoebus.pv.PVPool; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7848f60..50c7c67 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -14,3 +14,5 @@ EPICS_PVA_ADDR_LIST= EPICS_PVA_AUTO_ADDR_LIST=YES EPICS_PVA_BROADCAST_PORT=5076 EPICS_PVA_NAME_SERVERS= + +PV_READ_TIMEOUT=5000