[Major] New GetAddressState PLC telegram

This commit is contained in:
Robert von Burg 2022-10-07 15:39:10 +02:00
parent 20bd342651
commit 15c9eb731d
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
11 changed files with 350 additions and 173 deletions

View File

@ -2,6 +2,7 @@ package li.strolch.plc.gw.client;
import static java.net.NetworkInterface.getByInetAddress;
import static li.strolch.model.Tags.Json.*;
import static li.strolch.plc.model.ModelHelper.valueToJson;
import static li.strolch.plc.model.PlcConstants.*;
import static li.strolch.runtime.StrolchConstants.DEFAULT_REALM;
import static li.strolch.utils.helper.ExceptionHelper.*;
@ -17,12 +18,14 @@ import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import li.strolch.agent.api.*;
import li.strolch.model.Locator;
import li.strolch.model.Resource;
@ -34,6 +37,7 @@ import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.model.*;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.utils.CheckedRunnable;
import li.strolch.utils.helper.NetworkHelper;
import org.glassfish.tyrus.client.ClientManager;
@ -64,7 +68,7 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
private ScheduledFuture<?> serverConnectFuture;
private Map<PlcAddress, Object> notConnectedQueue;
private LinkedBlockingDeque<Callable<?>> messageQueue;
private LinkedBlockingDeque<CheckedRunnable> messageQueue;
private int maxMessageQueue;
private boolean run;
private Future<?> messageSenderTask;
@ -176,6 +180,10 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
logger.error(
"Connection refused to connect to server. Will try to connect again in " + RETRY_DELAY + "s: "
+ getExceptionMessageWithCauses(e));
} else if (rootCause.getMessage() != null && rootCause.getMessage()
.contains("Response code was not 101: 404.")) {
logger.error("Connection failed with 404 error code. Is URL " + this.gwServerUrl + " correct?");
logger.error("Server not yet ready with 404 error. Will try again in " + RETRY_DELAY + "s");
} else {
logger.error("Failed to connect to server! Will try to connect again in " + RETRY_DELAY + "s", e);
}
@ -300,15 +308,15 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
}
}
private void addMsg(Callable<?> callable) {
private void async(CheckedRunnable runnable) {
if (this.messageQueue.size() > this.maxMessageQueue)
this.messageQueue.removeFirst();
this.messageQueue.addLast(callable);
this.messageQueue.addLast(runnable);
}
@Override
public void sendMsg(LogMessage message) {
addMsg(() -> {
async(() -> {
JsonObject messageJ = new JsonObject();
messageJ.addProperty(PARAM_PLC_ID, this.plcId);
@ -318,14 +326,12 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
sendDataToClient(messageJ);
if (this.verbose)
logger.info("Sent msg " + message.getLocator() + " to server");
return null;
});
}
@Override
public void disableMsg(Locator locator) {
addMsg(() -> {
async(() -> {
JsonObject messageJ = new JsonObject();
messageJ.addProperty(PARAM_PLC_ID, this.plcId);
@ -336,8 +342,6 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
sendDataToClient(messageJ);
if (this.verbose)
logger.info("Sent msg " + locator + " to server");
return null;
});
}
@ -345,29 +349,19 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
if (!plcAddress.remote)
return;
addMsg(() -> {
async(() -> {
JsonObject notificationJ = new JsonObject();
notificationJ.addProperty(PARAM_PLC_ID, this.plcId);
notificationJ.addProperty(PARAM_MESSAGE_TYPE, MSG_TYPE_PLC_NOTIFICATION);
notificationJ.addProperty(PARAM_RESOURCE, plcAddress.resource);
notificationJ.addProperty(PARAM_ACTION, plcAddress.action);
if (value instanceof Boolean)
notificationJ.add(PARAM_VALUE, new JsonPrimitive((Boolean) value));
else if (value instanceof Number)
notificationJ.add(PARAM_VALUE, new JsonPrimitive((Number) value));
else if (value instanceof String)
notificationJ.add(PARAM_VALUE, new JsonPrimitive((String) value));
else
notificationJ.add(PARAM_VALUE, new JsonPrimitive(value.toString()));
notificationJ.add(PARAM_VALUE, valueToJson(value));
sendDataToClient(notificationJ);
if (this.verbose)
logger.info("Sent notification for " + plcAddress.toKey() + " to server");
return null;
});
}
@ -382,11 +376,12 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
try {
runAsAgent(ctx -> {
if (MSG_TYPE_AUTHENTICATION.equals(messageType)) {
handleAuthResponse(ctx, jsonObject);
} else if (MSG_TYPE_PLC_TELEGRAM.equals(messageType)) {
handleTelegram(jsonObject);
async(() -> handleTelegram(jsonObject));
} else if (MSG_TYPE_PLC_GET_ADDRESS_STATE.equals(messageType)) {
async(() -> handleGetAddressState(ctx, jsonObject));
} else {
logger.error("Unhandled message type " + messageType);
}
@ -396,54 +391,55 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
}
}
private void handleTelegram(JsonObject telegramJ) {
private void handleGetAddressState(PrivilegeContext ctx, JsonObject telegramJ) throws Exception {
PlcAddress plcAddress = null;
try (StrolchTransaction tx = openTx(ctx.getCertificate(), true)) {
plcAddress = parsePlcAddress(telegramJ);
String plcAddressId = this.plcHandler.getPlcAddressId(plcAddress.resource, plcAddress.action);
Resource address = tx.getResourceBy(TYPE_PLC_ADDRESS, plcAddressId, true);
Object value = address.getParameter(PARAM_VALUE, true).getValue();
telegramJ.add(PARAM_VALUE, valueToJson(value));
telegramJ.addProperty(PARAM_STATE, PlcResponseState.Done.name());
telegramJ.addProperty(PARAM_STATE_MSG, "");
if (this.verbose)
logger.info("Sent address state for " + plcAddress.toKey() + " = " + value + " to server");
} catch (Exception e) {
handleFailedTelegram(telegramJ, plcAddress, e);
}
sendDataToClient(telegramJ);
}
private void handleTelegram(JsonObject telegramJ) throws Exception {
PlcAddress plcAddress = null;
try {
if (!telegramJ.has(PARAM_RESOURCE) || !telegramJ.has(PARAM_ACTION))
throw new IllegalArgumentException("Both " + PARAM_RESOURCE + " and " + PARAM_ACTION + " is required!");
String resource = telegramJ.get(PARAM_RESOURCE).getAsString();
String action = telegramJ.get(PARAM_ACTION).getAsString();
plcAddress = this.plcHandler.getPlcAddress(resource, action);
plcAddress = parsePlcAddress(telegramJ);
if (telegramJ.has(PARAM_VALUE)) {
String valueS = telegramJ.get(PARAM_VALUE).getAsString();
Object value = plcAddress.valueType.parseValue(valueS);
this.plcHandler.send(resource, action, value, false, false);
this.plcHandler.send(plcAddress.resource, plcAddress.action, value, false, false);
} else {
this.plcHandler.send(resource, action, false, false);
this.plcHandler.send(plcAddress.resource, plcAddress.action, false, false);
}
telegramJ.addProperty(PARAM_STATE, PlcResponseState.Done.name());
telegramJ.addProperty(PARAM_STATE_MSG, "");
} catch (Exception e) {
if (plcAddress == null) {
logger.error("Failed to handle telegram: " + telegramJ, e);
telegramJ.addProperty(PARAM_STATE, PlcResponseState.Failed.name());
telegramJ.addProperty(PARAM_STATE_MSG,
"Could not evaluate PlcAddress: " + getExceptionMessage(getRootCause(e), false));
} else {
logger.error("Failed to execute telegram: " + plcAddress.toKeyAddress(), e);
telegramJ.addProperty(PARAM_STATE, PlcResponseState.Failed.name());
telegramJ.addProperty(PARAM_STATE_MSG,
"Failed to perform " + plcAddress.toKey() + ": " + getExceptionMessage(getRootCause(e), false));
}
handleFailedTelegram(telegramJ, plcAddress, e);
}
// async sending of response
PlcAddress address = plcAddress;
addMsg(() -> {
sendDataToClient(telegramJ);
if (this.verbose)
logger.info(
"Sent Telegram response for " + (address == null ? "unknown" : address.toKey()) + " to server");
return null;
});
sendDataToClient(telegramJ);
if (this.verbose)
logger.info("Sent Telegram response for " + (plcAddress == null ? "unknown" : plcAddress.toKey())
+ " to server");
}
private void handleAuthResponse(PrivilegeContext ctx, JsonObject response) {
@ -542,15 +538,15 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
continue;
}
Callable<?> callable = null;
CheckedRunnable runnable = null;
try {
callable = this.messageQueue.takeFirst();
callable.call();
runnable = this.messageQueue.takeFirst();
runnable.run();
} catch (Exception e) {
closeBrokenGwSessionUpdateState("Failed to send message",
"Failed to send message, reconnecting in " + RETRY_DELAY + "s.");
if (callable != null) {
this.messageQueue.addFirst(callable);
if (runnable != null) {
this.messageQueue.addFirst(runnable);
logger.error(
"Failed to send message, reconnecting in " + RETRY_DELAY + "s. And then retrying message.",
e);
@ -584,9 +580,10 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
VersionQueryResult versionQueryResult = getContainer().getAgent().getVersion();
this.versions.add(AGENT_VERSION, versionQueryResult.getAgentVersion().toJson());
this.versions.add(APP_VERSION, versionQueryResult.getAppVersion().toJson());
this.versions.add(COMPONENT_VERSIONS,
versionQueryResult.getComponentVersions().stream().map(ComponentVersion::toJson)
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll));
this.versions.add(COMPONENT_VERSIONS, versionQueryResult.getComponentVersions()
.stream()
.map(ComponentVersion::toJson)
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll));
}
return this.versions;
@ -635,6 +632,30 @@ public class PlcGwClientHandler extends StrolchComponent implements GlobalPlcLis
notifyServer(address, value);
}
private PlcAddress parsePlcAddress(JsonObject telegramJ) {
if (!telegramJ.has(PARAM_RESOURCE) || !telegramJ.has(PARAM_ACTION))
throw new IllegalArgumentException("Both " + PARAM_RESOURCE + " and " + PARAM_ACTION + " is required!");
String resource = telegramJ.get(PARAM_RESOURCE).getAsString();
String action = telegramJ.get(PARAM_ACTION).getAsString();
return this.plcHandler.getPlcAddress(resource, action);
}
private static void handleFailedTelegram(JsonObject telegramJ, PlcAddress plcAddress, Exception e) {
if (plcAddress == null) {
logger.error("Failed to handle telegram: " + telegramJ, e);
telegramJ.addProperty(PARAM_STATE, PlcResponseState.Failed.name());
telegramJ.addProperty(PARAM_STATE_MSG,
"Could not evaluate PlcAddress: " + getExceptionMessage(getRootCause(e), false));
} else {
logger.error("Failed to execute telegram: " + plcAddress.toKeyAddress(), e);
telegramJ.addProperty(PARAM_STATE, PlcResponseState.Failed.name());
telegramJ.addProperty(PARAM_STATE_MSG,
"Failed to perform " + plcAddress.toKey() + ": " + getExceptionMessage(getRootCause(e), false));
}
}
@ClientEndpoint
public static class PlcGwClientEndpoint {

View File

@ -0,0 +1,8 @@
package li.strolch.plc.gw.server;
import li.strolch.plc.model.PlcAddressValueResponse;
public interface PlcAddressResponseValueListener {
void handleResponse(PlcAddressValueResponse response) throws Exception;
}

View File

@ -1,6 +1,8 @@
package li.strolch.plc.gw.server;
import static java.util.stream.Collectors.toSet;
import static li.strolch.plc.model.ModelHelper.jsonToValue;
import static li.strolch.plc.model.ModelHelper.valueToJson;
import static li.strolch.plc.model.PlcConstants.*;
import static li.strolch.utils.collections.SynchronizedCollections.synchronizedMapOfLists;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
@ -8,7 +10,6 @@ import static li.strolch.websocket.WebSocketRemoteIp.get;
import javax.websocket.CloseReason;
import javax.websocket.PongMessage;
import javax.websocket.RemoteEndpoint.Basic;
import javax.websocket.Session;
import java.io.IOException;
import java.util.*;
@ -120,6 +121,10 @@ public class PlcGwServerHandler extends StrolchComponent {
this.plcConnectionStateListeners.addElement(plcId, listener);
}
public void unregister(String plcId, PlcConnectionStateListener listener) {
this.plcConnectionStateListeners.removeElement(plcId, listener);
}
public void register(String plcId, PlcAddressKey addressKey, PlcNotificationListener listener) {
DBC.PRE.assertNotNull("addressKey must not be null", addressKey);
DBC.PRE.assertNotEmpty("plcId must not be empty", plcId);
@ -160,52 +165,20 @@ public class PlcGwServerHandler extends StrolchComponent {
return super.runAsWithResult(this.runAsUser, runnable);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, boolean value,
PlcAddressResponseListener listener) {
sendMessage(addressKey, plcId, new JsonPrimitive(value), listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, int value, PlcAddressResponseListener listener) {
sendMessage(addressKey, plcId, new JsonPrimitive(value), listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, double value, PlcAddressResponseListener listener) {
sendMessage(addressKey, plcId, new JsonPrimitive(value), listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, String value, PlcAddressResponseListener listener) {
sendMessage(addressKey, plcId, new JsonPrimitive(value), listener);
public void sendMessage(PlcAddressKey addressKey, String plcId, Object value, PlcAddressResponseListener listener) {
sendMessage(addressKey, plcId, value == null ? null : valueToJson(value), listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, PlcAddressResponseListener listener) {
sendMessage(addressKey, plcId, (JsonPrimitive) null, listener);
}
public PlcAddressResponse sendMessageSync(PlcAddressKey addressKey, String plcId, boolean value) {
return sendMessageSync(addressKey, plcId, new JsonPrimitive(value));
}
public PlcAddressResponse sendMessage(PlcAddressKey addressKey, String plcId, int value) {
return sendMessageSync(addressKey, plcId, new JsonPrimitive(value));
}
public PlcAddressResponse sendMessage(PlcAddressKey addressKey, String plcId, double value) {
return sendMessageSync(addressKey, plcId, new JsonPrimitive(value));
}
public PlcAddressResponse sendMessage(PlcAddressKey addressKey, String plcId, String value) {
return sendMessageSync(addressKey, plcId, new JsonPrimitive(value));
}
public PlcAddressResponse sendMessage(PlcAddressKey addressKey, String plcId) {
return sendMessageSync(addressKey, plcId, (JsonPrimitive) null);
sendMessage(addressKey, plcId, null, listener);
}
public PlcAddressResponse sendMessageSync(PlcAddressKey addressKey, String plcId) {
return sendMessageSync(addressKey, plcId, null);
}
public PlcAddressResponse sendMessageSync(PlcAddressKey addressKey, String plcId, JsonPrimitive valueJ) {
public PlcAddressResponse sendMessageSync(PlcAddressKey addressKey, String plcId, Object value) {
JsonPrimitive valueJ = value == null ? null : valueToJson(value);
PlcAddressResponse[] response = new PlcAddressResponse[1];
@ -228,44 +201,30 @@ public class PlcGwServerHandler extends StrolchComponent {
private void sendMessage(PlcAddressKey addressKey, String plcId, JsonPrimitive valueJ,
PlcAddressResponseListener listener) {
PlcSession plcSession = this.plcSessionsByPlcId.get(plcId);
if (plcSession == null)
throw new IllegalStateException("PLC " + plcId + " is not connected!");
assertPlcAuthed(plcId, plcSession.session.getId());
PlcSession plcSession = getPlcSession(plcId);
getExecutorService(THREAD_POOL).submit(() -> send(plcSession, addressKey, valueJ, listener));
}
public void asyncGetAddressState(PlcAddressKey addressKey, String plcId, PlcAddressResponseValueListener listener) {
PlcSession plcSession = getPlcSession(plcId);
getExecutorService(THREAD_POOL).submit(() -> asyncGetAddressState(plcSession, addressKey, listener));
}
private void send(PlcSession plcSession, PlcAddressKey plcAddressKey, JsonPrimitive valueJ,
PlcAddressResponseListener listener) {
if (valueJ == null)
logger.info("Sending " + plcAddressKey + " to " + plcSession.plcId + "...");
else
logger.info("Sending " + plcAddressKey + " with value " + valueJ + " to " + plcSession.plcId + "...");
logger.info("Sending " + plcAddressKey + " = " + valueJ + " to " + plcSession.plcId + "...");
PlcAddressResponse plcResponse = new PlcAddressResponse(plcSession.plcId, plcAddressKey);
plcResponse.setListener(() -> handleResponse(listener, plcResponse));
plcResponse.setListener(() -> listener.handleResponse(plcResponse));
try {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(PARAM_SEQUENCE_ID, plcResponse.getSequenceId());
jsonObject.addProperty(PARAM_MESSAGE_TYPE, MSG_TYPE_PLC_TELEGRAM);
jsonObject.addProperty(PARAM_PLC_ID, plcSession.plcId);
jsonObject.addProperty(PARAM_RESOURCE, plcAddressKey.resource);
jsonObject.addProperty(PARAM_ACTION, plcAddressKey.action);
if (valueJ != null)
jsonObject.add(PARAM_VALUE, valueJ);
String data = jsonObject.toString();
String data = buildJsonTelegram(plcSession.plcId, plcAddressKey, valueJ, plcResponse).toString();
this.plcResponses.put(plcResponse.getSequenceId(), plcResponse);
synchronized (plcSession.session) {
sendDataToClient(data, plcSession.session.getBasicRemote());
}
sendDataToClient(data, plcSession.session);
} catch (Exception e) {
logger.error("Failed to send " + plcAddressKey + " to PLC " + plcSession.plcId, e);
@ -281,14 +240,57 @@ public class PlcGwServerHandler extends StrolchComponent {
}
}
private void handleResponse(PlcAddressResponseListener listener, PlcAddressResponse response) {
private void asyncGetAddressState(PlcSession plcSession, PlcAddressKey plcAddressKey,
PlcAddressResponseValueListener listener) {
logger.info("Requesting value for address " + plcAddressKey + " from PLC " + plcSession.plcId + "...");
PlcAddressValueResponse plcResponse = new PlcAddressValueResponse(plcSession.plcId, plcAddressKey);
plcResponse.setListener(() -> listener.handleResponse(plcResponse));
try {
listener.handleResponse(response);
String data = buildJsonGetAddressStateTelegram(plcSession.plcId, plcAddressKey, plcResponse).toString();
this.plcResponses.put(plcResponse.getSequenceId(), plcResponse);
sendDataToClient(data, plcSession.session);
} catch (Exception e) {
logger.error("Failed to notify listener " + listener + " for response of " + response, e);
logger.error("Failed to get address state for " + plcAddressKey + " from PLC " + plcSession.plcId, e);
plcResponse.setState(PlcResponseState.Failed);
plcResponse.setStateMsg(
"Failed to get address state for " + plcAddressKey + " from PLC " + plcSession.plcId + ": "
+ getExceptionMessageWithCauses(e));
try {
listener.handleResponse(plcResponse);
} catch (Exception ex) {
logger.error("Failed to notify listener " + listener, ex);
}
}
}
private static JsonObject buildJsonTelegram(String plcId, PlcAddressKey plcAddressKey, JsonPrimitive valueJ,
PlcAddressResponse plcResponse) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(PARAM_SEQUENCE_ID, plcResponse.getSequenceId());
jsonObject.addProperty(PARAM_MESSAGE_TYPE, MSG_TYPE_PLC_TELEGRAM);
jsonObject.addProperty(PARAM_PLC_ID, plcId);
jsonObject.addProperty(PARAM_RESOURCE, plcAddressKey.resource);
jsonObject.addProperty(PARAM_ACTION, plcAddressKey.action);
if (valueJ != null)
jsonObject.add(PARAM_VALUE, valueJ);
return jsonObject;
}
private static JsonObject buildJsonGetAddressStateTelegram(String plcId, PlcAddressKey plcAddressKey,
PlcAddressResponse plcResponse) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(PARAM_SEQUENCE_ID, plcResponse.getSequenceId());
jsonObject.addProperty(PARAM_MESSAGE_TYPE, MSG_TYPE_PLC_GET_ADDRESS_STATE);
jsonObject.addProperty(PARAM_PLC_ID, plcId);
jsonObject.addProperty(PARAM_RESOURCE, plcAddressKey.resource);
jsonObject.addProperty(PARAM_ACTION, plcAddressKey.action);
return jsonObject;
}
private PlcSession assertPlcAuthed(String plcId, String sessionId) throws NotAuthenticatedException {
PlcSession plcSession = this.plcSessionsBySessionId.get(sessionId);
if (plcSession.certificate == null)
@ -309,13 +311,16 @@ public class PlcGwServerHandler extends StrolchComponent {
return plcSession;
}
private void sendDataToClient(String data, Basic basic) throws IOException {
int pos = 0;
while (pos + 8192 < data.length()) {
basic.sendText(data.substring(pos, pos + 8192), false);
pos += 8192;
private void sendDataToClient(String data, Session session) throws IOException {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (session) {
int pos = 0;
while (pos + 8192 < data.length()) {
session.getBasicRemote().sendText(data.substring(pos, pos + 8192), false);
pos += 8192;
}
session.getBasicRemote().sendText(data.substring(pos), true);
}
basic.sendText(data.substring(pos), true);
}
public void onWsMessage(String message, Session session) throws IOException {
@ -333,6 +338,8 @@ public class PlcGwServerHandler extends StrolchComponent {
case MSG_TYPE_AUTHENTICATION -> handleAuth(session, jsonObject);
case MSG_TYPE_PLC_NOTIFICATION -> handleNotification(assertPlcAuthed(plcId, session.getId()), jsonObject);
case MSG_TYPE_PLC_TELEGRAM -> handleTelegramResponse(assertPlcAuthed(plcId, session.getId()), jsonObject);
case MSG_TYPE_PLC_GET_ADDRESS_STATE ->
handleGetAddressStateResponse(assertPlcAuthed(plcId, session.getId()), jsonObject);
case MSG_TYPE_STATE_NOTIFICATION -> handleStateMsg(assertPlcAuthed(plcId, session.getId()), jsonObject);
case MSG_TYPE_MESSAGE -> {
assertPlcAuthed(plcId, session.getId());
@ -393,7 +400,6 @@ public class PlcGwServerHandler extends StrolchComponent {
}
private void handleTelegramResponse(PlcSession plcSession, JsonObject responseJ) {
long sequenceId = responseJ.get(PARAM_SEQUENCE_ID).getAsLong();
PlcResponse plcResponse = this.plcResponses.remove(sequenceId);
if (plcResponse == null) {
@ -406,7 +412,38 @@ public class PlcGwServerHandler extends StrolchComponent {
plcResponse.setState(PlcResponseState.valueOf(state));
plcResponse.setStateMsg(stateMsg);
plcResponse.getListener().run();
try {
plcResponse.getListener().run();
} catch (Exception e) {
logger.error("Failed to notify listener " + plcResponse.getListener() + " for response of " + plcResponse,
e);
}
}
private void handleGetAddressStateResponse(PlcSession plcSession, JsonObject responseJ) {
long sequenceId = responseJ.get(PARAM_SEQUENCE_ID).getAsLong();
PlcResponse response = this.plcResponses.remove(sequenceId);
if (response == null) {
logger.error(plcSession.plcId + ": PlcResponse does not exist for sequenceId " + sequenceId);
return;
}
if (!(response instanceof PlcAddressValueResponse plcResponse))
throw new IllegalStateException(
"Performing a GetAddressState response handling, but listener is wrong: " + response);
String state = responseJ.get(PARAM_STATE).getAsString();
String stateMsg = responseJ.get(PARAM_STATE_MSG).getAsString();
plcResponse.setState(PlcResponseState.valueOf(state));
plcResponse.setStateMsg(stateMsg);
plcResponse.setValue(jsonToValue(responseJ.getAsJsonPrimitive(PARAM_VALUE)));
try {
plcResponse.getListener().run();
} catch (Exception e) {
logger.error("Failed to notify listener " + plcResponse.getListener() + " for response of " + plcResponse,
e);
}
}
private void handleMessage(JsonObject jsonObject) {
@ -481,10 +518,7 @@ public class PlcGwServerHandler extends StrolchComponent {
private void sendAuthResponse(PlcSession plcSession, JsonObject jsonObject) {
try {
String data = jsonObject.toString();
synchronized (plcSession.session) {
sendDataToClient(data, plcSession.session.getBasicRemote());
}
sendDataToClient(jsonObject.toString(), plcSession.session);
logger.info(plcSession.plcId + ": Sent " + MSG_TYPE_AUTHENTICATION + " response on Session "
+ plcSession.session.getId());
} catch (Exception e) {
@ -706,6 +740,15 @@ public class PlcGwServerHandler extends StrolchComponent {
logger.error(session.getId() + ": Error: " + throwable.getMessage(), throwable);
}
private PlcSession getPlcSession(String plcId) {
PlcSession plcSession = this.plcSessionsByPlcId.get(plcId);
if (plcSession == null)
throw new IllegalStateException("PLC " + plcId + " is not connected!");
assertPlcAuthed(plcId, plcSession.session.getId());
return plcSession;
}
public static class PlcSession {
public final String plcId;
public final Session session;

View File

@ -1,5 +1,6 @@
package li.strolch.plc.gw.server;
import static li.strolch.plc.model.ModelHelper.valueToJson;
import static li.strolch.plc.model.PlcConstants.TYPE_PLC;
import static li.strolch.runtime.StrolchConstants.SYSTEM_USER_AGENT;
import static li.strolch.utils.helper.ExceptionHelper.getCallerMethod;
@ -68,10 +69,17 @@ public abstract class PlcGwService implements PlcNotificationListener, PlcAddres
this.state = PlcServiceState.Unregistered;
}
protected PlcAddressKey register(String resource, String action) {
protected void register(String resource, String action) {
PlcAddressKey addressKey = keyFor(resource, action);
this.plcHandler.register(this.plcId, addressKey, this);
return addressKey;
}
protected void register(PlcConnectionStateListener listener) {
this.plcHandler.register(this.plcId, listener);
}
protected void unregister(PlcConnectionStateListener listener) {
this.plcHandler.unregister(this.plcId, listener);
}
protected void register(PlcAddressKey key) {
@ -90,25 +98,20 @@ public abstract class PlcGwService implements PlcNotificationListener, PlcAddres
return PlcAddressKey.keyFor(resource, action);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, boolean value,
PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(addressKey, plcId, value, listener);
public void sendMessage(String resource, String action, Object value, PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(keyFor(resource, action), this.plcId, valueToJson(value), listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, int value, PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(addressKey, plcId, value, listener);
public void sendMessage(PlcAddressKey addressKey, Object value, PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(addressKey, this.plcId, value, listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, double value, PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(addressKey, plcId, value, listener);
public void readState(String resource, String action, PlcAddressResponseValueListener listener) {
this.plcHandler.asyncGetAddressState(keyFor(resource, action), this.plcId, listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, String value, PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(addressKey, plcId, value, listener);
}
public void sendMessage(PlcAddressKey addressKey, String plcId, PlcAddressResponseListener listener) {
this.plcHandler.sendMessage(addressKey, plcId, listener);
public void readState(PlcAddressKey addressKey, PlcAddressResponseValueListener listener) {
this.plcHandler.asyncGetAddressState(addressKey, this.plcId, listener);
}
@Override

View File

@ -0,0 +1,26 @@
package li.strolch.plc.gw.server;
import li.strolch.plc.model.PlcAddressKey;
import li.strolch.plc.model.PlcAddressValueResponse;
public abstract class ReadStatePlcGwService extends PlcGwService implements PlcConnectionStateListener {
public ReadStatePlcGwService(String plcId, PlcGwServerHandler plcHandler) {
super(plcId, plcHandler);
}
protected void handleGetState(PlcAddressValueResponse response) {
PlcAddressKey addressKey = response.getPlcAddressKey();
if (response.isFailed()) {
logger.error("Failed to read value for address " + addressKey + ": " + response.getStateMsg());
} else {
storeAddressState(addressKey, response.getValue());
}
}
protected void readState(String resource, String action) {
this.plcHandler.asyncGetAddressState(keyFor(resource, action), this.plcId, this::handleGetState);
}
protected abstract void storeAddressState(PlcAddressKey addressKey, Object value);
}

View File

@ -2,7 +2,6 @@ package li.strolch.plc.gw.server.service;
import static li.strolch.plc.model.PlcConstants.*;
import com.google.gson.JsonPrimitive;
import li.strolch.model.StrolchValueType;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.gw.server.PlcGwServerHandler;
@ -77,17 +76,6 @@ public class SendPlcTelegramCommand extends Command {
// sending with a value
StrolchValueType valueType = StrolchValueType.parse(this.valueTypeS);
Object value = valueType.parseValue(valueS);
JsonPrimitive valueJ;
if (value instanceof String)
valueJ = new JsonPrimitive((String) value);
else if (value instanceof Number)
valueJ = new JsonPrimitive((Number) value);
else if (value instanceof Boolean)
valueJ = new JsonPrimitive((Boolean) value);
else
throw new IllegalArgumentException("Unhandled value type " + valueType);
this.response = plcHandler.sendMessageSync(addressKey, this.plcId, valueJ);
this.response = plcHandler.sendMessageSync(addressKey, this.plcId, valueType.parseValue(valueS));
}
}

View File

@ -4,4 +4,16 @@ public enum ConnectionState {
Connected,
Disconnected,
Failed;
public boolean isConnected() {
return this == Connected;
}
public boolean isDisconnected() {
return this == Disconnected;
}
public boolean isFailed() {
return this == Failed;
}
}

View File

@ -0,0 +1,27 @@
package li.strolch.plc.model;
import com.google.gson.JsonPrimitive;
public class ModelHelper {
public static JsonPrimitive valueToJson(Object value) {
if (value instanceof Boolean)
return new JsonPrimitive((Boolean) value);
else if (value instanceof Number)
return new JsonPrimitive((Number) value);
else if (value instanceof String)
return new JsonPrimitive((String) value);
throw new IllegalArgumentException(
"Unhandled value type " + (value == null ? "(null)" : value.getClass().getName()));
}
public static Object jsonToValue(JsonPrimitive valueJ) {
if (valueJ.isBoolean())
return valueJ.getAsBoolean();
else if (valueJ.isNumber())
return valueJ.getAsNumber();
else if (valueJ.isString())
return valueJ.getAsString();
throw new IllegalArgumentException("Unhandled value type " + valueJ);
}
}

View File

@ -0,0 +1,46 @@
package li.strolch.plc.model;
public class PlcAddressValueResponse extends PlcAddressResponse {
private Object value;
public PlcAddressValueResponse(String plcId, PlcAddressKey plcAddressKey) {
super(plcId, plcAddressKey);
}
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return this.value;
}
public boolean getValueAsBoolean() {
return (Boolean) this.value;
}
public int getValueAsInt() {
return ((Number) this.value).intValue();
}
public double getValueAsDouble() {
return ((Number) this.value).doubleValue();
}
public String getValueAsString() {
return ((String) this.value);
}
@Override
public PlcAddressValueResponse state(PlcResponseState state, String stateMsg) {
super.state(state, stateMsg);
return this;
}
@Override
public String toString() {
return "PlcAddressValueResponse{" + "plcId='" + plcId + '\'' + ", sequenceId=" + sequenceId + ", state=" + state
+ ", stateMsg='" + stateMsg + '\'' + ", value=" + value + '}';
}
}

View File

@ -60,6 +60,7 @@ public class PlcConstants {
public static final String MSG_TYPE_AUTHENTICATION = "Authentication";
public static final String MSG_TYPE_PLC_NOTIFICATION = "PlcNotification";
public static final String MSG_TYPE_PLC_TELEGRAM = "PlcTelegram";
public static final String MSG_TYPE_PLC_GET_ADDRESS_STATE = "PlcGetAddressState";
public static final String MSG_TYPE_MESSAGE = "Message";
public static final String MSG_TYPE_DISABLE_MESSAGE = "DisableMessage";
public static final String MSG_TYPE_STATE_NOTIFICATION = "StateNotification";

View File

@ -1,5 +1,7 @@
package li.strolch.plc.model;
import li.strolch.utils.CheckedRunnable;
public class PlcResponse {
private static long lastSequenceId = System.currentTimeMillis();
@ -9,7 +11,7 @@ public class PlcResponse {
protected PlcResponseState state;
protected String stateMsg;
private Runnable listener;
private CheckedRunnable listener;
public PlcResponse(String plcId) {
this.plcId = plcId;
@ -60,11 +62,11 @@ public class PlcResponse {
this.stateMsg = stateMsg;
}
public Runnable getListener() {
public CheckedRunnable getListener() {
return this.listener;
}
public void setListener(Runnable listener) {
public void setListener(CheckedRunnable listener) {
this.listener = listener;
}
}