mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-08-30 17:19:58 +08:00
fix: address code review — security, thread safety, performance, and null safety
Security (CRITICAL): - KeyUtil: read JWT signing key from DC3_SECURITY_KEY env/property - UserPasswordServiceImpl: read default password from DC3_SECURITY_DEFAULT_PASSWORD env - KeyLoader: read OPC-UA keystore password from OPCUA_KEYSTORE_PASSWORD env - WebFilterConfig: return 401 on malformed X-Auth-User header - AlgorithmConstant: document deprecated hardcoded constants Thread safety (HIGH): - Fix TOCTOU races in 4 driver connectors (computeIfAbsent) - Fix PlcS7 lock leak, OpcUa connect timeout (5s) - CoapClientManager: synchronize setURI to prevent URI race - PointValueJob, MqttScheduleJob: add @DisallowConcurrentExecution - SystemHealthServiceImpl: preserve interrupt flag - WindowSampleBuffer: local AtomicInteger → int Performance (MEDIUM): - EntityStateExpiryScanner: batch alarm saves (saveBatch) - ImportDeviceServiceImpl: batch config saves - ResourceRegistrySyncServiceImpl: batch-load nodes, eliminate N+1 COUNTs - DriverSenderServiceImpl: debug-gate hot-path logging - PointServiceImpl: stream().count() → size() Null safety (LOW): - RegexUtil: null guards on isName/isPhone/isMail/isPassword/isHost - HostUtil: null-check getNetworkInterfaces() return - TimeUtil: log parse failures instead of silent null Infrastructure: - Add spring-boot-starter-cache + @EnableCaching CacheConfig - Replace embedded modbus4j/plc-s7 jars with external Maven dependencies - Add dc3-driver-modbus-rtu module
This commit is contained in:
@@ -29,6 +29,7 @@ updates:
|
||||
- /dc3-center/dc3-center-single
|
||||
- /dc3-driver/dc3-driver-listening-virtual
|
||||
- /dc3-driver/dc3-driver-modbus-tcp
|
||||
- /dc3-driver/dc3-driver-modbus-rtu
|
||||
- /dc3-driver/dc3-driver-mqtt
|
||||
- /dc3-driver/dc3-driver-opc-da
|
||||
- /dc3-driver/dc3-driver-opc-ua
|
||||
|
||||
@@ -79,6 +79,7 @@ jobs:
|
||||
dc3-center-single
|
||||
dc3-driver-listening-virtual
|
||||
dc3-driver-modbus-tcp
|
||||
dc3-driver-modbus-rtu
|
||||
dc3-driver-mqtt
|
||||
dc3-driver-opc-da
|
||||
dc3-driver-opc-ua
|
||||
|
||||
+14
@@ -233,6 +233,20 @@ ENTRYPOINT ["./entrypoint.sh"]
|
||||
CMD ["dc3-driver-modbus-tcp.jar"]
|
||||
|
||||
|
||||
# ---------- dc3-driver-modbus-rtu ----------
|
||||
FROM runtime-base AS dc3-driver-modbus-rtu
|
||||
ENV SERVER_NAME=dc3-driver-modbus-rtu
|
||||
ENV JAVA_HEAP_DUMP_PATH=dc3/logs/driver/modbus-rtu/gc/dump.hprof
|
||||
ENV JAVA_GC_LOG_PATH=dc3/logs/driver/modbus-rtu/gc/gc-%t.log
|
||||
WORKDIR /dc3-driver/dc3-driver-modbus-rtu
|
||||
RUN mkdir -p /dc3-driver/dc3-driver-modbus-rtu/dc3/logs/driver/modbus-rtu/gc
|
||||
COPY --from=builder /build/dc3-driver/dc3-driver-modbus-rtu/target/dc3-driver-modbus-rtu.jar ./
|
||||
RUN cp /usr/share/dc3/entrypoint.sh ./entrypoint.sh
|
||||
VOLUME /dc3-driver/dc3-driver-modbus-rtu/dc3/logs
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
CMD ["dc3-driver-modbus-rtu.jar"]
|
||||
|
||||
|
||||
# ---------- dc3-driver-mqtt ----------
|
||||
FROM runtime-base AS dc3-driver-mqtt
|
||||
ENV SERVER_NAME=dc3-driver-mqtt
|
||||
|
||||
+59
-15
@@ -50,6 +50,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.github.pnoker.common.constant.service.AuthConstant.API_GROUP_NODE_CODE_PREFIX;
|
||||
import static io.github.pnoker.common.constant.service.AuthConstant.API_RESOURCE_CODE_PREFIX;
|
||||
@@ -424,9 +425,32 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
|
||||
|
||||
private Map<String, Long> ensureGroupNodes(String serviceName, Long serviceNodeId, Set<String> targetGroups) {
|
||||
Map<String, Long> result = new HashMap<>(targetGroups.size());
|
||||
|
||||
// Build code→group lookup for both the batch query and the result
|
||||
Map<String, String> codeToGroup = new HashMap<>(targetGroups.size());
|
||||
List<String> allCodes = new ArrayList<>(targetGroups.size());
|
||||
for (String group : targetGroups) {
|
||||
String code = API_GROUP_NODE_CODE_PREFIX + serviceName + SymbolConstant.COLON + group;
|
||||
ResourceDO existing = findByResourceCode(code);
|
||||
codeToGroup.put(code, group);
|
||||
allCodes.add(code);
|
||||
}
|
||||
|
||||
// Batch-load all existing group nodes
|
||||
Map<String, ResourceDO> existingByCode = new HashMap<>();
|
||||
if (!allCodes.isEmpty()) {
|
||||
List<ResourceDO> existingList = resourceManager.list(Wrappers.<ResourceDO>lambdaQuery()
|
||||
.in(ResourceDO::getResourceCode, allCodes));
|
||||
for (ResourceDO node : existingList) {
|
||||
existingByCode.put(node.getResourceCode(), node);
|
||||
}
|
||||
}
|
||||
|
||||
List<ResourceDO> toInsert = new ArrayList<>();
|
||||
List<ResourceDO> toUpdate = new ArrayList<>();
|
||||
|
||||
for (String group : targetGroups) {
|
||||
String code = API_GROUP_NODE_CODE_PREFIX + serviceName + SymbolConstant.COLON + group;
|
||||
ResourceDO existing = existingByCode.get(code);
|
||||
if (Objects.isNull(existing)) {
|
||||
ResourceDO node = new ResourceDO();
|
||||
node.setParentResourceId(serviceNodeId);
|
||||
@@ -438,17 +462,29 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
|
||||
node.setResourceExt(new JsonExt());
|
||||
node.setEnableFlag(EnableFlagEnum.ENABLE.getIndex());
|
||||
node.setRemark("API grouping node (auto-registered)");
|
||||
resourceManager.save(node);
|
||||
result.put(group, node.getId());
|
||||
toInsert.add(node);
|
||||
} else {
|
||||
String name = group.isEmpty() ? "(ungrouped)" : group;
|
||||
if (needsGroupingNodeUpdate(existing, serviceNodeId, name, code, "API grouping node (auto-registered)")) {
|
||||
applyGroupingNodeUpdates(existing, serviceNodeId, name, code, "API grouping node (auto-registered)");
|
||||
resourceManager.updateById(existing);
|
||||
toUpdate.add(existing);
|
||||
}
|
||||
result.put(group, existing.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!toInsert.isEmpty()) {
|
||||
resourceManager.saveBatch(toInsert);
|
||||
for (ResourceDO node : toInsert) {
|
||||
result.put(codeToGroup.get(node.getResourceCode()), node.getId());
|
||||
}
|
||||
}
|
||||
if (!toUpdate.isEmpty()) {
|
||||
resourceManager.updateBatchById(toUpdate);
|
||||
}
|
||||
for (ResourceDO existing : existingByCode.values()) {
|
||||
result.put(codeToGroup.get(existing.getResourceCode()), existing.getId());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -512,17 +548,25 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
|
||||
List<ResourceDO> groupNodes = resourceManager.list(Wrappers.<ResourceDO>lambdaQuery()
|
||||
.likeRight(ResourceDO::getResourceCode, API_GROUP_NODE_CODE_PREFIX + serviceName + SymbolConstant.COLON)
|
||||
.eq(ResourceDO::getEntityId, 0L));
|
||||
List<Long> idsToDrop = new ArrayList<>();
|
||||
for (ResourceDO node : groupNodes) {
|
||||
long children = resourceManager
|
||||
.count(Wrappers.<ResourceDO>lambdaQuery().eq(ResourceDO::getParentResourceId, node.getId()));
|
||||
if (children == 0) {
|
||||
idsToDrop.add(node.getId());
|
||||
if (!groupNodes.isEmpty()) {
|
||||
// Single query to find which parents have children, instead of per-node COUNT
|
||||
List<Long> parentIds = groupNodes.stream().map(ResourceDO::getId).toList();
|
||||
Set<Long> parentsWithChildren = resourceManager.list(Wrappers.<ResourceDO>lambdaQuery()
|
||||
.select(ResourceDO::getParentResourceId)
|
||||
.in(ResourceDO::getParentResourceId, parentIds))
|
||||
.stream()
|
||||
.map(ResourceDO::getParentResourceId)
|
||||
.collect(Collectors.toSet());
|
||||
List<Long> idsToDrop = new ArrayList<>();
|
||||
for (ResourceDO node : groupNodes) {
|
||||
if (!parentsWithChildren.contains(node.getId())) {
|
||||
idsToDrop.add(node.getId());
|
||||
}
|
||||
}
|
||||
if (!idsToDrop.isEmpty()) {
|
||||
resourceManager.removeByIds(idsToDrop);
|
||||
removed += idsToDrop.size();
|
||||
}
|
||||
}
|
||||
if (!idsToDrop.isEmpty()) {
|
||||
resourceManager.removeByIds(idsToDrop);
|
||||
removed += idsToDrop.size();
|
||||
}
|
||||
ResourceDO serviceNode = findByResourceCode(API_SERVICE_NODE_CODE_PREFIX + serviceName);
|
||||
if (Objects.nonNull(serviceNode)) {
|
||||
|
||||
+6
-1
@@ -116,7 +116,12 @@ public class UserPasswordServiceImpl implements UserPasswordService {
|
||||
public void restPassword(Long id) {
|
||||
UserPasswordBO userPasswordBO = getById(id);
|
||||
if (Objects.nonNull(userPasswordBO)) {
|
||||
userPasswordBO.setLoginPassword(AlgorithmConstant.DEFAULT_PASSWORD);
|
||||
String defaultPassword = System.getenv("DC3_SECURITY_DEFAULT_PASSWORD");
|
||||
if (defaultPassword == null || defaultPassword.isBlank()) {
|
||||
defaultPassword = System.getProperty("dc3.security.default-password",
|
||||
AlgorithmConstant.DEFAULT_PASSWORD);
|
||||
}
|
||||
userPasswordBO.setLoginPassword(defaultPassword);
|
||||
update(userPasswordBO);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -31,12 +31,18 @@ package io.github.pnoker.common.constant.common;
|
||||
public class AlgorithmConstant {
|
||||
|
||||
/**
|
||||
* Default encryption key
|
||||
* Default encryption key (fallback only).
|
||||
* <p>
|
||||
* Production deployments should set the {@code DC3_SECURITY_KEY} environment variable
|
||||
* or the {@code dc3.security.key} property instead.
|
||||
*/
|
||||
public static final String DEFAULT_KEY = "io.github.pnoker.dc3";
|
||||
|
||||
/**
|
||||
* Default password
|
||||
* Default password (fallback only).
|
||||
* <p>
|
||||
* Production deployments should set the {@code DC3_SECURITY_DEFAULT_PASSWORD} environment
|
||||
* variable or the {@code dc3.security.default-password} property instead.
|
||||
*/
|
||||
public static final String DEFAULT_PASSWORD = "dc3dc3dc3";
|
||||
|
||||
|
||||
+3
-4
@@ -32,7 +32,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Per-entity ring of recent samples used for short-window alarm evaluation.
|
||||
@@ -160,9 +159,9 @@ public class WindowSampleBuffer {
|
||||
|
||||
// Trim by count. Concurrent removes here are benign — at most we under-
|
||||
// shoot maxSamples for a moment.
|
||||
AtomicInteger drift = new AtomicInteger(deque.size());
|
||||
while (drift.get() > maxSamples && Objects.nonNull(deque.pollFirst())) {
|
||||
drift.decrementAndGet();
|
||||
int drift = deque.size();
|
||||
while (drift > maxSamples && Objects.nonNull(deque.pollFirst())) {
|
||||
drift--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-4
@@ -44,6 +44,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -131,17 +132,33 @@ public class EntityStateExpiryScanner {
|
||||
return;
|
||||
}
|
||||
|
||||
List<EntityAlarmDO> alarms = new ArrayList<>();
|
||||
List<ExpiredDeviceContext> contexts = new ArrayList<>();
|
||||
|
||||
for (EntityStateDO state : expired) {
|
||||
try {
|
||||
processExpiredDevice(state);
|
||||
EntityAlarmDO alarm = buildOfflineAlarm(state);
|
||||
alarms.add(alarm);
|
||||
contexts.add(new ExpiredDeviceContext(state, alarm));
|
||||
} catch (Exception e) {
|
||||
log.warn("Device expiry processing failed, deviceId={}", state.getEntityId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!alarms.isEmpty()) {
|
||||
entityAlarmManager.saveBatch(alarms);
|
||||
}
|
||||
|
||||
for (ExpiredDeviceContext ctx : contexts) {
|
||||
try {
|
||||
completeExpiredDevice(ctx);
|
||||
} catch (Exception e) {
|
||||
log.warn("Device expiry completion failed, deviceId={}", ctx.state.getEntityId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processExpiredDevice(EntityStateDO scanned) {
|
||||
// Write alarm row
|
||||
private EntityAlarmDO buildOfflineAlarm(EntityStateDO scanned) {
|
||||
EntityStatusEnum prev = EntityStatusEnum.ofIndex(scanned.getLastStateFlag());
|
||||
String prevCode = Objects.nonNull(prev) ? prev.getCode() : "unknown";
|
||||
String message = String.format("Device heartbeat timed out (last=%s); marked OFFLINE", prevCode);
|
||||
@@ -161,7 +178,14 @@ public class EntityStateExpiryScanner {
|
||||
alarm.setExpiredTime(0L);
|
||||
alarm.setConfirmFlag((byte) 0);
|
||||
alarm.setTenantId(scanned.getTenantId());
|
||||
entityAlarmManager.save(alarm);
|
||||
return alarm;
|
||||
}
|
||||
|
||||
private void completeExpiredDevice(ExpiredDeviceContext ctx) {
|
||||
EntityStateDO scanned = ctx.state;
|
||||
EntityAlarmDO alarm = ctx.alarm;
|
||||
EntityStatusEnum prev = EntityStatusEnum.ofIndex(scanned.getLastStateFlag());
|
||||
String prevCode = Objects.nonNull(prev) ? prev.getCode() : "unknown";
|
||||
|
||||
// Update lastAlarmId
|
||||
entityStateManager.lambdaUpdate()
|
||||
@@ -174,6 +198,7 @@ public class EntityStateExpiryScanner {
|
||||
.update();
|
||||
|
||||
// Trigger alarm rule pipeline
|
||||
String message = String.format("Device heartbeat timed out (last=%s); marked OFFLINE", prevCode);
|
||||
DeviceAlarmDTO dto = DeviceAlarmDTO.builder()
|
||||
.driverId(scanned.getParentEntityId())
|
||||
.tenantId(scanned.getTenantId())
|
||||
@@ -188,4 +213,7 @@ public class EntityStateExpiryScanner {
|
||||
log.info("Device scan marked OFFLINE: deviceId={}, tenantId={}, prevStatus={}",
|
||||
scanned.getEntityId(), scanned.getTenantId(), prevCode);
|
||||
}
|
||||
|
||||
private record ExpiredDeviceContext(EntityStateDO state, EntityAlarmDO alarm) {
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -174,6 +174,10 @@ public class SystemHealthServiceImpl implements SystemHealthService {
|
||||
List<FacadeDriverBO> drivers;
|
||||
try {
|
||||
drivers = future.get(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("Driver summary interrupted, tenantId={}", tenantId, e);
|
||||
return summary;
|
||||
} catch (Exception e) {
|
||||
future.cancel(true);
|
||||
log.warn("Driver summary failed, tenantId={}", tenantId, e);
|
||||
@@ -209,6 +213,10 @@ public class SystemHealthServiceImpl implements SystemHealthService {
|
||||
List<FacadeDeviceBO> devices;
|
||||
try {
|
||||
devices = future.get(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("Device summary interrupted, tenantId={}", tenantId, e);
|
||||
return summary;
|
||||
} catch (Exception e) {
|
||||
future.cancel(true);
|
||||
log.warn("Device summary failed, tenantId={}", tenantId, e);
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import io.github.pnoker.common.data.entity.property.PointBatchProperties;
|
||||
import io.github.pnoker.common.entity.bo.PointValueBO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
@@ -43,6 +44,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@DisallowConcurrentExecution
|
||||
public class PointValueJob extends QuartzJobBean {
|
||||
|
||||
private static final ReentrantReadWriteLock VALUE_LOCK = new ReentrantReadWriteLock();
|
||||
|
||||
+3
-1
@@ -145,7 +145,9 @@ public class DriverSenderServiceImpl implements DriverSenderService {
|
||||
log.warn(
|
||||
"DriverMetadata has no registered driver yet; point value will be published without driverId/tenantId");
|
||||
}
|
||||
log.info("Send point value: {}", JsonUtil.toJsonString(entityDTO));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Send point value: {}", JsonUtil.toJsonString(entityDTO));
|
||||
}
|
||||
rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE_VALUE,
|
||||
RabbitConstant.ROUTING_POINT_VALUE_PREFIX + driverProperties.getService(), entityDTO);
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class GrpcFacadeSupport {
|
||||
} catch (StatusRuntimeException e) {
|
||||
Status status = e.getStatus();
|
||||
String description = Objects.requireNonNullElse(status.getDescription(), e.getMessage());
|
||||
throw new ServiceException("{} transport failed: [{}] {}", operation, status.getCode(), description);
|
||||
throw new ServiceException(operation + " transport failed: [" + status.getCode() + "] " + description);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring Cache with Caffeine -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- gRPC Related -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.grpc</groupId>
|
||||
|
||||
+31
-24
@@ -21,16 +21,16 @@ import io.github.pnoker.common.entity.ext.JsonExt;
|
||||
import io.github.pnoker.common.exception.ImportException;
|
||||
import io.github.pnoker.common.manager.biz.ImportDeviceService;
|
||||
import io.github.pnoker.common.manager.dal.DeviceManager;
|
||||
import io.github.pnoker.common.manager.dal.DriverAttributeConfigManager;
|
||||
import io.github.pnoker.common.manager.dal.PointAttributeConfigManager;
|
||||
import io.github.pnoker.common.manager.entity.bo.DeviceBO;
|
||||
import io.github.pnoker.common.manager.entity.bo.DriverAttributeBO;
|
||||
import io.github.pnoker.common.manager.entity.bo.DriverAttributeConfigBO;
|
||||
import io.github.pnoker.common.manager.entity.bo.PointAttributeBO;
|
||||
import io.github.pnoker.common.manager.entity.bo.PointAttributeConfigBO;
|
||||
import io.github.pnoker.common.manager.entity.bo.PointBO;
|
||||
import io.github.pnoker.common.manager.entity.builder.DeviceBuilder;
|
||||
import io.github.pnoker.common.manager.entity.model.DeviceDO;
|
||||
import io.github.pnoker.common.manager.service.DriverAttributeConfigService;
|
||||
import io.github.pnoker.common.manager.service.PointAttributeConfigService;
|
||||
import io.github.pnoker.common.manager.entity.model.DriverAttributeConfigDO;
|
||||
import io.github.pnoker.common.manager.entity.model.PointAttributeConfigDO;
|
||||
import io.github.pnoker.common.utils.PoiUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -39,6 +39,7 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -57,9 +58,9 @@ public class ImportDeviceServiceImpl implements ImportDeviceService {
|
||||
|
||||
private final DeviceManager deviceManager;
|
||||
|
||||
private final DriverAttributeConfigService driverAttributeConfigService;
|
||||
private final DriverAttributeConfigManager driverAttributeConfigManager;
|
||||
|
||||
private final PointAttributeConfigService pointAttributeConfigService;
|
||||
private final PointAttributeConfigManager pointAttributeConfigManager;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -103,16 +104,19 @@ public class ImportDeviceServiceImpl implements ImportDeviceService {
|
||||
*/
|
||||
private void importDriverAttributeConfig(DeviceBO deviceBO, List<DriverAttributeBO> driverAttributeBOList,
|
||||
Sheet sheet, int row) {
|
||||
List<DriverAttributeConfigDO> entities = new ArrayList<>();
|
||||
for (int j = 0; j < driverAttributeBOList.size(); j++) {
|
||||
DriverAttributeConfigBO entityBO = new DriverAttributeConfigBO();
|
||||
DriverAttributeConfigDO entityDO = new DriverAttributeConfigDO();
|
||||
DriverAttributeBO driverAttributeBO = driverAttributeBOList.get(j);
|
||||
entityBO.setAttributeId(driverAttributeBO.getId());
|
||||
entityBO.setDeviceId(deviceBO.getId());
|
||||
String attributeValue = PoiUtil.getCellStringValue(sheet, row, 2 + j);
|
||||
entityBO.setConfigValue(attributeValue);
|
||||
entityBO.setRemark(deviceBO.getRemark());
|
||||
entityBO.setTenantId(deviceBO.getTenantId());
|
||||
driverAttributeConfigService.innerSave(entityBO);
|
||||
entityDO.setAttributeId(driverAttributeBO.getId());
|
||||
entityDO.setDeviceId(deviceBO.getId());
|
||||
entityDO.setConfigValue(PoiUtil.getCellStringValue(sheet, row, 2 + j));
|
||||
entityDO.setRemark(deviceBO.getRemark());
|
||||
entityDO.setTenantId(deviceBO.getTenantId());
|
||||
entities.add(entityDO);
|
||||
}
|
||||
if (!entities.isEmpty()) {
|
||||
driverAttributeConfigManager.saveBatch(entities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,22 +133,25 @@ public class ImportDeviceServiceImpl implements ImportDeviceService {
|
||||
private void importPointAttributeConfig(DeviceBO deviceBO, List<PointBO> pointBOList,
|
||||
List<DriverAttributeBO> driverAttributeBOList, List<PointAttributeBO> pointAttributeBOList, Sheet sheet,
|
||||
int row) {
|
||||
List<PointAttributeConfigDO> entities = new ArrayList<>();
|
||||
for (int j = 0; j < pointBOList.size(); j++) {
|
||||
for (int k = 0; k < pointAttributeBOList.size(); k++) {
|
||||
PointAttributeConfigBO entityBO = new PointAttributeConfigBO();
|
||||
PointAttributeConfigDO entityDO = new PointAttributeConfigDO();
|
||||
PointBO pointBO = pointBOList.get(j);
|
||||
PointAttributeBO pointAttributeBO = pointAttributeBOList.get(k);
|
||||
entityBO.setAttributeId(pointAttributeBO.getId());
|
||||
entityBO.setDeviceId(deviceBO.getId());
|
||||
entityBO.setPointId(pointBO.getId());
|
||||
String attributeValue = PoiUtil.getCellStringValue(sheet, row,
|
||||
2 + driverAttributeBOList.size() + k * pointAttributeBOList.size() + j);
|
||||
entityBO.setConfigValue(attributeValue);
|
||||
entityBO.setRemark(deviceBO.getRemark());
|
||||
entityBO.setTenantId(deviceBO.getTenantId());
|
||||
pointAttributeConfigService.innerSave(entityBO);
|
||||
entityDO.setAttributeId(pointAttributeBO.getId());
|
||||
entityDO.setDeviceId(deviceBO.getId());
|
||||
entityDO.setPointId(pointBO.getId());
|
||||
entityDO.setConfigValue(PoiUtil.getCellStringValue(sheet, row,
|
||||
2 + driverAttributeBOList.size() + k * pointAttributeBOList.size() + j));
|
||||
entityDO.setRemark(deviceBO.getRemark());
|
||||
entityDO.setTenantId(deviceBO.getTenantId());
|
||||
entities.add(entityDO);
|
||||
}
|
||||
}
|
||||
if (!entities.isEmpty()) {
|
||||
pointAttributeConfigManager.saveBatch(entities);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-18
@@ -14,28 +14,20 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.github.pnoker.driver.api.impl.serializer.parser;
|
||||
|
||||
import java.util.Vector;
|
||||
package io.github.pnoker.common.manager.config;
|
||||
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Bean Parse Result
|
||||
* Enables Spring Cache abstraction backed by Caffeine (auto-configured by Boot).
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
* @version 2026.5.26
|
||||
* @since 2026.5.26
|
||||
*/
|
||||
|
||||
public final class BeanParseResult {
|
||||
|
||||
/**
|
||||
* The needed blocksize
|
||||
*/
|
||||
public int blockSize;
|
||||
|
||||
/**
|
||||
* The Bean entries
|
||||
*/
|
||||
public Vector<BeanEntry> entries = new Vector<BeanEntry>();
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
}
|
||||
+7
-1
@@ -30,6 +30,7 @@ import io.github.pnoker.api.common.GrpcCommandDTO;
|
||||
import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.manager.entity.bo.CommandBO;
|
||||
import io.github.pnoker.common.manager.entity.query.CommandQuery;
|
||||
import io.github.pnoker.common.manager.grpc.builder.GrpcCommandBuilder;
|
||||
@@ -130,7 +131,12 @@ public class ManagerCommandServer extends CommandApiGrpc.CommandApiImplBase {
|
||||
GrpcRCommandDTO.Builder builder = GrpcRCommandDTO.newBuilder();
|
||||
GrpcR.Builder rBuilder = GrpcR.newBuilder();
|
||||
|
||||
CommandBO entityBO = commandService.getById(request.getCommandId());
|
||||
CommandBO entityBO;
|
||||
try {
|
||||
entityBO = commandService.getById(request.getCommandId());
|
||||
} catch (NotFoundException e) {
|
||||
entityBO = null;
|
||||
}
|
||||
if (Objects.isNull(entityBO)) {
|
||||
rBuilder.setOk(false);
|
||||
rBuilder.setCode(ResponseEnum.NO_RESOURCE.getCode());
|
||||
|
||||
+7
-1
@@ -32,6 +32,7 @@ import io.github.pnoker.api.common.GrpcDeviceDTO;
|
||||
import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.manager.entity.bo.DeviceBO;
|
||||
import io.github.pnoker.common.manager.entity.query.DeviceQuery;
|
||||
import io.github.pnoker.common.manager.grpc.builder.GrpcDeviceBuilder;
|
||||
@@ -186,7 +187,12 @@ public class ManagerDeviceServer extends DeviceApiGrpc.DeviceApiImplBase {
|
||||
GrpcRDeviceDTO.Builder builder = GrpcRDeviceDTO.newBuilder();
|
||||
GrpcR.Builder rBuilder = GrpcR.newBuilder();
|
||||
|
||||
DeviceBO entityBO = deviceService.getById(request.getDeviceId());
|
||||
DeviceBO entityBO;
|
||||
try {
|
||||
entityBO = deviceService.getById(request.getDeviceId());
|
||||
} catch (NotFoundException e) {
|
||||
entityBO = null;
|
||||
}
|
||||
if (Objects.isNull(entityBO)) {
|
||||
rBuilder.setOk(false);
|
||||
rBuilder.setCode(ResponseEnum.NO_RESOURCE.getCode());
|
||||
|
||||
+7
-1
@@ -31,6 +31,7 @@ import io.github.pnoker.api.common.GrpcDriverDTO;
|
||||
import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.manager.entity.bo.DriverBO;
|
||||
import io.github.pnoker.common.manager.entity.query.DriverQuery;
|
||||
import io.github.pnoker.common.manager.grpc.builder.GrpcDriverBuilder;
|
||||
@@ -154,7 +155,12 @@ public class ManagerDriverServer extends DriverApiGrpc.DriverApiImplBase {
|
||||
GrpcRDriverDTO.Builder builder = GrpcRDriverDTO.newBuilder();
|
||||
GrpcR.Builder rBuilder = GrpcR.newBuilder();
|
||||
|
||||
DriverBO driverBO = driverService.getById(request.getDriverId());
|
||||
DriverBO driverBO;
|
||||
try {
|
||||
driverBO = driverService.getById(request.getDriverId());
|
||||
} catch (NotFoundException e) {
|
||||
driverBO = null;
|
||||
}
|
||||
if (Objects.isNull(driverBO)) {
|
||||
rBuilder.setOk(false);
|
||||
rBuilder.setCode(ResponseEnum.NO_RESOURCE.getCode());
|
||||
|
||||
+7
-1
@@ -30,6 +30,7 @@ import io.github.pnoker.api.common.GrpcEventDTO;
|
||||
import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.manager.entity.bo.EventBO;
|
||||
import io.github.pnoker.common.manager.entity.query.EventQuery;
|
||||
import io.github.pnoker.common.manager.grpc.builder.GrpcEventBuilder;
|
||||
@@ -130,7 +131,12 @@ public class ManagerEventServer extends EventApiGrpc.EventApiImplBase {
|
||||
GrpcREventDTO.Builder builder = GrpcREventDTO.newBuilder();
|
||||
GrpcR.Builder rBuilder = GrpcR.newBuilder();
|
||||
|
||||
EventBO entityBO = eventService.getById(request.getEventId());
|
||||
EventBO entityBO;
|
||||
try {
|
||||
entityBO = eventService.getById(request.getEventId());
|
||||
} catch (NotFoundException e) {
|
||||
entityBO = null;
|
||||
}
|
||||
if (Objects.isNull(entityBO)) {
|
||||
rBuilder.setOk(false);
|
||||
rBuilder.setCode(ResponseEnum.NO_RESOURCE.getCode());
|
||||
|
||||
+7
-1
@@ -30,6 +30,7 @@ import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcPointDTO;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.manager.entity.bo.PointBO;
|
||||
import io.github.pnoker.common.manager.entity.query.PointQuery;
|
||||
import io.github.pnoker.common.manager.grpc.builder.GrpcPointBuilder;
|
||||
@@ -130,7 +131,12 @@ public class ManagerPointServer extends PointApiGrpc.PointApiImplBase {
|
||||
GrpcRPointDTO.Builder builder = GrpcRPointDTO.newBuilder();
|
||||
GrpcR.Builder rBuilder = GrpcR.newBuilder();
|
||||
|
||||
PointBO entityBO = pointService.getById(request.getPointId());
|
||||
PointBO entityBO;
|
||||
try {
|
||||
entityBO = pointService.getById(request.getPointId());
|
||||
} catch (NotFoundException e) {
|
||||
entityBO = null;
|
||||
}
|
||||
if (Objects.isNull(entityBO)) {
|
||||
rBuilder.setOk(false);
|
||||
rBuilder.setCode(ResponseEnum.NO_RESOURCE.getCode());
|
||||
|
||||
+7
-1
@@ -30,6 +30,7 @@ import io.github.pnoker.api.center.manager.ProfileApiGrpc;
|
||||
import io.github.pnoker.api.common.GrpcProfileDTO;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
|
||||
import io.github.pnoker.common.manager.entity.query.ProfileQuery;
|
||||
import io.github.pnoker.common.manager.grpc.builder.GrpcProfileBuilder;
|
||||
@@ -90,7 +91,12 @@ public class ManagerProfileServer extends ProfileApiGrpc.ProfileApiImplBase {
|
||||
GrpcRProfileDTO.Builder builder = GrpcRProfileDTO.newBuilder();
|
||||
GrpcR.Builder rBuilder = GrpcR.newBuilder();
|
||||
|
||||
ProfileBO profile = profileService.getById(request.getProfileId());
|
||||
ProfileBO profile;
|
||||
try {
|
||||
profile = profileService.getById(request.getProfileId());
|
||||
} catch (NotFoundException e) {
|
||||
profile = null;
|
||||
}
|
||||
if (Objects.isNull(profile)) {
|
||||
noResource(rBuilder);
|
||||
} else {
|
||||
|
||||
+9
-9
@@ -165,18 +165,18 @@ public class CommandParamServiceImpl implements CommandParamService {
|
||||
|
||||
private LambdaQueryWrapper<CommandParamDO> fuzzyQuery(CommandParamQuery entityQuery) {
|
||||
QueryWrapper<CommandParamDO> wrapper = Wrappers.query();
|
||||
wrapper.eq("dcp.deleted", 0);
|
||||
wrapper.like(StringUtils.isNotEmpty(entityQuery.getParamName()), "dcp.param_name", entityQuery.getParamName());
|
||||
wrapper.eq(StringUtils.isNotEmpty(entityQuery.getParamCode()), "dcp.param_code", entityQuery.getParamCode());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getParamDirection()), "dcp.param_direction_flag",
|
||||
wrapper.eq("deleted", 0);
|
||||
wrapper.like(StringUtils.isNotEmpty(entityQuery.getParamName()), "param_name", entityQuery.getParamName());
|
||||
wrapper.eq(StringUtils.isNotEmpty(entityQuery.getParamCode()), "param_code", entityQuery.getParamCode());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getParamDirection()), "param_direction_flag",
|
||||
Objects.isNull(entityQuery.getParamDirection()) ? null : entityQuery.getParamDirection().getIndex());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getParamTypeFlag()), "dcp.param_type_flag",
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getParamTypeFlag()), "param_type_flag",
|
||||
Objects.isNull(entityQuery.getParamTypeFlag()) ? null : entityQuery.getParamTypeFlag().getIndex());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getCommandId()), "dcp.command_id", entityQuery.getCommandId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getEnableFlag()), "dcp.enable_flag",
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getCommandId()), "command_id", entityQuery.getCommandId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getEnableFlag()), "enable_flag",
|
||||
Objects.isNull(entityQuery.getEnableFlag()) ? null : entityQuery.getEnableFlag().getIndex());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getTenantId()), "dcp.tenant_id", entityQuery.getTenantId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getVersion()), "dcp.version", entityQuery.getVersion());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getTenantId()), "tenant_id", entityQuery.getTenantId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getVersion()), "version", entityQuery.getVersion());
|
||||
return wrapper.lambda();
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -165,16 +165,16 @@ public class EventParamServiceImpl implements EventParamService {
|
||||
|
||||
private LambdaQueryWrapper<EventParamDO> fuzzyQuery(EventParamQuery entityQuery) {
|
||||
QueryWrapper<EventParamDO> wrapper = Wrappers.query();
|
||||
wrapper.eq("dep.deleted", 0);
|
||||
wrapper.like(StringUtils.isNotEmpty(entityQuery.getParamName()), "dep.param_name", entityQuery.getParamName());
|
||||
wrapper.eq(StringUtils.isNotEmpty(entityQuery.getParamCode()), "dep.param_code", entityQuery.getParamCode());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getParamTypeFlag()), "dep.param_type_flag",
|
||||
wrapper.eq("deleted", 0);
|
||||
wrapper.like(StringUtils.isNotEmpty(entityQuery.getParamName()), "param_name", entityQuery.getParamName());
|
||||
wrapper.eq(StringUtils.isNotEmpty(entityQuery.getParamCode()), "param_code", entityQuery.getParamCode());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getParamTypeFlag()), "param_type_flag",
|
||||
Objects.isNull(entityQuery.getParamTypeFlag()) ? null : entityQuery.getParamTypeFlag().getIndex());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getEventId()), "dep.event_id", entityQuery.getEventId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getEnableFlag()), "dep.enable_flag",
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getEventId()), "event_id", entityQuery.getEventId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getEnableFlag()), "enable_flag",
|
||||
Objects.isNull(entityQuery.getEnableFlag()) ? null : entityQuery.getEnableFlag().getIndex());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getTenantId()), "dep.tenant_id", entityQuery.getTenantId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getVersion()), "dep.version", entityQuery.getVersion());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getTenantId()), "tenant_id", entityQuery.getTenantId());
|
||||
wrapper.eq(Objects.nonNull(entityQuery.getVersion()), "version", entityQuery.getVersion());
|
||||
return wrapper.lambda();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ public class PointServiceImpl implements PointService {
|
||||
List<DeviceDO> deviceDOList = deviceMapper
|
||||
.selectList(new LambdaQueryWrapper<DeviceDO>().in(DeviceDO::getId, deviceIds));
|
||||
deviceByPointBO.setDevices(deviceDOList);
|
||||
deviceByPointBO.setCount(deviceDOList.stream().count());
|
||||
deviceByPointBO.setCount((long) deviceDOList.size());
|
||||
} else {
|
||||
deviceByPointBO.setDevices(Collections.emptyList());
|
||||
deviceByPointBO.setCount(0L);
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import io.github.pnoker.common.mqtt.entity.property.MqttProperties;
|
||||
import io.github.pnoker.common.mqtt.service.MqttReceiveService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -49,6 +50,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnBean(MqttReceiveService.class)
|
||||
@DisallowConcurrentExecution
|
||||
public class MqttScheduleJob extends QuartzJobBean {
|
||||
|
||||
private static final ReentrantReadWriteLock MESSAGE_LOCK = new ReentrantReadWriteLock();
|
||||
|
||||
@@ -119,6 +119,9 @@ public class HostUtil {
|
||||
ArrayList<String> macList = new ArrayList<>(16);
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
if (Objects.isNull(interfaces)) {
|
||||
return macList;
|
||||
}
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
lookupLocalMac(macList, networkInterface);
|
||||
@@ -143,6 +146,9 @@ public class HostUtil {
|
||||
*/
|
||||
private static void loopBackAddresses(Set<String> hostNames, boolean includeLoopBack) throws SocketException {
|
||||
Enumeration<NetworkInterface> interfaceEnumeration = NetworkInterface.getNetworkInterfaces();
|
||||
if (Objects.isNull(interfaceEnumeration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (NetworkInterface networkInterface : Collections.list(interfaceEnumeration)) {
|
||||
Collections.list(networkInterface.getInetAddresses()).forEach(inetAddress -> {
|
||||
|
||||
+16
-6
@@ -199,6 +199,14 @@ public class KeyUtil {
|
||||
return DecodeUtil.byteToString(cipher.doFinal(inputByte));
|
||||
}
|
||||
|
||||
private static String getSecurityKey() {
|
||||
String key = System.getenv("DC3_SECURITY_KEY");
|
||||
if (key == null || key.isBlank()) {
|
||||
key = System.getProperty("dc3.security.key", AlgorithmConstant.DEFAULT_KEY);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JWT token.
|
||||
*
|
||||
@@ -208,11 +216,12 @@ public class KeyUtil {
|
||||
* @return Token string
|
||||
*/
|
||||
public static String generateToken(String userName, String salt, Long tenantId) {
|
||||
String securityKey = getSecurityKey();
|
||||
SecretKey key = io.jsonwebtoken.security.Keys
|
||||
.hmacShaKeyFor(DecodeUtil.stringToByte(AlgorithmConstant.DEFAULT_KEY + SymbolConstant.COLON + salt));
|
||||
.hmacShaKeyFor(DecodeUtil.stringToByte(securityKey + SymbolConstant.COLON + salt));
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.issuer(AlgorithmConstant.DEFAULT_KEY + SymbolConstant.COLON + tenantId)
|
||||
.subject(AlgorithmConstant.DEFAULT_KEY + SymbolConstant.COLON + userName)
|
||||
.issuer(securityKey + SymbolConstant.COLON + tenantId)
|
||||
.subject(securityKey + SymbolConstant.COLON + userName)
|
||||
.issuedAt(new Date())
|
||||
.signWith(key, Jwts.SIG.HS256)
|
||||
.expiration(TimeUtil.expireTime(TimeoutConstant.TOKEN_CACHE_TIMEOUT, Calendar.HOUR));
|
||||
@@ -229,11 +238,12 @@ public class KeyUtil {
|
||||
* @return Claims
|
||||
*/
|
||||
public static Claims parserToken(String userName, String salt, String token, Long tenantId) {
|
||||
String securityKey = getSecurityKey();
|
||||
SecretKey key = io.jsonwebtoken.security.Keys
|
||||
.hmacShaKeyFor(DecodeUtil.stringToByte(AlgorithmConstant.DEFAULT_KEY + SymbolConstant.COLON + salt));
|
||||
.hmacShaKeyFor(DecodeUtil.stringToByte(securityKey + SymbolConstant.COLON + salt));
|
||||
JwtParser parser = Jwts.parser()
|
||||
.requireIssuer(AlgorithmConstant.DEFAULT_KEY + SymbolConstant.COLON + tenantId)
|
||||
.requireSubject(AlgorithmConstant.DEFAULT_KEY + SymbolConstant.COLON + userName)
|
||||
.requireIssuer(securityKey + SymbolConstant.COLON + tenantId)
|
||||
.requireSubject(securityKey + SymbolConstant.COLON + userName)
|
||||
.verifyWith(key)
|
||||
.build();
|
||||
return parser.parseSignedClaims(token).getPayload();
|
||||
|
||||
+15
@@ -60,6 +60,9 @@ public class RegexUtil {
|
||||
* @return true if valid name format, false otherwise
|
||||
*/
|
||||
public static boolean isName(String name) {
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
return false;
|
||||
}
|
||||
String regex = "^[A-Za-z0-9\\u4e00-\\u9fa5][A-Za-z0-9\\u4e00-\\u9fa5-_#@/.|]{1,31}$";
|
||||
return name.matches(regex);
|
||||
}
|
||||
@@ -71,6 +74,9 @@ public class RegexUtil {
|
||||
* @return true if valid mobile format, false otherwise
|
||||
*/
|
||||
public static boolean isPhone(String phone) {
|
||||
if (StringUtils.isEmpty(phone)) {
|
||||
return false;
|
||||
}
|
||||
String regex = "^1([3-9])\\d{9}$";
|
||||
return phone.matches(regex);
|
||||
}
|
||||
@@ -82,6 +88,9 @@ public class RegexUtil {
|
||||
* @return true if valid email format, false otherwise
|
||||
*/
|
||||
public static boolean isMail(String mail) {
|
||||
if (StringUtils.isEmpty(mail)) {
|
||||
return false;
|
||||
}
|
||||
String regex = "^[A-Za-z0-9_.-]+@[A-Za-z0-9]+\\.[A-Za-z0-9]+$";
|
||||
return mail.matches(regex);
|
||||
}
|
||||
@@ -93,6 +102,9 @@ public class RegexUtil {
|
||||
* @return true if valid password format, false otherwise
|
||||
*/
|
||||
public static boolean isPassword(String password) {
|
||||
if (StringUtils.isEmpty(password)) {
|
||||
return false;
|
||||
}
|
||||
String regex = "^[a-zA-Z]\\w{7,15}$";
|
||||
return password.matches(regex);
|
||||
}
|
||||
@@ -104,6 +116,9 @@ public class RegexUtil {
|
||||
* @return true if valid host format, false otherwise
|
||||
*/
|
||||
public static boolean isHost(String host) {
|
||||
if (StringUtils.isEmpty(host)) {
|
||||
return false;
|
||||
}
|
||||
String regex = "^((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}$";
|
||||
return host.matches(regex);
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ public class TimeUtil {
|
||||
try {
|
||||
return DEFAULT_DATE_FORMAT_THREAD_LOCAL.get().parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
log.debug("Failed to parse date string '{}' with default format", dateString);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -127,6 +128,7 @@ public class TimeUtil {
|
||||
try {
|
||||
return COMPLETE_DATE_FORMAT_THREAD_LOCAL.get().parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
log.debug("Failed to parse date string '{}' with complete format", dateString);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -98,10 +98,9 @@ public class WebFilterConfig {
|
||||
.contextWrite(context -> context.put(RequestConstant.Key.USER_HEADER, userHeader));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error parsing user header", e);
|
||||
log.warn("Rejecting request with malformed X-Auth-User header, Url: {}", request.getURI(), e);
|
||||
return writeUnauthorized(exchange);
|
||||
}
|
||||
|
||||
return chain.filter(exchange);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,11 @@
|
||||
<artifactId>dc3-driver-modbus-tcp</artifactId>
|
||||
<version>${dc3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.pnoker</groupId>
|
||||
<artifactId>dc3-driver-modbus-rtu</artifactId>
|
||||
<version>${dc3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.pnoker</groupId>
|
||||
<artifactId>dc3-driver-mqtt</artifactId>
|
||||
|
||||
+26
-22
@@ -67,22 +67,24 @@ public class CoapClientManager implements DisposableBean {
|
||||
*/
|
||||
public CoapResult get(String uri, String path) {
|
||||
CoapClient client = getClient(uri);
|
||||
client.setURI(uri + path);
|
||||
try {
|
||||
Configuration config = client.getEndpoint().getConfig();
|
||||
config.set(CoapConfig.EXCHANGE_LIFETIME, coapProperties.getClientTimeout(), TimeUnit.MILLISECONDS);
|
||||
config.set(CoapConfig.ACK_TIMEOUT, coapProperties.getClientAckTimeout(), TimeUnit.MILLISECONDS);
|
||||
config.set(CoapConfig.MAX_RETRANSMIT, coapProperties.getClientMaxRetransmit());
|
||||
synchronized (client) {
|
||||
client.setURI(uri + path);
|
||||
try {
|
||||
Configuration config = client.getEndpoint().getConfig();
|
||||
config.set(CoapConfig.EXCHANGE_LIFETIME, coapProperties.getClientTimeout(), TimeUnit.MILLISECONDS);
|
||||
config.set(CoapConfig.ACK_TIMEOUT, coapProperties.getClientAckTimeout(), TimeUnit.MILLISECONDS);
|
||||
config.set(CoapConfig.MAX_RETRANSMIT, coapProperties.getClientMaxRetransmit());
|
||||
|
||||
org.eclipse.californium.core.CoapResponse response = client.get();
|
||||
if (response == null) {
|
||||
log.warn("CoAP GET timeout: {}{}", uri, path);
|
||||
org.eclipse.californium.core.CoapResponse response = client.get();
|
||||
if (response == null) {
|
||||
log.warn("CoAP GET timeout: {}{}", uri, path);
|
||||
return null;
|
||||
}
|
||||
return toResult(response);
|
||||
} catch (Exception e) {
|
||||
log.error("CoAP GET failed: {}{}", uri, path, e);
|
||||
return null;
|
||||
}
|
||||
return toResult(response);
|
||||
} catch (Exception e) {
|
||||
log.error("CoAP GET failed: {}{}", uri, path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,17 +98,19 @@ public class CoapClientManager implements DisposableBean {
|
||||
*/
|
||||
public CoapResult put(String uri, String path, String payload) {
|
||||
CoapClient client = getClient(uri);
|
||||
client.setURI(uri + path);
|
||||
try {
|
||||
org.eclipse.californium.core.CoapResponse response = client.put(payload, MediaTypeRegistry.APPLICATION_JSON);
|
||||
if (response == null) {
|
||||
log.warn("CoAP PUT timeout: {}{}", uri, path);
|
||||
synchronized (client) {
|
||||
client.setURI(uri + path);
|
||||
try {
|
||||
org.eclipse.californium.core.CoapResponse response = client.put(payload, MediaTypeRegistry.APPLICATION_JSON);
|
||||
if (response == null) {
|
||||
log.warn("CoAP PUT timeout: {}{}", uri, path);
|
||||
return null;
|
||||
}
|
||||
return toResult(response);
|
||||
} catch (Exception e) {
|
||||
log.error("CoAP PUT failed: {}{}", uri, path, e);
|
||||
return null;
|
||||
}
|
||||
return toResult(response);
|
||||
} catch (Exception e) {
|
||||
log.error("CoAP PUT failed: {}{}", uri, path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2016-present the IoT DC3 original author or authors.
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU Affero General Public License as
|
||||
~ published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU Affero General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.github.pnoker</groupId>
|
||||
<artifactId>dc3-driver</artifactId>
|
||||
<version>2026.5.22</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dc3-driver-modbus-rtu</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>The Modbus RTU driver for the IoT DC3 platform.</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Modbus Protocol -->
|
||||
<dependency>
|
||||
<groupId>com.infiniteautomation</groupId>
|
||||
<artifactId>modbus4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Serial Communication -->
|
||||
<dependency>
|
||||
<groupId>com.fazecast</groupId>
|
||||
<artifactId>jSerialComm</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+15
-12
@@ -14,28 +14,31 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
package io.github.pnoker.driver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Slave Id Not Equal
|
||||
* Modbus RTU Driver Application for DC3 IoT Platform. This driver implements
|
||||
* serial communication with Modbus RTU devices, providing data collection and
|
||||
* device management capabilities over RS-232/RS-485 serial interfaces.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2025.9.0
|
||||
* @version 2026.5.22
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
|
||||
public class SlaveIdNotEqual extends ModbusTransportException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
@SpringBootApplication
|
||||
public class ModbusRtuDriverApplication {
|
||||
|
||||
/**
|
||||
* Exception to show that the requested slave id is not what was received
|
||||
* Main method to start the Modbus RTU driver application
|
||||
*
|
||||
* @param requestSlaveId - slave id requested
|
||||
* @param responseSlaveId - slave id of response
|
||||
* @param args Command line arguments passed to the application
|
||||
*/
|
||||
public SlaveIdNotEqual(int requestSlaveId, int responseSlaveId) {
|
||||
super("Response slave id different from requested id", requestSlaveId);
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ModbusRtuDriverApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.driver.service.impl;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import com.serotonin.modbus4j.serial.SerialPortWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* jSerialComm implementation of the modbus4j SerialPortWrapper interface.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.22
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
@Slf4j
|
||||
public class JSerialCommWrapper implements SerialPortWrapper {
|
||||
|
||||
private final String portName;
|
||||
private final int baudRate;
|
||||
private final int dataBits;
|
||||
private final int stopBits;
|
||||
private final int parity;
|
||||
private SerialPort serialPort;
|
||||
|
||||
/**
|
||||
* @param portName serial port name (e.g. /dev/ttyUSB0, COM3)
|
||||
* @param baudRate baud rate (e.g. 9600, 19200, 115200)
|
||||
* @param dataBits data bits (7 or 8)
|
||||
* @param stopBits stop bits (1 or 2)
|
||||
* @param parity parity (0=None, 1=Odd, 2=Even, 3=Mark, 4=Space)
|
||||
*/
|
||||
public JSerialCommWrapper(String portName, int baudRate, int dataBits, int stopBits, int parity) {
|
||||
this.portName = portName;
|
||||
this.baudRate = baudRate;
|
||||
this.dataBits = dataBits;
|
||||
this.stopBits = stopBits;
|
||||
this.parity = parity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() throws Exception {
|
||||
serialPort = SerialPort.getCommPort(portName);
|
||||
serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity);
|
||||
serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 2000, 0);
|
||||
if (!serialPort.openPort()) {
|
||||
throw new Exception("Failed to open serial port: " + portName);
|
||||
}
|
||||
log.info("Serial port opened, port={}, baudRate={}, dataBits={}, stopBits={}, parity={}",
|
||||
portName, baudRate, dataBits, stopBits, parity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (serialPort != null && serialPort.isOpen()) {
|
||||
serialPort.closePort();
|
||||
log.info("Serial port closed, port={}", portName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return serialPort.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return serialPort.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaudRate() {
|
||||
return baudRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataBits() {
|
||||
return dataBits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStopBits() {
|
||||
return stopBits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParity() {
|
||||
return parity;
|
||||
}
|
||||
|
||||
}
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.driver.service.impl;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusFactory;
|
||||
import com.serotonin.modbus4j.ModbusMaster;
|
||||
import com.serotonin.modbus4j.code.DataType;
|
||||
import com.serotonin.modbus4j.exception.ErrorResponseException;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.locator.BaseLocator;
|
||||
import com.serotonin.modbus4j.msg.WriteCoilRequest;
|
||||
import com.serotonin.modbus4j.msg.WriteCoilResponse;
|
||||
import io.github.pnoker.common.driver.entity.bean.DeviceHealthState;
|
||||
import io.github.pnoker.common.driver.entity.bean.ReadPointValue;
|
||||
import io.github.pnoker.common.driver.entity.bean.WritePointValue;
|
||||
import io.github.pnoker.common.driver.entity.bo.AttributeBO;
|
||||
import io.github.pnoker.common.driver.entity.bo.DeviceBO;
|
||||
import io.github.pnoker.common.driver.entity.bo.PointBO;
|
||||
import io.github.pnoker.common.driver.metadata.DriverMetadata;
|
||||
import io.github.pnoker.common.driver.service.DriverCustomService;
|
||||
import io.github.pnoker.common.driver.service.DriverSenderService;
|
||||
import io.github.pnoker.common.entity.dto.MetadataEventDTO;
|
||||
import io.github.pnoker.common.enums.MetadataOperateTypeEnum;
|
||||
import io.github.pnoker.common.enums.MetadataTypeEnum;
|
||||
import io.github.pnoker.common.enums.PointTypeFlagEnum;
|
||||
import io.github.pnoker.common.exception.ConnectorException;
|
||||
import io.github.pnoker.common.exception.ReadPointException;
|
||||
import io.github.pnoker.common.exception.UnSupportException;
|
||||
import io.github.pnoker.common.exception.WritePointException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Custom driver service implementation for the Modbus RTU driver.
|
||||
* <p>
|
||||
* Manages Modbus RTU serial connections, reads point values from Modbus devices
|
||||
* via function codes 1-4, and writes values to coils and holding registers.
|
||||
* Each RTU device is connected via a dedicated serial port.
|
||||
* </p>
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.22
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ModbusRtuDriverCustomServiceImpl implements DriverCustomService {
|
||||
|
||||
/**
|
||||
* Modbus factory for creating ModbusMaster instances.
|
||||
*/
|
||||
static ModbusFactory modbusFactory;
|
||||
|
||||
static {
|
||||
modbusFactory = new ModbusFactory();
|
||||
}
|
||||
|
||||
private final DriverMetadata driverMetadata;
|
||||
private final DriverSenderService driverSenderService;
|
||||
@Value("${dc3.driver.code}")
|
||||
private String driverCode;
|
||||
/**
|
||||
* Cache of device ID to ModbusMaster connections.
|
||||
*/
|
||||
private Map<Long, ModbusMaster> connectMap;
|
||||
|
||||
@Override
|
||||
public void initial() {
|
||||
connectMap = new ConcurrentHashMap<>(16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule() {
|
||||
// Device state lease renewal is owned by the SDK device health job.
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceHealthState health(Map<String, AttributeBO> driverConfig, DeviceBO device) {
|
||||
if (Objects.isNull(device) || Objects.isNull(device.getId())) {
|
||||
return DeviceHealthState.offline();
|
||||
}
|
||||
try {
|
||||
return getConnector(device.getId(), driverConfig).isInitialized()
|
||||
? DeviceHealthState.online()
|
||||
: DeviceHealthState.offline();
|
||||
} catch (Exception e) {
|
||||
log.warn("Driver health check failed, protocol=" + driverCode + ", deviceId={}", device.getId(), e);
|
||||
return DeviceHealthState.offline();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void event(MetadataEventDTO metadataEvent) {
|
||||
MetadataTypeEnum metadataType = metadataEvent.getMetadataType();
|
||||
MetadataOperateTypeEnum operateType = metadataEvent.getOperateType();
|
||||
if (MetadataTypeEnum.DEVICE.equals(metadataType)) {
|
||||
log.info("Driver metadata event received, protocol=" + driverCode + ", metadataType={}, operateType={}, deviceId={}",
|
||||
metadataType, operateType, metadataEvent.getId());
|
||||
|
||||
// Remove stale connection when device is updated or deleted
|
||||
if (MetadataOperateTypeEnum.DELETE.equals(operateType)
|
||||
|| MetadataOperateTypeEnum.UPDATE.equals(operateType)) {
|
||||
ModbusMaster removed = connectMap.remove(metadataEvent.getId());
|
||||
if (Objects.nonNull(removed)) {
|
||||
removed.destroy();
|
||||
log.info("Driver connection destroyed, protocol=" + driverCode + ", deviceId={}, operateType={}",
|
||||
metadataEvent.getId(), operateType);
|
||||
}
|
||||
}
|
||||
} else if (MetadataTypeEnum.POINT.equals(metadataType)) {
|
||||
log.info("Driver metadata event received, protocol=" + driverCode + ", metadataType={}, operateType={}, pointId={}",
|
||||
metadataType, operateType, metadataEvent.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadPointValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, DeviceBO device,
|
||||
PointBO point) {
|
||||
return new ReadPointValue(device, point,
|
||||
readValue(getConnector(device.getId(), driverConfig), pointConfig, point.getPointTypeFlag().getCode()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, DeviceBO device,
|
||||
PointBO point, WritePointValue writePointValue) {
|
||||
ModbusMaster modbusMaster = getConnector(device.getId(), driverConfig);
|
||||
return writeValue(modbusMaster, pointConfig, writePointValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a Modbus RTU connection for the given device.
|
||||
*
|
||||
* @param deviceId unique device identifier
|
||||
* @param driverConfig driver configuration containing serial port parameters
|
||||
* @return cached or newly created ModbusMaster
|
||||
* @throws ConnectorException if connection initialization fails
|
||||
*/
|
||||
private ModbusMaster getConnector(Long deviceId, Map<String, AttributeBO> driverConfig) {
|
||||
return connectMap.computeIfAbsent(deviceId, id -> {
|
||||
String port = driverConfig.get("port").getValue(String.class);
|
||||
int baudRate = driverConfig.get("baudRate").getValue(Integer.class);
|
||||
int dataBits = driverConfig.get("dataBits").getValue(Integer.class);
|
||||
int stopBits = driverConfig.get("stopBits").getValue(Integer.class);
|
||||
int parity = driverConfig.get("parity").getValue(Integer.class);
|
||||
log.debug("Driver connection creating, protocol=" + driverCode + ", deviceId={}, port={}, baudRate={}", deviceId,
|
||||
port, baudRate);
|
||||
JSerialCommWrapper wrapper = new JSerialCommWrapper(port, baudRate, dataBits, stopBits, parity);
|
||||
ModbusMaster modbusMaster = modbusFactory.createRtuMaster(wrapper);
|
||||
try {
|
||||
modbusMaster.init();
|
||||
log.info("Driver connection established, protocol=" + driverCode + ", deviceId={}, port={}, baudRate={}",
|
||||
deviceId, port, baudRate);
|
||||
} catch (ModbusInitException e) {
|
||||
log.error("Driver connection failed, protocol=" + driverCode + ", deviceId={}, port={}, baudRate={}", deviceId,
|
||||
port, baudRate, e);
|
||||
throw new ConnectorException("Driver connection failed, protocol=" + driverCode + ", deviceId={}, port={}, message={}",
|
||||
deviceId, port, e.getMessage(), e);
|
||||
}
|
||||
return modbusMaster;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a point value from the Modbus device by function code.
|
||||
* <p>
|
||||
* Function codes: 1=coil, 2=input status, 3=holding register, 4=input register.
|
||||
*
|
||||
* @param modbusMaster active Modbus connection
|
||||
* @param pointConfig point configuration (slaveId, functionCode, offset)
|
||||
* @param type point value type for register data interpretation
|
||||
* @return read value as string, or "0" for unsupported function codes
|
||||
*/
|
||||
private String readValue(ModbusMaster modbusMaster, Map<String, AttributeBO> pointConfig, String type) {
|
||||
int slaveId = pointConfig.get("slaveId").getValue(Integer.class);
|
||||
int functionCode = pointConfig.get("functionCode").getValue(Integer.class);
|
||||
int offset = pointConfig.get("offset").getValue(Integer.class);
|
||||
switch (functionCode) {
|
||||
case 1:
|
||||
BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, offset);
|
||||
Boolean coilValue = getMasterValue(modbusMaster, coilLocator);
|
||||
return String.valueOf(coilValue);
|
||||
case 2:
|
||||
BaseLocator<Boolean> inputLocator = BaseLocator.inputStatus(slaveId, offset);
|
||||
Boolean inputStatusValue = getMasterValue(modbusMaster, inputLocator);
|
||||
return String.valueOf(inputStatusValue);
|
||||
case 3:
|
||||
BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, offset, getValueType(type));
|
||||
Number holdingValue = getMasterValue(modbusMaster, holdingLocator);
|
||||
return String.valueOf(holdingValue);
|
||||
case 4:
|
||||
BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, offset, getValueType(type));
|
||||
Number inputRegisterValue = getMasterValue(modbusMaster, inputRegister);
|
||||
return String.valueOf(inputRegisterValue);
|
||||
default:
|
||||
log.warn("Unsupported Modbus function code, slaveId={}, functionCode={}, offset={}", slaveId,
|
||||
functionCode, offset);
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a value from the Modbus device using the given locator.
|
||||
*
|
||||
* @param modbusMaster active Modbus connection
|
||||
* @param locator identifies the target point (slave, function, offset)
|
||||
* @param <T> value type determined by the locator
|
||||
* @return the read value
|
||||
* @throws ReadPointException if a transport or error response occurs
|
||||
*/
|
||||
private <T> T getMasterValue(ModbusMaster modbusMaster, BaseLocator<T> locator) {
|
||||
try {
|
||||
return modbusMaster.getValue(locator);
|
||||
} catch (ModbusTransportException | ErrorResponseException e) {
|
||||
log.error("Driver point read failed, protocol=" + driverCode + "", e);
|
||||
throw new ReadPointException("Driver point read failed, protocol=" + driverCode + ", message={}", e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a point value to the Modbus device by function code.
|
||||
* <p>
|
||||
* Function codes: 1=write coil, 3=write holding register. Others return false.
|
||||
*
|
||||
* @param modbusMaster active Modbus connection
|
||||
* @param pointConfig point configuration (slaveId, functionCode, offset)
|
||||
* @param writePointValue value to write
|
||||
* @return true if write succeeded, false if failed or unsupported function code
|
||||
*/
|
||||
private boolean writeValue(ModbusMaster modbusMaster, Map<String, AttributeBO> pointConfig, WritePointValue writePointValue) {
|
||||
int slaveId = pointConfig.get("slaveId").getValue(Integer.class);
|
||||
int functionCode = pointConfig.get("functionCode").getValue(Integer.class);
|
||||
int offset = pointConfig.get("offset").getValue(Integer.class);
|
||||
switch (functionCode) {
|
||||
case 1:
|
||||
WriteCoilResponse coilResponse = setMasterValue(modbusMaster, slaveId, offset, writePointValue);
|
||||
return !coilResponse.isException();
|
||||
case 3:
|
||||
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset,
|
||||
getValueType(writePointValue.getType().getCode()));
|
||||
setMasterValue(modbusMaster, locator, writePointValue);
|
||||
return true;
|
||||
default:
|
||||
log.warn("Unsupported Modbus write function code, slaveId={}, functionCode={}, offset={}", slaveId,
|
||||
functionCode, offset);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a point type flag to a Modbus DataType constant.
|
||||
* <p>
|
||||
* LONG->4-byte int, FLOAT->4-byte float, DOUBLE->8-byte float, else 2-byte int.
|
||||
*
|
||||
* @param type point type code
|
||||
* @return Modbus DataType constant
|
||||
* @throws UnSupportException if the type is unknown
|
||||
*/
|
||||
private int getValueType(String type) {
|
||||
PointTypeFlagEnum valueType = PointTypeFlagEnum.ofCode(type);
|
||||
if (Objects.isNull(valueType)) {
|
||||
throw new UnSupportException("Unsupported type of " + type);
|
||||
}
|
||||
|
||||
return switch (valueType) {
|
||||
case LONG -> DataType.FOUR_BYTE_INT_SIGNED;
|
||||
case FLOAT -> DataType.FOUR_BYTE_FLOAT;
|
||||
case DOUBLE -> DataType.EIGHT_BYTE_FLOAT;
|
||||
default -> DataType.TWO_BYTE_INT_SIGNED;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a boolean value to a Modbus coil.
|
||||
*
|
||||
* @param modbusMaster active Modbus connection
|
||||
* @param slaveId target slave address
|
||||
* @param offset coil offset
|
||||
* @param writePointValue value containing the boolean to write
|
||||
* @return the coil write response
|
||||
* @throws WritePointException if a transport error occurs
|
||||
*/
|
||||
private WriteCoilResponse setMasterValue(ModbusMaster modbusMaster, int slaveId, int offset, WritePointValue writePointValue) {
|
||||
try {
|
||||
WriteCoilRequest coilRequest = new WriteCoilRequest(slaveId, offset, writePointValue.getValue(Boolean.class));
|
||||
return (WriteCoilResponse) modbusMaster.send(coilRequest);
|
||||
} catch (ModbusTransportException e) {
|
||||
log.error("Driver point write failed, protocol=" + driverCode + ", slaveId={}, offset={}", slaveId, offset, e);
|
||||
throw new WritePointException("Driver point write failed, protocol=" + driverCode + ", slaveId={}, offset={}, message={}",
|
||||
slaveId, offset, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a numeric value to a Modbus holding register via the given locator.
|
||||
*
|
||||
* @param modbusMaster active Modbus connection
|
||||
* @param locator identifies the target register
|
||||
* @param writePointValue value to write
|
||||
* @param <T> value type determined by the locator
|
||||
* @throws WritePointException if a transport or error response occurs
|
||||
*/
|
||||
private <T> void setMasterValue(ModbusMaster modbusMaster, BaseLocator<T> locator, WritePointValue writePointValue) {
|
||||
try {
|
||||
PointTypeFlagEnum valueType = PointTypeFlagEnum.ofCode(writePointValue.getType().getCode());
|
||||
if (Objects.isNull(valueType)) {
|
||||
throw new UnSupportException("Unsupported type of " + writePointValue.getType());
|
||||
}
|
||||
Number value = switch (valueType) {
|
||||
case INT -> writePointValue.getValue(Integer.class);
|
||||
case LONG -> writePointValue.getValue(Long.class);
|
||||
case FLOAT -> writePointValue.getValue(Float.class);
|
||||
case DOUBLE -> writePointValue.getValue(Double.class);
|
||||
default -> writePointValue.getValue(Float.class);
|
||||
};
|
||||
modbusMaster.setValue(locator, value);
|
||||
} catch (ModbusTransportException | ErrorResponseException e) {
|
||||
log.error("Driver point write failed, protocol=" + driverCode + "", e);
|
||||
throw new WritePointException("Driver point write failed, protocol=" + driverCode + ", message={}", e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# Copyright 2016-present the IoT DC3 original author or authors.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Development environment configuration will overwrite the content configured in application-*.yml
|
||||
dc3:
|
||||
driver:
|
||||
schedule:
|
||||
# Read data regularly
|
||||
read:
|
||||
cron: '0/5 * * * * ?'
|
||||
|
||||
spring:
|
||||
# When enabling env and group, auth, manager and data services need to be started locally
|
||||
env: dev
|
||||
group: ${user.name}
|
||||
rabbitmq:
|
||||
virtual-host: ${RABBITMQ_VIRTUAL_HOST:dc3}
|
||||
host: ${RABBITMQ_HOST:dc3-rabbitmq}
|
||||
port: ${RABBITMQ_PORT:35672}
|
||||
username: ${RABBITMQ_USERNAME:dc3}
|
||||
password: ${RABBITMQ_PASSWORD:dc3dc3dc3}
|
||||
|
||||
logging:
|
||||
level:
|
||||
io.github.pnoker: DEBUG
|
||||
io.github.pnoker.driver.sdk: DEBUG
|
||||
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# Copyright 2016-present the IoT DC3 original author or authors.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Pre-release environment configuration will overwrite the content configured in application-*.yml
|
||||
dc3:
|
||||
driver:
|
||||
schedule:
|
||||
# Read data regularly
|
||||
read:
|
||||
cron: '0 0/15 * * * ?'
|
||||
|
||||
spring:
|
||||
rabbitmq:
|
||||
virtual-host: ${RABBITMQ_VIRTUAL_HOST:dc3}
|
||||
host: ${RABBITMQ_HOST:dc3-rabbitmq}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:dc3}
|
||||
password: ${RABBITMQ_PASSWORD:dc3dc3dc3}
|
||||
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# Copyright 2016-present the IoT DC3 original author or authors.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Production environment configuration will overwrite the content configured in application-*.yml
|
||||
dc3:
|
||||
driver:
|
||||
schedule:
|
||||
# Read data regularly
|
||||
read:
|
||||
cron: '0 0/15 * * * ?'
|
||||
|
||||
spring:
|
||||
rabbitmq:
|
||||
virtual-host: ${RABBITMQ_VIRTUAL_HOST:dc3}
|
||||
host: ${RABBITMQ_HOST:dc3-rabbitmq}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:dc3}
|
||||
password: ${RABBITMQ_PASSWORD:dc3dc3dc3}
|
||||
@@ -0,0 +1,36 @@
|
||||
#
|
||||
# Copyright 2016-present the IoT DC3 original author or authors.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Test environment configuration will overwrite the content configured in application-*.yml
|
||||
dc3:
|
||||
driver:
|
||||
schedule:
|
||||
# Read data regularly
|
||||
read:
|
||||
cron: '0/5 * * * * ?'
|
||||
|
||||
spring:
|
||||
rabbitmq:
|
||||
virtual-host: ${RABBITMQ_VIRTUAL_HOST:dc3}
|
||||
host: ${RABBITMQ_HOST:dc3-rabbitmq}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:dc3}
|
||||
password: ${RABBITMQ_PASSWORD:dc3dc3dc3}
|
||||
|
||||
logging:
|
||||
level:
|
||||
io.github.pnoker: DEBUG
|
||||
@@ -0,0 +1,112 @@
|
||||
#
|
||||
# Copyright 2016-present the IoT DC3 original author or authors.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
dc3:
|
||||
driver:
|
||||
tenant: default
|
||||
name: Modbus RTU Driver
|
||||
code: ModbusRtuDriver
|
||||
type: DRIVER_CLIENT
|
||||
remark: @project.description@
|
||||
schedule:
|
||||
# Read data regularly
|
||||
read:
|
||||
enable: true
|
||||
cron: '0/30 * * * * ?'
|
||||
custom:
|
||||
enable: true
|
||||
cron: '0/5 * * * * ?'
|
||||
health:
|
||||
device:
|
||||
enable: true
|
||||
cron: '0/15 * * * * ?'
|
||||
timeout: 45
|
||||
timeout-unit: SECONDS
|
||||
driver-attribute:
|
||||
- attribute-name: Port
|
||||
attribute-code: port
|
||||
attribute-type-flag: STRING
|
||||
default-value: /dev/ttyUSB0
|
||||
remark: Serial port name (e.g. /dev/ttyUSB0, COM3)
|
||||
- attribute-name: Baud Rate
|
||||
attribute-code: baudRate
|
||||
attribute-type-flag: INT
|
||||
default-value: 9600
|
||||
remark: Serial baud rate (e.g. 9600, 19200, 115200)
|
||||
- attribute-name: Data Bits
|
||||
attribute-code: dataBits
|
||||
attribute-type-flag: INT
|
||||
default-value: 8
|
||||
remark: Data bits (7 or 8)
|
||||
- attribute-name: Stop Bits
|
||||
attribute-code: stopBits
|
||||
attribute-type-flag: INT
|
||||
default-value: 1
|
||||
remark: Stop bits (1 or 2)
|
||||
- attribute-name: Parity
|
||||
attribute-code: parity
|
||||
attribute-type-flag: INT
|
||||
default-value: 0
|
||||
remark: Parity (0=None, 1=Odd, 2=Even, 3=Mark, 4=Space)
|
||||
point-attribute:
|
||||
- attribute-name: Slave ID
|
||||
attribute-code: slaveId
|
||||
attribute-type-flag: INT
|
||||
default-value: 1
|
||||
remark: Modbus slave unit ID
|
||||
- attribute-name: Function Code
|
||||
attribute-code: functionCode
|
||||
attribute-type-flag: INT
|
||||
default-value: 1
|
||||
remark: Modbus function code [1, 2, 3, 4]
|
||||
- attribute-name: Offset
|
||||
attribute-code: offset
|
||||
attribute-type-flag: INT
|
||||
default-value: 0
|
||||
remark: Register or coil address offset
|
||||
command-attribute:
|
||||
- attribute-name: Slave ID
|
||||
attribute-code: slaveId
|
||||
attribute-type-flag: INT
|
||||
default-value: 1
|
||||
remark: Modbus slave unit ID
|
||||
- attribute-name: Function Code
|
||||
attribute-code: functionCode
|
||||
attribute-type-flag: INT
|
||||
default-value: 6
|
||||
remark: Modbus write function code [5, 6, 15, 16]
|
||||
- attribute-name: Offset
|
||||
attribute-code: offset
|
||||
attribute-type-flag: INT
|
||||
default-value: 0
|
||||
remark: Register or coil address offset
|
||||
- attribute-name: Value Template
|
||||
attribute-code: valueTemplate
|
||||
attribute-type-flag: STRING
|
||||
default-value: '${value}'
|
||||
remark: Value template rendered with command params
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: @project.artifactId@
|
||||
profiles:
|
||||
active:
|
||||
- ${NODE_ENV:dev}
|
||||
|
||||
logging:
|
||||
file:
|
||||
name: dc3/logs/driver/modbus-rtu/${spring.application.name}.log
|
||||
@@ -32,6 +32,12 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Modbus Protocol -->
|
||||
<dependency>
|
||||
<groupId>com.infiniteautomation</groupId>
|
||||
<artifactId>modbus4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
-635
@@ -1,635 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.base.RangeAndOffset;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.IllegalDataAddressException;
|
||||
import com.serotonin.modbus4j.exception.ModbusIdException;
|
||||
import com.serotonin.modbus4j.locator.BaseLocator;
|
||||
import com.serotonin.modbus4j.locator.NumericLocator;
|
||||
import com.serotonin.modbus4j.locator.StringLocator;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* BasicProcessImage class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class BasicProcessImage implements ProcessImage {
|
||||
|
||||
private final int slaveId;
|
||||
|
||||
private final Map<Integer, Boolean> coils = new HashMap<>();
|
||||
|
||||
private final Map<Integer, Boolean> inputs = new HashMap<>();
|
||||
|
||||
private final Map<Integer, Short> holdingRegisters = new HashMap<>();
|
||||
|
||||
private final Map<Integer, Short> inputRegisters = new HashMap<>();
|
||||
|
||||
private final List<ProcessImageListener> writeListeners = new ArrayList<>();
|
||||
|
||||
private boolean allowInvalidAddress = false;
|
||||
|
||||
private short invalidAddressValue = 0;
|
||||
|
||||
private byte exceptionStatus;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for BasicProcessImage.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public BasicProcessImage(int slaveId) {
|
||||
ModbusUtils.validateSlaveId(slaveId, false);
|
||||
this.slaveId = slaveId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlaveId() {
|
||||
return slaveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* addListener.
|
||||
* </p>
|
||||
*
|
||||
* @param l a {@link ProcessImageListener} object.
|
||||
*/
|
||||
public synchronized void addListener(ProcessImageListener l) {
|
||||
writeListeners.add(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* removeListener.
|
||||
* </p>
|
||||
*
|
||||
* @param l a {@link ProcessImageListener} object.
|
||||
*/
|
||||
public synchronized void removeListener(ProcessImageListener l) {
|
||||
writeListeners.remove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isAllowInvalidAddress.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isAllowInvalidAddress() {
|
||||
return allowInvalidAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>allowInvalidAddress</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param allowInvalidAddress a boolean.
|
||||
*/
|
||||
public void setAllowInvalidAddress(boolean allowInvalidAddress) {
|
||||
this.allowInvalidAddress = allowInvalidAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>invalidAddressValue</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a short.
|
||||
*/
|
||||
public short getInvalidAddressValue() {
|
||||
return invalidAddressValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>invalidAddressValue</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param invalidAddressValue a short.
|
||||
*/
|
||||
public void setInvalidAddressValue(short invalidAddressValue) {
|
||||
this.invalidAddressValue = invalidAddressValue;
|
||||
}
|
||||
|
||||
//
|
||||
// /
|
||||
// / Additional convenience methods.
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setBinary.
|
||||
* </p>
|
||||
*
|
||||
* @param registerId a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
public void setBinary(int registerId, boolean value) {
|
||||
RangeAndOffset rao = new RangeAndOffset(registerId);
|
||||
setBinary(rao.getRange(), rao.getOffset(), value);
|
||||
}
|
||||
|
||||
//
|
||||
// Binaries
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setBinary.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
public void setBinary(int range, int offset, boolean value) {
|
||||
if (range == RegisterRange.COIL_STATUS)
|
||||
setCoil(offset, value);
|
||||
else if (range == RegisterRange.INPUT_STATUS)
|
||||
setInput(offset, value);
|
||||
else
|
||||
throw new ModbusIdException("Invalid range to set binary: " + range);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setNumeric.
|
||||
* </p>
|
||||
*
|
||||
* @param registerId a int.
|
||||
* @param dataType a int.
|
||||
* @param value a {@link Number} object.
|
||||
*/
|
||||
public synchronized void setNumeric(int registerId, int dataType, Number value) {
|
||||
RangeAndOffset rao = new RangeAndOffset(registerId);
|
||||
setNumeric(rao.getRange(), rao.getOffset(), dataType, value);
|
||||
}
|
||||
|
||||
//
|
||||
// Numerics
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setNumeric.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param value a {@link Number} object.
|
||||
*/
|
||||
public synchronized void setNumeric(int range, int offset, int dataType, Number value) {
|
||||
short[] registers = new NumericLocator(slaveId, range, offset, dataType).valueToShorts(value);
|
||||
|
||||
// Write the value.
|
||||
if (range == RegisterRange.HOLDING_REGISTER)
|
||||
setHoldingRegister(offset, registers);
|
||||
else if (range == RegisterRange.INPUT_REGISTER)
|
||||
setInputRegister(offset, registers);
|
||||
else
|
||||
throw new ModbusIdException("Invalid range to set register: " + range);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setString.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param registerCount a int.
|
||||
* @param s a {@link String} object.
|
||||
*/
|
||||
public synchronized void setString(int range, int offset, int dataType, int registerCount, String s) {
|
||||
setString(range, offset, dataType, registerCount, StringLocator.ASCII, s);
|
||||
}
|
||||
|
||||
//
|
||||
// Strings
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setString.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param registerCount a int.
|
||||
* @param charset a {@link Charset} object.
|
||||
* @param s a {@link String} object.
|
||||
*/
|
||||
public synchronized void setString(int range, int offset, int dataType, int registerCount, Charset charset,
|
||||
String s) {
|
||||
short[] registers = new StringLocator(slaveId, range, offset, dataType, registerCount, charset)
|
||||
.valueToShorts(s);
|
||||
|
||||
// Write the value.
|
||||
if (range == RegisterRange.HOLDING_REGISTER)
|
||||
setHoldingRegister(offset, registers);
|
||||
else if (range == RegisterRange.INPUT_REGISTER)
|
||||
setInputRegister(offset, registers);
|
||||
else
|
||||
throw new ModbusIdException("Invalid range to set register: " + range);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setHoldingRegister.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param registers an array of {@link short} objects.
|
||||
*/
|
||||
public synchronized void setHoldingRegister(int offset, short[] registers) {
|
||||
validateOffset(offset);
|
||||
for (int i = 0; i < registers.length; i++)
|
||||
setHoldingRegister(offset + i, registers[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setInputRegister.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param registers an array of {@link short} objects.
|
||||
*/
|
||||
public synchronized void setInputRegister(int offset, short[] registers) {
|
||||
validateOffset(offset);
|
||||
for (int i = 0; i < registers.length; i++)
|
||||
setInputRegister(offset + i, registers[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setBit.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
public synchronized void setBit(int range, int offset, int bit, boolean value) {
|
||||
if (range == RegisterRange.HOLDING_REGISTER)
|
||||
setHoldingRegisterBit(offset, bit, value);
|
||||
else if (range == RegisterRange.INPUT_REGISTER)
|
||||
setInputRegisterBit(offset, bit, value);
|
||||
else
|
||||
throw new ModbusIdException("Invalid range to set register: " + range);
|
||||
}
|
||||
|
||||
//
|
||||
// Bits
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setHoldingRegisterBit.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
public synchronized void setHoldingRegisterBit(int offset, int bit, boolean value) {
|
||||
validateBit(bit);
|
||||
short s;
|
||||
try {
|
||||
s = getHoldingRegister(offset);
|
||||
} catch (IllegalDataAddressException e) {
|
||||
s = 0;
|
||||
}
|
||||
setHoldingRegister(offset, setBit(s, bit, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setInputRegisterBit.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
public synchronized void setInputRegisterBit(int offset, int bit, boolean value) {
|
||||
validateBit(bit);
|
||||
short s;
|
||||
try {
|
||||
s = getInputRegister(offset);
|
||||
} catch (IllegalDataAddressException e) {
|
||||
s = 0;
|
||||
}
|
||||
setInputRegister(offset, setBit(s, bit, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getBit.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @return a boolean.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public boolean getBit(int range, int offset, int bit) throws IllegalDataAddressException {
|
||||
if (range == RegisterRange.HOLDING_REGISTER)
|
||||
return getHoldingRegisterBit(offset, bit);
|
||||
if (range == RegisterRange.INPUT_REGISTER)
|
||||
return getInputRegisterBit(offset, bit);
|
||||
throw new ModbusIdException("Invalid range to get register: " + range);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getHoldingRegisterBit.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @return a boolean.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public boolean getHoldingRegisterBit(int offset, int bit) throws IllegalDataAddressException {
|
||||
validateBit(bit);
|
||||
return getBit(getHoldingRegister(offset), bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getInputRegisterBit.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @return a boolean.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public boolean getInputRegisterBit(int offset, int bit) throws IllegalDataAddressException {
|
||||
validateBit(bit);
|
||||
return getBit(getInputRegister(offset), bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getNumeric.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @return a {@link Number} object.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public Number getNumeric(int range, int offset, int dataType) throws IllegalDataAddressException {
|
||||
return getRegister(new NumericLocator(slaveId, range, offset, dataType));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getString.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param registerCount a int.
|
||||
* @return a {@link String} object.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public String getString(int range, int offset, int dataType, int registerCount) throws IllegalDataAddressException {
|
||||
return getRegister(new StringLocator(slaveId, range, offset, dataType, registerCount, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getString.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param registerCount a int.
|
||||
* @param charset a {@link Charset} object.
|
||||
* @return a {@link String} object.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public String getString(int range, int offset, int dataType, int registerCount, Charset charset)
|
||||
throws IllegalDataAddressException {
|
||||
return getRegister(new StringLocator(slaveId, range, offset, dataType, registerCount, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getRegister.
|
||||
* </p>
|
||||
*
|
||||
* @param locator a {@link BaseLocator} object.
|
||||
* @param <T> a T object.
|
||||
* @return a T object.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
public synchronized <T> T getRegister(BaseLocator<T> locator) throws IllegalDataAddressException {
|
||||
int words = locator.getRegisterCount();
|
||||
byte[] b = new byte[locator.getRegisterCount() * 2];
|
||||
for (int i = 0; i < words; i++) {
|
||||
short s;
|
||||
if (locator.getRange() == RegisterRange.INPUT_REGISTER)
|
||||
s = getInputRegister(locator.getOffset() + i);
|
||||
else if (locator.getRange() == RegisterRange.HOLDING_REGISTER)
|
||||
s = getHoldingRegister(locator.getOffset() + i);
|
||||
else if (allowInvalidAddress)
|
||||
s = invalidAddressValue;
|
||||
else
|
||||
throw new IllegalDataAddressException();
|
||||
b[i * 2] = ModbusUtils.toByte(s, true);
|
||||
b[i * 2 + 1] = ModbusUtils.toByte(s, false);
|
||||
}
|
||||
|
||||
return locator.bytesToValueRealOffset(b, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean getCoil(int offset) throws IllegalDataAddressException {
|
||||
return getBoolean(offset, coils);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// ProcessImage interface
|
||||
//
|
||||
|
||||
//
|
||||
// Coils
|
||||
|
||||
@Override
|
||||
public synchronized void setCoil(int offset, boolean value) {
|
||||
validateOffset(offset);
|
||||
coils.put(offset, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void writeCoil(int offset, boolean value) throws IllegalDataAddressException {
|
||||
boolean old = getBoolean(offset, coils);
|
||||
setCoil(offset, value);
|
||||
|
||||
for (ProcessImageListener l : writeListeners)
|
||||
l.coilWrite(offset, old, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean getInput(int offset) throws IllegalDataAddressException {
|
||||
return getBoolean(offset, inputs);
|
||||
}
|
||||
|
||||
//
|
||||
// Inputs
|
||||
|
||||
@Override
|
||||
public synchronized void setInput(int offset, boolean value) {
|
||||
validateOffset(offset);
|
||||
inputs.put(offset, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized short getHoldingRegister(int offset) throws IllegalDataAddressException {
|
||||
return getShort(offset, holdingRegisters);
|
||||
}
|
||||
|
||||
//
|
||||
// Holding registers
|
||||
|
||||
@Override
|
||||
public synchronized void setHoldingRegister(int offset, short value) {
|
||||
validateOffset(offset);
|
||||
holdingRegisters.put(offset, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void writeHoldingRegister(int offset, short value) throws IllegalDataAddressException {
|
||||
short old = getShort(offset, holdingRegisters);
|
||||
setHoldingRegister(offset, value);
|
||||
|
||||
for (ProcessImageListener l : writeListeners)
|
||||
l.holdingRegisterWrite(offset, old, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized short getInputRegister(int offset) throws IllegalDataAddressException {
|
||||
return getShort(offset, inputRegisters);
|
||||
}
|
||||
|
||||
//
|
||||
// Input registers
|
||||
|
||||
@Override
|
||||
public synchronized void setInputRegister(int offset, short value) {
|
||||
validateOffset(offset);
|
||||
inputRegisters.put(offset, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getExceptionStatus() {
|
||||
return exceptionStatus;
|
||||
}
|
||||
|
||||
//
|
||||
// Exception status
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>exceptionStatus</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param exceptionStatus a byte.
|
||||
*/
|
||||
public void setExceptionStatus(byte exceptionStatus) {
|
||||
this.exceptionStatus = exceptionStatus;
|
||||
}
|
||||
|
||||
//
|
||||
// Report slave id
|
||||
|
||||
@Override
|
||||
public byte[] getReportSlaveIdData() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Private
|
||||
//
|
||||
private short getShort(int offset, Map<Integer, Short> map) throws IllegalDataAddressException {
|
||||
Short value = map.get(offset);
|
||||
if (value == null) {
|
||||
if (allowInvalidAddress)
|
||||
return invalidAddressValue;
|
||||
throw new IllegalDataAddressException();
|
||||
}
|
||||
return value.shortValue();
|
||||
}
|
||||
|
||||
private boolean getBoolean(int offset, Map<Integer, Boolean> map) throws IllegalDataAddressException {
|
||||
Boolean value = map.get(offset);
|
||||
if (value == null) {
|
||||
if (allowInvalidAddress)
|
||||
return false;
|
||||
throw new IllegalDataAddressException();
|
||||
}
|
||||
return value.booleanValue();
|
||||
}
|
||||
|
||||
private void validateOffset(int offset) {
|
||||
if (offset < 0 || offset > 65535)
|
||||
throw new ModbusIdException("Invalid offset: " + offset);
|
||||
}
|
||||
|
||||
private void validateBit(int bit) {
|
||||
if (bit < 0 || bit > 15)
|
||||
throw new ModbusIdException("Invalid bit: " + bit);
|
||||
}
|
||||
|
||||
private short setBit(short s, int bit, boolean value) {
|
||||
return (short) (s | ((value ? 1 : 0) << bit));
|
||||
}
|
||||
|
||||
private boolean getBit(short s, int bit) {
|
||||
return ((s >> bit) & 0x1) == 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.base.KeyedModbusLocator;
|
||||
import com.serotonin.modbus4j.base.ReadFunctionGroup;
|
||||
import com.serotonin.modbus4j.base.SlaveAndRange;
|
||||
import com.serotonin.modbus4j.locator.BaseLocator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class for defining the information required to obtain in a batch.
|
||||
* <p>
|
||||
* The generic parameterization represents the class of the key that will be used to find
|
||||
* the results in the BatchRead object. Typically String would be used, but any Object is
|
||||
* valid.
|
||||
* <p>
|
||||
* Some modbus devices have non-contiguous sets of values within a single register range.
|
||||
* These gaps between values may cause the device to return error responses if a request
|
||||
* attempts to read them. In spite of this, because it is generally more efficient to read
|
||||
* a set of values with a single request, the batch read by default will assume that no
|
||||
* such error responses will be returned. If your batch request results in such errors, it
|
||||
* is recommended that you separate the offending request to a separate batch read object,
|
||||
* or you can use the "contiguous requests" setting which causes requests to be
|
||||
* partitioned into only contiguous sets.
|
||||
*
|
||||
* @param <K> - Type of read
|
||||
* @author mlohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class BatchRead<K> {
|
||||
|
||||
private final List<KeyedModbusLocator<K>> requestValues = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* See documentation above.
|
||||
*/
|
||||
private boolean contiguousRequests = false;
|
||||
|
||||
/**
|
||||
* If this value is false, any error response received will cause an exception to be
|
||||
* thrown, and the entire batch to be aborted (unless exceptionsInResults is true -
|
||||
* see below). If set to true, error responses will be set as the result of all
|
||||
* affected locators and the entire batch will be attempted with no such exceptions
|
||||
* thrown.
|
||||
*/
|
||||
private boolean errorsInResults = false;
|
||||
|
||||
/**
|
||||
* If this value is false, any exceptions thrown will cause the entire batch to be
|
||||
* aborted. If set to true, the exception will be set as the result of all affected
|
||||
* locators and the entire batch will be attempted with no such exceptions thrown.
|
||||
*/
|
||||
private boolean exceptionsInResults = false;
|
||||
|
||||
/**
|
||||
* A batch may be split into an arbitrary number of individual Modbus requests, and so
|
||||
* a given batch may take an arbitrary amount of time to complete. The cancel field is
|
||||
* provided to allow the batch to be cancelled.
|
||||
*/
|
||||
private boolean cancel;
|
||||
|
||||
/**
|
||||
* This is what the data looks like after partitioning.
|
||||
*/
|
||||
private List<ReadFunctionGroup<K>> functionGroups;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isContiguousRequests.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isContiguousRequests() {
|
||||
return contiguousRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>contiguousRequests</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param contiguousRequests a boolean.
|
||||
*/
|
||||
public void setContiguousRequests(boolean contiguousRequests) {
|
||||
this.contiguousRequests = contiguousRequests;
|
||||
functionGroups = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isErrorsInResults.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isErrorsInResults() {
|
||||
return errorsInResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>errorsInResults</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param errorsInResults a boolean.
|
||||
*/
|
||||
public void setErrorsInResults(boolean errorsInResults) {
|
||||
this.errorsInResults = errorsInResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isExceptionsInResults.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isExceptionsInResults() {
|
||||
return exceptionsInResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>exceptionsInResults</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param exceptionsInResults a boolean.
|
||||
*/
|
||||
public void setExceptionsInResults(boolean exceptionsInResults) {
|
||||
this.exceptionsInResults = exceptionsInResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getReadFunctionGroups.
|
||||
* </p>
|
||||
*
|
||||
* @param master a {@link ModbusMaster} object.
|
||||
* @return a {@link List} object.
|
||||
*/
|
||||
public List<ReadFunctionGroup<K>> getReadFunctionGroups(ModbusMaster master) {
|
||||
if (functionGroups == null)
|
||||
doPartition(master);
|
||||
return functionGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* addLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param id a K object.
|
||||
* @param locator a {@link BaseLocator} object.
|
||||
*/
|
||||
public void addLocator(K id, BaseLocator<?> locator) {
|
||||
addLocator(new KeyedModbusLocator<>(id, locator));
|
||||
}
|
||||
|
||||
private void addLocator(KeyedModbusLocator<K> locator) {
|
||||
requestValues.add(locator);
|
||||
functionGroups = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isCancel.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>cancel</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param cancel a boolean.
|
||||
*/
|
||||
public void setCancel(boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Private stuff
|
||||
//
|
||||
private void doPartition(ModbusMaster master) {
|
||||
Map<SlaveAndRange, List<KeyedModbusLocator<K>>> slaveRangeBatch = new HashMap<>();
|
||||
|
||||
// Separate the batch into slave ids and read functions.
|
||||
List<KeyedModbusLocator<K>> functions;
|
||||
for (KeyedModbusLocator<K> locator : requestValues) {
|
||||
// Find the function list for this slave and range. Create it if necessary.
|
||||
functions = slaveRangeBatch.computeIfAbsent(locator.getSlaveAndRange(), k -> new ArrayList<>());
|
||||
|
||||
// Add this locator to the function list.
|
||||
functions.add(locator);
|
||||
}
|
||||
|
||||
// Now that we have locators grouped into slave and function, check each read
|
||||
// function group and break into
|
||||
// parts as necessary.
|
||||
Collection<List<KeyedModbusLocator<K>>> functionLocatorLists = slaveRangeBatch.values();
|
||||
FunctionLocatorComparator comparator = new FunctionLocatorComparator();
|
||||
functionGroups = new ArrayList<>();
|
||||
for (List<KeyedModbusLocator<K>> functionLocatorList : functionLocatorLists) {
|
||||
// Sort the list by offset.
|
||||
Collections.sort(functionLocatorList, comparator);
|
||||
|
||||
// Break into parts by excessive request length. Remember the max item count
|
||||
// that we can ask for, for
|
||||
// this function
|
||||
int maxReadCount = master.getMaxReadCount(functionLocatorList.get(0).getSlaveAndRange().getRange());
|
||||
|
||||
// Create the request groups.
|
||||
createRequestGroups(functionGroups, functionLocatorList, maxReadCount);
|
||||
// System.out.println("requests: " + functionGroups.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We aren't trying to do anything fancy here, like some kind of artificial optimal
|
||||
* group for performance or anything. We pretty much just try to fit as many locators
|
||||
* as possible into a single valid request, and then move on.
|
||||
* <p>
|
||||
* This method assumes the locators have already been sorted by start offset.
|
||||
*/
|
||||
private void createRequestGroups(List<ReadFunctionGroup<K>> functionGroups, List<KeyedModbusLocator<K>> locators,
|
||||
int maxCount) {
|
||||
ReadFunctionGroup<K> functionGroup;
|
||||
KeyedModbusLocator<K> locator;
|
||||
int index;
|
||||
int endOffset;
|
||||
// Loop for creation of groups.
|
||||
while (locators.size() > 0) {
|
||||
functionGroup = new ReadFunctionGroup<>(locators.remove(0));
|
||||
functionGroups.add(functionGroup);
|
||||
endOffset = functionGroup.getStartOffset() + maxCount - 1;
|
||||
|
||||
// Loop for adding locators to the current group
|
||||
index = 0;
|
||||
while (locators.size() > index) {
|
||||
locator = locators.get(index);
|
||||
boolean added = false;
|
||||
|
||||
if (locator.getEndOffset() <= endOffset) {
|
||||
if (contiguousRequests) {
|
||||
// The locator must at least abut the other locators in the group.
|
||||
if (locator.getOffset() <= functionGroup.getEndOffset() + 1) {
|
||||
functionGroup.add(locators.remove(index));
|
||||
added = true;
|
||||
}
|
||||
} else {
|
||||
functionGroup.add(locators.remove(index));
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
// This locator doesn't fit inside the current function...
|
||||
if (locator.getOffset() > endOffset)
|
||||
// ... and since the list is sorted by offset, no other locators
|
||||
// can either, so quit the loop.
|
||||
break;
|
||||
|
||||
// ... but there still may be other locators that can, so increment
|
||||
// the index
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionLocatorComparator implements Comparator<KeyedModbusLocator<K>> {
|
||||
|
||||
@Override
|
||||
public int compare(KeyedModbusLocator<K> ml1, KeyedModbusLocator<K> ml2) {
|
||||
return ml1.getOffset() - ml2.getOffset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* BatchResults class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class BatchResults<K> {
|
||||
|
||||
private final Map<K, Object> data = new HashMap<>();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* addResult.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @param value a {@link Object} object.
|
||||
*/
|
||||
public void addResult(K key, Object value) {
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getValue.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @return a {@link Object} object.
|
||||
*/
|
||||
public Object getValue(K key) {
|
||||
return data.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getIntValue.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @return a {@link Integer} object.
|
||||
*/
|
||||
public Integer getIntValue(K key) {
|
||||
return (Integer) getValue(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getLongValue.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @return a {@link Long} object.
|
||||
*/
|
||||
public Long getLongValue(K key) {
|
||||
return (Long) getValue(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getDoubleValue.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @return a {@link Double} object.
|
||||
*/
|
||||
public Double getDoubleValue(K key) {
|
||||
return (Double) getValue(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getFloatValue.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @return a {@link Float} object.
|
||||
*/
|
||||
public Float getFloatValue(K key) {
|
||||
return (Float) getValue(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.code.ExceptionCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ExceptionResult class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ExceptionResult {
|
||||
|
||||
private final byte exceptionCode;
|
||||
|
||||
private final String exceptionMessage;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ExceptionResult.
|
||||
* </p>
|
||||
*
|
||||
* @param exceptionCode a byte.
|
||||
*/
|
||||
public ExceptionResult(byte exceptionCode) {
|
||||
this.exceptionCode = exceptionCode;
|
||||
exceptionMessage = ExceptionCode.getExceptionMessage(exceptionCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>exceptionCode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a byte.
|
||||
*/
|
||||
public byte getExceptionCode() {
|
||||
return exceptionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>exceptionMessage</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link String} object.
|
||||
*/
|
||||
public String getExceptionMessage() {
|
||||
return exceptionMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.sero.messaging.DefaultMessagingExceptionHandler;
|
||||
import com.serotonin.modbus4j.sero.messaging.MessagingExceptionHandler;
|
||||
|
||||
/**
|
||||
* Base level for masters and slaves/listeners
|
||||
* <p>
|
||||
* TODO: - handle echoing in RS485
|
||||
*
|
||||
* @author mlohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class Modbus {
|
||||
|
||||
/**
|
||||
* Constant <code>DEFAULT_MAX_READ_BIT_COUNT=2000</code>
|
||||
*/
|
||||
public static final int DEFAULT_MAX_READ_BIT_COUNT = 2000;
|
||||
|
||||
/**
|
||||
* Constant <code>DEFAULT_MAX_READ_REGISTER_COUNT=125</code>
|
||||
*/
|
||||
public static final int DEFAULT_MAX_READ_REGISTER_COUNT = 125;
|
||||
|
||||
/**
|
||||
* Constant <code>DEFAULT_MAX_WRITE_REGISTER_COUNT=120</code>
|
||||
*/
|
||||
public static final int DEFAULT_MAX_WRITE_REGISTER_COUNT = 120;
|
||||
|
||||
private MessagingExceptionHandler exceptionHandler = new DefaultMessagingExceptionHandler();
|
||||
|
||||
private int maxReadBitCount = DEFAULT_MAX_READ_BIT_COUNT;
|
||||
|
||||
private int maxReadRegisterCount = DEFAULT_MAX_READ_REGISTER_COUNT;
|
||||
|
||||
private int maxWriteRegisterCount = DEFAULT_MAX_WRITE_REGISTER_COUNT;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMaxReadCount.
|
||||
* </p>
|
||||
*
|
||||
* @param registerRange a int.
|
||||
* @return a int.
|
||||
*/
|
||||
public int getMaxReadCount(int registerRange) {
|
||||
switch (registerRange) {
|
||||
case RegisterRange.COIL_STATUS:
|
||||
case RegisterRange.INPUT_STATUS:
|
||||
return maxReadBitCount;
|
||||
case RegisterRange.HOLDING_REGISTER:
|
||||
case RegisterRange.INPUT_REGISTER:
|
||||
return maxReadRegisterCount;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateNumberOfBits.
|
||||
* </p>
|
||||
*
|
||||
* @param bits a int.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public void validateNumberOfBits(int bits) throws ModbusTransportException {
|
||||
if (bits < 1 || bits > maxReadBitCount)
|
||||
throw new ModbusTransportException("Invalid number of bits: " + bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateNumberOfRegisters.
|
||||
* </p>
|
||||
*
|
||||
* @param registers a int.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public void validateNumberOfRegisters(int registers) throws ModbusTransportException {
|
||||
if (registers < 1 || registers > maxReadRegisterCount)
|
||||
throw new ModbusTransportException("Invalid number of registers: " + registers);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>exceptionHandler</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link MessagingExceptionHandler} object.
|
||||
*/
|
||||
public MessagingExceptionHandler getExceptionHandler() {
|
||||
return exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>exceptionHandler</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param exceptionHandler a {@link MessagingExceptionHandler} object.
|
||||
*/
|
||||
public void setExceptionHandler(MessagingExceptionHandler exceptionHandler) {
|
||||
if (exceptionHandler == null)
|
||||
this.exceptionHandler = new DefaultMessagingExceptionHandler();
|
||||
else
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>maxReadBitCount</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getMaxReadBitCount() {
|
||||
return maxReadBitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>maxReadBitCount</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param maxReadBitCount a int.
|
||||
*/
|
||||
public void setMaxReadBitCount(int maxReadBitCount) {
|
||||
this.maxReadBitCount = maxReadBitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>maxReadRegisterCount</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getMaxReadRegisterCount() {
|
||||
return maxReadRegisterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>maxReadRegisterCount</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param maxReadRegisterCount a int.
|
||||
*/
|
||||
public void setMaxReadRegisterCount(int maxReadRegisterCount) {
|
||||
this.maxReadRegisterCount = maxReadRegisterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>maxWriteRegisterCount</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getMaxWriteRegisterCount() {
|
||||
return maxWriteRegisterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>maxWriteRegisterCount</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param maxWriteRegisterCount a int.
|
||||
*/
|
||||
public void setMaxWriteRegisterCount(int maxWriteRegisterCount) {
|
||||
this.maxWriteRegisterCount = maxWriteRegisterCount;
|
||||
}
|
||||
|
||||
}
|
||||
-215
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.ModbusIdException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.ip.IpParameters;
|
||||
import com.serotonin.modbus4j.ip.listener.TcpListener;
|
||||
import com.serotonin.modbus4j.ip.tcp.TcpMaster;
|
||||
import com.serotonin.modbus4j.ip.tcp.TcpSlave;
|
||||
import com.serotonin.modbus4j.ip.udp.UdpMaster;
|
||||
import com.serotonin.modbus4j.ip.udp.UdpSlave;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadCoilsRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadDiscreteInputsRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadInputRegistersRequest;
|
||||
import com.serotonin.modbus4j.serial.SerialPortWrapper;
|
||||
import com.serotonin.modbus4j.serial.ascii.AsciiMaster;
|
||||
import com.serotonin.modbus4j.serial.ascii.AsciiSlave;
|
||||
import com.serotonin.modbus4j.serial.rtu.RtuMaster;
|
||||
import com.serotonin.modbus4j.serial.rtu.RtuSlave;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ModbusFactory class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ModbusFactory {
|
||||
|
||||
//
|
||||
// Modbus masters
|
||||
//
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createRtuMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param wrapper a {@link SerialPortWrapper} object.
|
||||
* @return a {@link ModbusMaster} object.
|
||||
*/
|
||||
public ModbusMaster createRtuMaster(SerialPortWrapper wrapper) {
|
||||
return new RtuMaster(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createAsciiMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param wrapper a {@link SerialPortWrapper} object.
|
||||
* @return a {@link ModbusMaster} object.
|
||||
*/
|
||||
public ModbusMaster createAsciiMaster(SerialPortWrapper wrapper) {
|
||||
return new AsciiMaster(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createTcpMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
* @return a {@link ModbusMaster} object.
|
||||
*/
|
||||
public ModbusMaster createTcpMaster(IpParameters params, boolean keepAlive) {
|
||||
return new TcpMaster(params, keepAlive);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createTcpMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
* @param lingerTime an Integer.
|
||||
* @return a {@link ModbusMaster} object.
|
||||
*/
|
||||
public ModbusMaster createTcpMaster(IpParameters params, boolean keepAlive, Integer lingerTime) {
|
||||
return new TcpMaster(params, keepAlive, lingerTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createUdpMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @return a {@link ModbusMaster} object.
|
||||
*/
|
||||
public ModbusMaster createUdpMaster(IpParameters params) {
|
||||
return new UdpMaster(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createTcpListener.
|
||||
* </p>
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @return a {@link ModbusMaster} object.
|
||||
*/
|
||||
public ModbusMaster createTcpListener(IpParameters params) {
|
||||
return new TcpListener(params);
|
||||
}
|
||||
|
||||
//
|
||||
// Modbus slaves
|
||||
//
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createRtuSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param wrapper a {@link SerialPortWrapper} object.
|
||||
* @return a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public ModbusSlaveSet createRtuSlave(SerialPortWrapper wrapper) {
|
||||
return new RtuSlave(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createAsciiSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param wrapper a {@link SerialPortWrapper} object.
|
||||
* @return a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public ModbusSlaveSet createAsciiSlave(SerialPortWrapper wrapper) {
|
||||
return new AsciiSlave(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createTcpSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param encapsulated a boolean.
|
||||
* @return a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public ModbusSlaveSet createTcpSlave(boolean encapsulated) {
|
||||
return new TcpSlave(encapsulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createUdpSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param encapsulated a boolean.
|
||||
* @return a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public ModbusSlaveSet createUdpSlave(boolean encapsulated) {
|
||||
return new UdpSlave(encapsulated);
|
||||
}
|
||||
|
||||
//
|
||||
// Modbus requests
|
||||
//
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createReadRequest.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param length a int.
|
||||
* @return a {@link ModbusRequest} object.
|
||||
* @throws ModbusTransportException if any.
|
||||
* @throws ModbusIdException if any.
|
||||
*/
|
||||
public ModbusRequest createReadRequest(int slaveId, int range, int offset, int length)
|
||||
throws ModbusTransportException, ModbusIdException {
|
||||
ModbusUtils.validateRegisterRange(range);
|
||||
|
||||
if (range == RegisterRange.COIL_STATUS)
|
||||
return new ReadCoilsRequest(slaveId, offset, length);
|
||||
|
||||
if (range == RegisterRange.INPUT_STATUS)
|
||||
return new ReadDiscreteInputsRequest(slaveId, offset, length);
|
||||
|
||||
if (range == RegisterRange.INPUT_REGISTER)
|
||||
return new ReadInputRegistersRequest(slaveId, offset, length);
|
||||
|
||||
return new ReadHoldingRegistersRequest(slaveId, offset, length);
|
||||
}
|
||||
|
||||
}
|
||||
-656
@@ -1,656 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.base.KeyedModbusLocator;
|
||||
import com.serotonin.modbus4j.base.ReadFunctionGroup;
|
||||
import com.serotonin.modbus4j.base.SlaveProfile;
|
||||
import com.serotonin.modbus4j.code.DataType;
|
||||
import com.serotonin.modbus4j.code.ExceptionCode;
|
||||
import com.serotonin.modbus4j.code.FunctionCode;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.ErrorResponseException;
|
||||
import com.serotonin.modbus4j.exception.InvalidDataConversionException;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.locator.BaseLocator;
|
||||
import com.serotonin.modbus4j.locator.BinaryLocator;
|
||||
import com.serotonin.modbus4j.locator.NumericLocator;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.msg.ReadCoilsRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadDiscreteInputsRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadInputRegistersRequest;
|
||||
import com.serotonin.modbus4j.msg.ReadResponse;
|
||||
import com.serotonin.modbus4j.msg.WriteCoilRequest;
|
||||
import com.serotonin.modbus4j.msg.WriteCoilsRequest;
|
||||
import com.serotonin.modbus4j.msg.WriteMaskRegisterRequest;
|
||||
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
|
||||
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
|
||||
import com.serotonin.modbus4j.sero.epoll.InputStreamEPollWrapper;
|
||||
import com.serotonin.modbus4j.sero.log.BaseIOLog;
|
||||
import com.serotonin.modbus4j.sero.messaging.MessageControl;
|
||||
import com.serotonin.modbus4j.sero.util.ArrayUtils;
|
||||
import com.serotonin.modbus4j.sero.util.ProgressiveTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract base class for Modbus master implementations. This class provides the core
|
||||
* functionality for implementing Modbus masters that can communicate with Modbus slave
|
||||
* devices over various transport mechanisms. It includes methods for reading and writing
|
||||
* values, batch operations, and slave node discovery.
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public abstract class ModbusMaster extends Modbus {
|
||||
|
||||
private final Map<Integer, SlaveProfile> slaveProfiles = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Should we validate the responses: - ensure that the requested slave id is what is
|
||||
* in the response
|
||||
*/
|
||||
protected boolean validateResponse;
|
||||
|
||||
/**
|
||||
* If connection is established with slave/slaves
|
||||
*/
|
||||
protected boolean connected = false;
|
||||
|
||||
protected boolean initialized;
|
||||
|
||||
private int timeout = 500;
|
||||
|
||||
private int retries = 2;
|
||||
|
||||
/**
|
||||
* If the slave equipment only supports multiple write commands, set this to true.
|
||||
* Otherwise, and combination of single or multiple write commands will be used as
|
||||
* appropriate.
|
||||
*/
|
||||
private boolean multipleWritesOnly;
|
||||
|
||||
private int discardDataDelay = 0;
|
||||
|
||||
private BaseIOLog ioLog;
|
||||
|
||||
/**
|
||||
* An input stream ePoll will use a single thread to read all input streams. If
|
||||
* multiple serial or TCP modbus connections are to be made, an ePoll can be much more
|
||||
* efficient.
|
||||
*/
|
||||
private InputStreamEPollWrapper ePoll;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isConnected.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>connected</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param connected a boolean.
|
||||
*/
|
||||
public void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* init.
|
||||
* </p>
|
||||
*
|
||||
* @throws ModbusInitException if any.
|
||||
*/
|
||||
abstract public void init() throws ModbusInitException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isInitialized.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* destroy.
|
||||
* </p>
|
||||
*/
|
||||
abstract public void destroy();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* send.
|
||||
* </p>
|
||||
*
|
||||
* @param request a {@link ModbusRequest} object.
|
||||
* @return a {@link ModbusResponse} object.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public final ModbusResponse send(ModbusRequest request) throws ModbusTransportException {
|
||||
request.validate(this);
|
||||
ModbusResponse modbusResponse = sendImpl(request);
|
||||
if (validateResponse)
|
||||
modbusResponse.validateResponse(request);
|
||||
return modbusResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* sendImpl.
|
||||
* </p>
|
||||
*
|
||||
* @param request a {@link ModbusRequest} object.
|
||||
* @return a {@link ModbusResponse} object.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
abstract public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException;
|
||||
|
||||
/**
|
||||
* Returns a value from the modbus network according to the given locator information.
|
||||
* Various data types are allowed to be requested including multi-word types. The
|
||||
* determination of the correct request message to send is handled automatically.
|
||||
*
|
||||
* @param locator the information required to locate the value in the modbus network.
|
||||
* @param <T> a T object.
|
||||
* @return an object representing the value found. This will be one of Boolean, Short,
|
||||
* Integer, Long, BigInteger, Float, or Double. See the DataType enumeration for
|
||||
* details on which type to expect.
|
||||
* @throws ModbusTransportException if there was an IO error or other technical
|
||||
* failure while sending the message
|
||||
* @throws ErrorResponseException if the response returned from the slave was an
|
||||
* exception.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getValue(BaseLocator<T> locator) throws ModbusTransportException, ErrorResponseException {
|
||||
BatchRead<String> batch = new BatchRead<>();
|
||||
batch.addLocator("", locator);
|
||||
BatchResults<String> result = send(batch);
|
||||
return (T) result.getValue("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given value in the modbus network according to the given locator
|
||||
* information. Various data types are allowed to be set including including
|
||||
* multi-word types. The determination of the correct write message to send is handled
|
||||
* automatically.
|
||||
*
|
||||
* @param locator the information required to locate the value in the modbus network.
|
||||
* @param value an object representing the value to be set. This will be one of
|
||||
* Boolean, Short, Integer, Long, BigInteger, Float, or Double. See the DataType
|
||||
* enumeration for details on which type to expect.
|
||||
* @param <T> type of locator
|
||||
* @throws ModbusTransportException if there was an IO error or other technical
|
||||
* failure while sending the message
|
||||
* @throws ErrorResponseException if the response returned from the slave was an
|
||||
* exception.
|
||||
*/
|
||||
public <T> void setValue(BaseLocator<T> locator, Object value)
|
||||
throws ModbusTransportException, ErrorResponseException {
|
||||
int slaveId = locator.getSlaveId();
|
||||
int registerRange = locator.getRange();
|
||||
int writeOffset = locator.getOffset();
|
||||
|
||||
// Determine the request type that we will use
|
||||
if (registerRange == RegisterRange.INPUT_STATUS || registerRange == RegisterRange.INPUT_REGISTER)
|
||||
throw new RuntimeException("Cannot write to input status or input register ranges");
|
||||
|
||||
if (registerRange == RegisterRange.COIL_STATUS) {
|
||||
if (!(value instanceof Boolean))
|
||||
throw new InvalidDataConversionException("Only boolean values can be written to coils");
|
||||
if (multipleWritesOnly)
|
||||
setValue(new WriteCoilsRequest(slaveId, writeOffset,
|
||||
new boolean[]{((Boolean) value).booleanValue()}));
|
||||
else
|
||||
setValue(new WriteCoilRequest(slaveId, writeOffset, ((Boolean) value).booleanValue()));
|
||||
} else {
|
||||
// Writing to holding registers.
|
||||
if (locator.getDataType() == DataType.BINARY) {
|
||||
if (!(value instanceof Boolean))
|
||||
throw new InvalidDataConversionException("Only boolean values can be written to coils");
|
||||
setHoldingRegisterBit(slaveId, writeOffset, ((BinaryLocator) locator).getBit(),
|
||||
((Boolean) value).booleanValue());
|
||||
} else {
|
||||
// Writing some kind of value to a holding register.
|
||||
@SuppressWarnings("unchecked")
|
||||
short[] data = locator.valueToShorts((T) value);
|
||||
if (data.length == 1 && !multipleWritesOnly)
|
||||
setValue(new WriteRegisterRequest(slaveId, writeOffset, data[0]));
|
||||
else
|
||||
setValue(new WriteRegistersRequest(slaveId, writeOffset, data));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Node scanning. Returns a list of slave nodes that respond to a read exception
|
||||
* status request (perhaps with an error, but respond nonetheless).
|
||||
* <p>
|
||||
* Note: a similar scan could be done for registers in nodes, but, for one thing, it
|
||||
* would take some time to run, and in any case the results would not be meaningful
|
||||
* since there would be no semantic information accompanying the results.
|
||||
*
|
||||
* @return a {@link List} object.
|
||||
*/
|
||||
public List<Integer> scanForSlaveNodes() {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (int i = 1; i <= 240; i++) {
|
||||
if (testSlaveNode(i))
|
||||
result.add(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* scanForSlaveNodes.
|
||||
* </p>
|
||||
*
|
||||
* @param l a {@link NodeScanListener} object.
|
||||
* @return a {@link ProgressiveTask} object.
|
||||
*/
|
||||
public ProgressiveTask scanForSlaveNodes(final NodeScanListener l) {
|
||||
l.progressUpdate(0);
|
||||
ProgressiveTask task = new ProgressiveTask(l) {
|
||||
private int node = 1;
|
||||
|
||||
@Override
|
||||
protected void runImpl() {
|
||||
if (testSlaveNode(node))
|
||||
l.nodeFound(node);
|
||||
|
||||
declareProgress(((float) node) / 240);
|
||||
|
||||
node++;
|
||||
if (node > 240)
|
||||
completed = true;
|
||||
}
|
||||
};
|
||||
|
||||
new Thread(task).start();
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* testSlaveNode.
|
||||
* </p>
|
||||
*
|
||||
* @param node a int.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean testSlaveNode(int node) {
|
||||
try {
|
||||
send(new ReadHoldingRegistersRequest(node, 0, 1));
|
||||
} catch (ModbusTransportException e) {
|
||||
// If there was a transport exception, there's no node there.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>retries</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getRetries() {
|
||||
return retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>retries</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param retries a int.
|
||||
*/
|
||||
public void setRetries(int retries) {
|
||||
if (retries < 0)
|
||||
this.retries = 0;
|
||||
else
|
||||
this.retries = retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>timeout</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>timeout</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param timeout a int.
|
||||
*/
|
||||
public void setTimeout(int timeout) {
|
||||
if (timeout < 1)
|
||||
this.timeout = 1;
|
||||
else
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isMultipleWritesOnly.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isMultipleWritesOnly() {
|
||||
return multipleWritesOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>multipleWritesOnly</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param multipleWritesOnly a boolean.
|
||||
*/
|
||||
public void setMultipleWritesOnly(boolean multipleWritesOnly) {
|
||||
this.multipleWritesOnly = multipleWritesOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>discardDataDelay</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getDiscardDataDelay() {
|
||||
return discardDataDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>discardDataDelay</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param discardDataDelay a int.
|
||||
*/
|
||||
public void setDiscardDataDelay(int discardDataDelay) {
|
||||
if (discardDataDelay < 0)
|
||||
this.discardDataDelay = 0;
|
||||
else
|
||||
this.discardDataDelay = discardDataDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>ioLog</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link BaseIOLog} object.
|
||||
*/
|
||||
public BaseIOLog getIoLog() {
|
||||
return ioLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>ioLog</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param ioLog a {@link BaseIOLog} object.
|
||||
*/
|
||||
public void setIoLog(BaseIOLog ioLog) {
|
||||
this.ioLog = ioLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>ePoll</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link InputStreamEPollWrapper} object.
|
||||
*/
|
||||
public InputStreamEPollWrapper getePoll() {
|
||||
return ePoll;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>ePoll</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param ePoll a {@link InputStreamEPollWrapper} object.
|
||||
*/
|
||||
public void setePoll(InputStreamEPollWrapper ePoll) {
|
||||
this.ePoll = ePoll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful for sending a number of polling commands at once, or at least in as optimal
|
||||
* a batch as possible.
|
||||
*
|
||||
* @param batch a {@link BatchRead} object.
|
||||
* @param <K> type of result
|
||||
* @return a {@link BatchResults} object.
|
||||
* @throws ModbusTransportException if any.
|
||||
* @throws ErrorResponseException if any.
|
||||
*/
|
||||
public <K> BatchResults<K> send(BatchRead<K> batch) throws ModbusTransportException, ErrorResponseException {
|
||||
if (!initialized)
|
||||
throw new ModbusTransportException("not initialized");
|
||||
|
||||
BatchResults<K> results = new BatchResults<>();
|
||||
List<ReadFunctionGroup<K>> functionGroups = batch.getReadFunctionGroups(this);
|
||||
|
||||
// Execute each read function and process the results.
|
||||
for (ReadFunctionGroup<K> functionGroup : functionGroups) {
|
||||
sendFunctionGroup(functionGroup, results, batch.isErrorsInResults(), batch.isExceptionsInResults());
|
||||
if (batch.isCancel())
|
||||
break;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMessageControl.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link MessageControl} object.
|
||||
*/
|
||||
protected MessageControl getMessageControl() {
|
||||
MessageControl conn = new MessageControl();
|
||||
conn.setRetries(getRetries());
|
||||
conn.setTimeout(getTimeout());
|
||||
conn.setDiscardDataDelay(getDiscardDataDelay());
|
||||
conn.setExceptionHandler(getExceptionHandler());
|
||||
conn.setIoLog(ioLog);
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* closeMessageControl.
|
||||
* </p>
|
||||
*
|
||||
* @param conn a {@link MessageControl} object.
|
||||
*/
|
||||
protected void closeMessageControl(MessageControl conn) {
|
||||
if (conn != null)
|
||||
conn.close();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Private stuff
|
||||
//
|
||||
|
||||
/**
|
||||
* This method assumes that all locators have already been pre-sorted and grouped into
|
||||
* valid requests, say, by the createRequestGroups method.
|
||||
*/
|
||||
private <K> void sendFunctionGroup(ReadFunctionGroup<K> functionGroup, BatchResults<K> results,
|
||||
boolean errorsInResults, boolean exceptionsInResults)
|
||||
throws ModbusTransportException, ErrorResponseException {
|
||||
int slaveId = functionGroup.getSlaveAndRange().getSlaveId();
|
||||
int startOffset = functionGroup.getStartOffset();
|
||||
int length = functionGroup.getLength();
|
||||
|
||||
// Inspect the function group for data required to create the request.
|
||||
ModbusRequest request;
|
||||
if (functionGroup.getFunctionCode() == FunctionCode.READ_COILS)
|
||||
request = new ReadCoilsRequest(slaveId, startOffset, length);
|
||||
else if (functionGroup.getFunctionCode() == FunctionCode.READ_DISCRETE_INPUTS)
|
||||
request = new ReadDiscreteInputsRequest(slaveId, startOffset, length);
|
||||
else if (functionGroup.getFunctionCode() == FunctionCode.READ_HOLDING_REGISTERS)
|
||||
request = new ReadHoldingRegistersRequest(slaveId, startOffset, length);
|
||||
else if (functionGroup.getFunctionCode() == FunctionCode.READ_INPUT_REGISTERS)
|
||||
request = new ReadInputRegistersRequest(slaveId, startOffset, length);
|
||||
else
|
||||
throw new RuntimeException("Unsupported function");
|
||||
|
||||
ReadResponse response;
|
||||
try {
|
||||
response = (ReadResponse) send(request);
|
||||
} catch (ModbusTransportException e) {
|
||||
if (!exceptionsInResults)
|
||||
throw e;
|
||||
|
||||
for (KeyedModbusLocator<K> locator : functionGroup.getLocators())
|
||||
results.addResult(locator.getKey(), e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] data = null;
|
||||
if (!errorsInResults && response.isException())
|
||||
throw new ErrorResponseException(request, response);
|
||||
else if (!response.isException())
|
||||
data = response.getData();
|
||||
|
||||
for (KeyedModbusLocator<K> locator : functionGroup.getLocators()) {
|
||||
if (errorsInResults && response.isException())
|
||||
results.addResult(locator.getKey(), new ExceptionResult(response.getExceptionCode()));
|
||||
else {
|
||||
try {
|
||||
results.addResult(locator.getKey(), locator.bytesToValue(data, startOffset));
|
||||
} catch (RuntimeException e) {
|
||||
throw new RuntimeException("Result conversion exception. data=" + ArrayUtils.toHexString(data)
|
||||
+ ", startOffset=" + startOffset + ", locator=" + locator + ", functionGroup.functionCode="
|
||||
+ functionGroup.getFunctionCode() + ", functionGroup.startOffset=" + startOffset
|
||||
+ ", functionGroup.length=" + length, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setValue(ModbusRequest request) throws ModbusTransportException, ErrorResponseException {
|
||||
ModbusResponse response = send(request);
|
||||
if (response == null)
|
||||
// This should only happen if the request was a broadcast
|
||||
return;
|
||||
if (response.isException())
|
||||
throw new ErrorResponseException(request, response);
|
||||
}
|
||||
|
||||
private void setHoldingRegisterBit(int slaveId, int writeOffset, int bit, boolean value)
|
||||
throws ModbusTransportException, ErrorResponseException {
|
||||
// Writing a bit in a holding register field. There are two ways to do this. The
|
||||
// easy way is to
|
||||
// use a write mask request, but it is not always supported. The hard way is to
|
||||
// read the value, change
|
||||
// the appropriate bit, and then write it back again (so as not to overwrite the
|
||||
// other bits in the
|
||||
// value). However, since the hard way is not atomic, it is not fail-safe either,
|
||||
// but it should be
|
||||
// at least possible.
|
||||
SlaveProfile sp = getSlaveProfile(slaveId);
|
||||
if (sp.getWriteMaskRegister()) {
|
||||
// Give the write mask a try.
|
||||
WriteMaskRegisterRequest request = new WriteMaskRegisterRequest(slaveId, writeOffset);
|
||||
request.setBit(bit, value);
|
||||
ModbusResponse response = send(request);
|
||||
if (response == null)
|
||||
// This should only happen if the request was a broadcast
|
||||
return;
|
||||
if (!response.isException())
|
||||
// Hey, cool, it worked.
|
||||
return;
|
||||
|
||||
if (response.getExceptionCode() == ExceptionCode.ILLEGAL_FUNCTION)
|
||||
// The function is probably not supported. Fail-over to the two step.
|
||||
sp.setWriteMaskRegister(false);
|
||||
else
|
||||
throw new ErrorResponseException(request, response);
|
||||
}
|
||||
|
||||
// Do it the hard way. Get the register's current value.
|
||||
int regValue = (Integer) getValue(new NumericLocator(slaveId, RegisterRange.HOLDING_REGISTER, writeOffset,
|
||||
DataType.TWO_BYTE_INT_UNSIGNED));
|
||||
|
||||
// Modify the value according to the given bit and value.
|
||||
if (value)
|
||||
regValue = regValue | 1 << bit;
|
||||
else
|
||||
regValue = regValue & ~(1 << bit);
|
||||
|
||||
// Write the new register value.
|
||||
setValue(new WriteRegisterRequest(slaveId, writeOffset, regValue));
|
||||
}
|
||||
|
||||
private SlaveProfile getSlaveProfile(int slaveId) {
|
||||
SlaveProfile sp = slaveProfiles.get(slaveId);
|
||||
if (sp == null) {
|
||||
sp = new SlaveProfile();
|
||||
slaveProfiles.put(slaveId, sp);
|
||||
}
|
||||
return sp;
|
||||
}
|
||||
|
||||
}
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract ModbusSlaveSet class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public abstract class ModbusSlaveSet extends Modbus {
|
||||
|
||||
private LinkedHashMap<Integer, ProcessImage> processImages = new LinkedHashMap<>();
|
||||
|
||||
private ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* addProcessImage.
|
||||
* </p>
|
||||
*
|
||||
* @param processImage a {@link ProcessImage} object.
|
||||
*/
|
||||
public void addProcessImage(ProcessImage processImage) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
processImages.put(processImage.getSlaveId(), processImage);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* removeProcessImage.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean removeProcessImage(int slaveId) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
return (processImages.remove(slaveId) != null);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* removeProcessImage.
|
||||
* </p>
|
||||
*
|
||||
* @param processImage a {@link ProcessImage} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean removeProcessImage(ProcessImage processImage) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
return (processImages.remove(processImage.getSlaveId()) != null);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getProcessImage.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @return a {@link ProcessImage} object.
|
||||
*/
|
||||
public ProcessImage getProcessImage(int slaveId) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return processImages.get(slaveId);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a copy of the current process images
|
||||
*
|
||||
* @return a {@link Collection} object.
|
||||
*/
|
||||
public Collection<ProcessImage> getProcessImages() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return new HashSet<>(processImages.values());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the slave. If an exception is not thrown, this method doesn't return, but
|
||||
* uses the thread to execute the listening.
|
||||
*
|
||||
* @throws ModbusInitException if necessary
|
||||
*/
|
||||
abstract public void start() throws ModbusInitException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* stop.
|
||||
* </p>
|
||||
*/
|
||||
abstract public void stop();
|
||||
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.sero.util.ProgressiveTaskListener;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* NodeScanListener interface.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public interface NodeScanListener extends ProgressiveTaskListener {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* nodeFound.
|
||||
* </p>
|
||||
*
|
||||
* @param nodeNumber a int.
|
||||
*/
|
||||
void nodeFound(int nodeNumber);
|
||||
|
||||
}
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
import com.serotonin.modbus4j.exception.IllegalDataAddressException;
|
||||
|
||||
/**
|
||||
* Used by slave implementors. Provides an interface by which slaves can easily manage
|
||||
* data.
|
||||
*
|
||||
* @author mlohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public interface ProcessImage {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getSlaveId.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
int getSlaveId();
|
||||
|
||||
//
|
||||
// /
|
||||
// / Coils
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the current value of the coil for the given offset.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @return the value of the coil
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
boolean getCoil(int offset) throws IllegalDataAddressException;
|
||||
|
||||
/**
|
||||
* Used internally for setting the value of the coil.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
void setCoil(int offset, boolean value);
|
||||
|
||||
/**
|
||||
* Used to set the coil as a result of a write command from the master.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param value a boolean.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
void writeCoil(int offset, boolean value) throws IllegalDataAddressException;
|
||||
|
||||
//
|
||||
// /
|
||||
// / Inputs
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the current value of the input for the given offset.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @return the value of the input
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
boolean getInput(int offset) throws IllegalDataAddressException;
|
||||
|
||||
/**
|
||||
* Used internally for setting the value of the input.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param value a boolean.
|
||||
*/
|
||||
void setInput(int offset, boolean value);
|
||||
|
||||
//
|
||||
// /
|
||||
// / Holding registers
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the current value of the holding register for the given offset.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @return the value of the register
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
short getHoldingRegister(int offset) throws IllegalDataAddressException;
|
||||
|
||||
/**
|
||||
* Used internally for setting the value of the holding register.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param value a short.
|
||||
*/
|
||||
void setHoldingRegister(int offset, short value);
|
||||
|
||||
/**
|
||||
* Used to set the holding register as a result of a write command from the master.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param value a short.
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
void writeHoldingRegister(int offset, short value) throws IllegalDataAddressException;
|
||||
|
||||
//
|
||||
// /
|
||||
// / Input registers
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the current value of the input register for the given offset.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @return the value of the register
|
||||
* @throws IllegalDataAddressException if any.
|
||||
*/
|
||||
short getInputRegister(int offset) throws IllegalDataAddressException;
|
||||
|
||||
/**
|
||||
* Used internally for setting the value of the input register.
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param value a short.
|
||||
*/
|
||||
void setInputRegister(int offset, short value);
|
||||
|
||||
//
|
||||
// /
|
||||
// / Exception status
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the current value of the exception status.
|
||||
*
|
||||
* @return the current value of the exception status.
|
||||
*/
|
||||
byte getExceptionStatus();
|
||||
|
||||
//
|
||||
// /
|
||||
// / Report slave id
|
||||
// /
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the data for the report slave id command.
|
||||
*
|
||||
* @return the data for the report slave id command.
|
||||
*/
|
||||
byte[] getReportSlaveIdData();
|
||||
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ProcessImageListener interface.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public interface ProcessImageListener {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* coilWrite.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param oldValue a boolean.
|
||||
* @param newValue a boolean.
|
||||
*/
|
||||
public void coilWrite(int offset, boolean oldValue, boolean newValue);
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* holdingRegisterWrite.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @param oldValue a short.
|
||||
* @param newValue a short.
|
||||
*/
|
||||
public void holdingRegisterWrite(int offset, short oldValue, short newValue);
|
||||
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.MessageParser;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract BaseMessageParser class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public abstract class BaseMessageParser implements MessageParser {
|
||||
|
||||
protected final boolean master;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for BaseMessageParser.
|
||||
* </p>
|
||||
*
|
||||
* @param master a boolean.
|
||||
*/
|
||||
public BaseMessageParser(boolean master) {
|
||||
this.master = master;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IncomingMessage parseMessage(ByteQueue queue) throws Exception {
|
||||
try {
|
||||
return parseMessageImpl(queue);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Means that we ran out of data trying to read the message. Just return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* parseMessageImpl.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @return a {@link IncomingMessage} object.
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
abstract protected IncomingMessage parseMessageImpl(ByteQueue queue) throws Exception;
|
||||
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusSlaveSet;
|
||||
import com.serotonin.modbus4j.ProcessImage;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.RequestHandler;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract BaseRequestHandler class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public abstract class BaseRequestHandler implements RequestHandler {
|
||||
|
||||
protected ModbusSlaveSet slave;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for BaseRequestHandler.
|
||||
* </p>
|
||||
*
|
||||
* @param slave a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public BaseRequestHandler(ModbusSlaveSet slave) {
|
||||
this.slave = slave;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* handleRequestImpl.
|
||||
* </p>
|
||||
*
|
||||
* @param request a {@link ModbusRequest} object.
|
||||
* @return a {@link ModbusResponse} object.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
protected ModbusResponse handleRequestImpl(ModbusRequest request) throws ModbusTransportException {
|
||||
request.validate(slave);
|
||||
|
||||
int slaveId = request.getSlaveId();
|
||||
|
||||
// Check the slave id.
|
||||
if (slaveId == 0) {
|
||||
// Broadcast message. Send to all process images.
|
||||
for (ProcessImage processImage : slave.getProcessImages())
|
||||
request.handle(processImage);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the process image to which to send.
|
||||
ProcessImage processImage = slave.getProcessImage(slaveId);
|
||||
if (processImage == null)
|
||||
return null;
|
||||
|
||||
return request.handle(processImage);
|
||||
}
|
||||
|
||||
}
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
import com.serotonin.modbus4j.ExceptionResult;
|
||||
import com.serotonin.modbus4j.code.ExceptionCode;
|
||||
import com.serotonin.modbus4j.locator.BaseLocator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* KeyedModbusLocator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class KeyedModbusLocator<K> {
|
||||
|
||||
private final K key;
|
||||
|
||||
private final BaseLocator<?> locator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for KeyedModbusLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param key a K object.
|
||||
* @param locator a {@link BaseLocator} object.
|
||||
*/
|
||||
public KeyedModbusLocator(K key, BaseLocator<?> locator) {
|
||||
this.key = key;
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>key</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a K object.
|
||||
*/
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>locator</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public BaseLocator<?> getLocator() {
|
||||
return locator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyedModbusLocator(key=" + key + ", locator=" + locator + ")";
|
||||
}
|
||||
|
||||
//
|
||||
///
|
||||
/// Delegation.
|
||||
///
|
||||
//
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getDataType.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getDataType() {
|
||||
return locator.getDataType();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getOffset.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getOffset() {
|
||||
return locator.getOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getSlaveAndRange.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link SlaveAndRange} object.
|
||||
*/
|
||||
public SlaveAndRange getSlaveAndRange() {
|
||||
return new SlaveAndRange(locator.getSlaveId(), locator.getRange());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getEndOffset.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getEndOffset() {
|
||||
return locator.getEndOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getRegisterCount.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getRegisterCount() {
|
||||
return locator.getRegisterCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* bytesToValue.
|
||||
* </p>
|
||||
*
|
||||
* @param data an array of {@link byte} objects.
|
||||
* @param requestOffset a int.
|
||||
* @return a {@link Object} object.
|
||||
*/
|
||||
public Object bytesToValue(byte[] data, int requestOffset) {
|
||||
try {
|
||||
return locator.bytesToValue(data, requestOffset);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Some equipment will not return data lengths that we expect, which causes
|
||||
// AIOOBEs. Catch them and convert
|
||||
// them into illegal data address exceptions.
|
||||
return new ExceptionResult(ExceptionCode.ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-294
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.IllegalSlaveIdException;
|
||||
import com.serotonin.modbus4j.exception.ModbusIdException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.msg.ModbusMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ModbusUtils class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ModbusUtils {
|
||||
|
||||
/**
|
||||
* Constant <code>TCP_PORT=502</code>
|
||||
*/
|
||||
public static final int TCP_PORT = 502;
|
||||
|
||||
/**
|
||||
* Constant <code>IP_PROTOCOL_ID=0</code>
|
||||
*/
|
||||
public static final int IP_PROTOCOL_ID = 0; // Modbus protocol
|
||||
|
||||
// public static final int MAX_READ_BIT_COUNT = 2000;
|
||||
// public static final int MAX_READ_REGISTER_COUNT = 125;
|
||||
// public static final int MAX_WRITE_REGISTER_COUNT = 120;
|
||||
// Table of CRC values for high-order byte
|
||||
private final static short[] lookupCRCHi = {0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
|
||||
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
|
||||
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
||||
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40};
|
||||
|
||||
// Table of CRC values for low-order byte
|
||||
private final static short[] lookupCRCLo = {0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
|
||||
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
||||
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
|
||||
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30,
|
||||
0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
|
||||
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B,
|
||||
0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
||||
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6,
|
||||
0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
|
||||
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD,
|
||||
0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
|
||||
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
||||
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
|
||||
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47,
|
||||
0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40};
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* pushByte.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @param value a int.
|
||||
*/
|
||||
public static void pushByte(ByteQueue queue, int value) {
|
||||
queue.push((byte) value);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* pushShort.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @param value a int.
|
||||
*/
|
||||
public static void pushShort(ByteQueue queue, int value) {
|
||||
queue.push((byte) (0xff & (value >> 8)));
|
||||
queue.push((byte) (0xff & value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* popByte.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int popByte(ByteQueue queue) {
|
||||
return queue.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* popUnsignedByte.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int popUnsignedByte(ByteQueue queue) {
|
||||
return queue.pop() & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* popShort.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int popShort(ByteQueue queue) {
|
||||
return toShort(queue.pop(), queue.pop());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* popUnsignedShort.
|
||||
* </p>
|
||||
*
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int popUnsignedShort(ByteQueue queue) {
|
||||
return ((queue.pop() & 0xff) << 8) | (queue.pop() & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* toShort.
|
||||
* </p>
|
||||
*
|
||||
* @param b1 a byte.
|
||||
* @param b2 a byte.
|
||||
* @return a short.
|
||||
*/
|
||||
public static short toShort(byte b1, byte b2) {
|
||||
return (short) ((b1 << 8) | (b2 & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* toByte.
|
||||
* </p>
|
||||
*
|
||||
* @param value a short.
|
||||
* @param first a boolean.
|
||||
* @return a byte.
|
||||
*/
|
||||
public static byte toByte(short value, boolean first) {
|
||||
if (first)
|
||||
return (byte) (0xff & (value >> 8));
|
||||
return (byte) (0xff & value);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateRegisterRange.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
*/
|
||||
public static void validateRegisterRange(int range) {
|
||||
if (RegisterRange.getReadFunctionCode(range) == -1)
|
||||
throw new ModbusIdException("Invalid register range: " + range);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateSlaveId.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param includeBroadcast a boolean.
|
||||
*/
|
||||
public static void validateSlaveId(int slaveId, boolean includeBroadcast) {
|
||||
if (slaveId < (includeBroadcast ? 0 : 1) /* || slaveId > 240 */)
|
||||
throw new IllegalSlaveIdException("Invalid slave id: " + slaveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateBit.
|
||||
* </p>
|
||||
*
|
||||
* @param bit a int.
|
||||
*/
|
||||
public static void validateBit(int bit) {
|
||||
if (bit < 0 || bit > 15)
|
||||
throw new ModbusIdException("Invalid bit: " + bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateOffset.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public static void validateOffset(int offset) throws ModbusTransportException {
|
||||
if (offset < 0 || offset > 65535)
|
||||
throw new ModbusTransportException("Invalid offset: " + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validateEndOffset.
|
||||
* </p>
|
||||
*
|
||||
* @param offset a int.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public static void validateEndOffset(int offset) throws ModbusTransportException {
|
||||
if (offset > 65535)
|
||||
throw new ModbusTransportException("Invalid end offset: " + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* checkCRC.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusMessage a {@link ModbusMessage} object.
|
||||
* @param queue a {@link ByteQueue} object.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public static void checkCRC(ModbusMessage modbusMessage, ByteQueue queue) throws ModbusTransportException {
|
||||
// Check the CRC
|
||||
int calcCrc = calculateCRC(modbusMessage);
|
||||
int givenCrc = ModbusUtils.popUnsignedShort(queue);
|
||||
|
||||
if (calcCrc != givenCrc)
|
||||
throw new ModbusTransportException("CRC mismatch: given=" + givenCrc + ", calc=" + calcCrc,
|
||||
modbusMessage.getSlaveId());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* calculateCRC.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusMessage a {@link ModbusMessage} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int calculateCRC(ModbusMessage modbusMessage) {
|
||||
ByteQueue queue = new ByteQueue();
|
||||
modbusMessage.write(queue);
|
||||
|
||||
int high = 0xff;
|
||||
int low = 0xff;
|
||||
int nextByte = 0;
|
||||
int uIndex;
|
||||
|
||||
while (queue.size() > 0) {
|
||||
nextByte = 0xFF & queue.pop();
|
||||
uIndex = high ^ nextByte;
|
||||
high = low ^ lookupCRCHi[uIndex];
|
||||
low = lookupCRCLo[uIndex];
|
||||
}
|
||||
|
||||
return (high << 8) | low;
|
||||
}
|
||||
|
||||
}
|
||||
-93
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* RangeAndOffset class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class RangeAndOffset {
|
||||
|
||||
private int range;
|
||||
|
||||
private int offset;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for RangeAndOffset.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
*/
|
||||
public RangeAndOffset(int range, int offset) {
|
||||
this.range = range;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor provides a best guess at the function and offset the user wants,
|
||||
* with the assumption that the offset will never go over 9999.
|
||||
*
|
||||
* @param registerId a int.
|
||||
*/
|
||||
public RangeAndOffset(int registerId) {
|
||||
if (registerId < 10000) {
|
||||
this.range = RegisterRange.COIL_STATUS;
|
||||
this.offset = registerId - 1;
|
||||
} else if (registerId < 20000) {
|
||||
this.range = RegisterRange.INPUT_STATUS;
|
||||
this.offset = registerId - 10001;
|
||||
} else if (registerId < 40000) {
|
||||
this.range = RegisterRange.INPUT_REGISTER;
|
||||
this.offset = registerId - 30001;
|
||||
} else {
|
||||
this.range = RegisterRange.HOLDING_REGISTER;
|
||||
this.offset = registerId - 40001;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>range</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>offset</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ReadFunctionGroup class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ReadFunctionGroup<K> {
|
||||
|
||||
private final SlaveAndRange slaveAndRange;
|
||||
|
||||
private final int functionCode;
|
||||
|
||||
private final List<KeyedModbusLocator<K>> locators = new ArrayList<>();
|
||||
|
||||
private int startOffset = 65536;
|
||||
|
||||
private int length = 0;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ReadFunctionGroup.
|
||||
* </p>
|
||||
*
|
||||
* @param locator a {@link KeyedModbusLocator} object.
|
||||
*/
|
||||
public ReadFunctionGroup(KeyedModbusLocator<K> locator) {
|
||||
slaveAndRange = locator.getSlaveAndRange();
|
||||
functionCode = RegisterRange.getReadFunctionCode(slaveAndRange.getRange());
|
||||
add(locator);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* add.
|
||||
* </p>
|
||||
*
|
||||
* @param locator a {@link KeyedModbusLocator} object.
|
||||
*/
|
||||
public void add(KeyedModbusLocator<K> locator) {
|
||||
if (startOffset > locator.getOffset())
|
||||
startOffset = locator.getOffset();
|
||||
if (length < locator.getEndOffset() - startOffset + 1)
|
||||
length = locator.getEndOffset() - startOffset + 1;
|
||||
locators.add(locator);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>startOffset</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getEndOffset.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getEndOffset() {
|
||||
return startOffset + length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>slaveAndRange</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link SlaveAndRange} object.
|
||||
*/
|
||||
public SlaveAndRange getSlaveAndRange() {
|
||||
return slaveAndRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>length</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>functionCode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getFunctionCode() {
|
||||
return functionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>locators</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link List} object.
|
||||
*/
|
||||
public List<KeyedModbusLocator<K>> getLocators() {
|
||||
return locators;
|
||||
}
|
||||
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* SlaveAndRange class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class SlaveAndRange {
|
||||
|
||||
private final int slaveId;
|
||||
|
||||
private final int range;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for SlaveAndRange.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
*/
|
||||
public SlaveAndRange(int slaveId, int range) {
|
||||
ModbusUtils.validateSlaveId(slaveId, true);
|
||||
|
||||
this.slaveId = slaveId;
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>range</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>slaveId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getSlaveId() {
|
||||
return slaveId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + range;
|
||||
result = prime * result + slaveId;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final SlaveAndRange other = (SlaveAndRange) obj;
|
||||
if (range != other.range)
|
||||
return false;
|
||||
if (slaveId != other.slaveId)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.base;
|
||||
|
||||
/**
|
||||
* Class for maintaining the profile of a slave device on the master side. Initially, we
|
||||
* assume that the device is fully featured, and then we note function failures so that we
|
||||
* know how requests should subsequently be sent.
|
||||
*
|
||||
* @author mlohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class SlaveProfile {
|
||||
|
||||
private boolean writeMaskRegister = true;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>writeMaskRegister</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean getWriteMaskRegister() {
|
||||
return writeMaskRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>writeMaskRegister</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param writeMaskRegister a boolean.
|
||||
*/
|
||||
public void setWriteMaskRegister(boolean writeMaskRegister) {
|
||||
this.writeMaskRegister = writeMaskRegister;
|
||||
}
|
||||
|
||||
}
|
||||
-317
@@ -1,317 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.code;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* DataType class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class DataType {
|
||||
|
||||
/**
|
||||
* Constant <code>BINARY=1</code>
|
||||
*/
|
||||
public static final int BINARY = 1;
|
||||
|
||||
/**
|
||||
* Constant <code>TWO_BYTE_INT_UNSIGNED=2</code>
|
||||
*/
|
||||
public static final int TWO_BYTE_INT_UNSIGNED = 2;
|
||||
|
||||
/**
|
||||
* Constant <code>TWO_BYTE_INT_SIGNED=3</code>
|
||||
*/
|
||||
public static final int TWO_BYTE_INT_SIGNED = 3;
|
||||
|
||||
/**
|
||||
* Constant <code>TWO_BYTE_INT_UNSIGNED_SWAPPED=22</code>
|
||||
*/
|
||||
public static final int TWO_BYTE_INT_UNSIGNED_SWAPPED = 22;
|
||||
|
||||
/**
|
||||
* Constant <code>TWO_BYTE_INT_SIGNED_SWAPPED=23</code>
|
||||
*/
|
||||
public static final int TWO_BYTE_INT_SIGNED_SWAPPED = 23;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_INT_UNSIGNED=4</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_INT_UNSIGNED = 4;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_INT_SIGNED=5</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_INT_SIGNED = 5;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_INT_UNSIGNED_SWAPPED=6</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_INT_UNSIGNED_SWAPPED = 6;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_INT_SIGNED_SWAPPED=7</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_INT_SIGNED_SWAPPED = 7;
|
||||
|
||||
/* 0xAABBCCDD is transmitted as 0xDDCCBBAA */
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED=24</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED = 24;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED=25</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED = 25;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_FLOAT=8</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_FLOAT = 8;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_FLOAT_SWAPPED=9</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_FLOAT_SWAPPED = 9;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_FLOAT_SWAPPED_INVERTED=21</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_FLOAT_SWAPPED_INVERTED = 21;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_INT_UNSIGNED=10</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_INT_UNSIGNED = 10;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_INT_SIGNED=11</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_INT_SIGNED = 11;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_INT_UNSIGNED_SWAPPED=12</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_INT_UNSIGNED_SWAPPED = 12;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_INT_SIGNED_SWAPPED=13</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_INT_SIGNED_SWAPPED = 13;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_FLOAT=14</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_FLOAT = 14;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_FLOAT_SWAPPED=15</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_FLOAT_SWAPPED = 15;
|
||||
|
||||
/**
|
||||
* Constant <code>TWO_BYTE_BCD=16</code>
|
||||
*/
|
||||
public static final int TWO_BYTE_BCD = 16;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_BCD=17</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_BCD = 17;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_BCD_SWAPPED=20</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_BCD_SWAPPED = 20;
|
||||
|
||||
/**
|
||||
* Constant <code>CHAR=18</code>
|
||||
*/
|
||||
public static final int CHAR = 18;
|
||||
|
||||
/**
|
||||
* Constant <code>VARCHAR=19</code>
|
||||
*/
|
||||
public static final int VARCHAR = 19;
|
||||
|
||||
// MOD10K two, three and four register types
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_MOD_10K=26</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_MOD_10K = 26;
|
||||
|
||||
/**
|
||||
* Constant <code>SIX_BYTE_MOD_10K=27</code>
|
||||
*/
|
||||
public static final int SIX_BYTE_MOD_10K = 27;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_MOD_10K=28</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_MOD_10K = 28;
|
||||
|
||||
/**
|
||||
* Constant <code>FOUR_BYTE_MOD_10K_SWAPPED=29</code>
|
||||
*/
|
||||
public static final int FOUR_BYTE_MOD_10K_SWAPPED = 29;
|
||||
|
||||
/**
|
||||
* Constant <code>SIX_BYTE_MOD_10K_SWAPPED=30</code>
|
||||
*/
|
||||
public static final int SIX_BYTE_MOD_10K_SWAPPED = 30;
|
||||
|
||||
/**
|
||||
* Constant <code>EIGHT_BYTE_MOD_10K_SWAPPED=31</code>
|
||||
*/
|
||||
public static final int EIGHT_BYTE_MOD_10K_SWAPPED = 31;
|
||||
|
||||
// One byte unsigned integer types
|
||||
/**
|
||||
* Constant <code>ONE_BYTE_INT_UNSIGNED_LOWER=32</code>
|
||||
*/
|
||||
public static final int ONE_BYTE_INT_UNSIGNED_LOWER = 32;
|
||||
|
||||
/**
|
||||
* Constant <code>ONE_BYTE_INT_UNSIGNED_UPPER=33</code>
|
||||
*/
|
||||
public static final int ONE_BYTE_INT_UNSIGNED_UPPER = 33;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getRegisterCount.
|
||||
* </p>
|
||||
*
|
||||
* @param id a int.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int getRegisterCount(int id) {
|
||||
switch (id) {
|
||||
case BINARY:
|
||||
case TWO_BYTE_INT_UNSIGNED:
|
||||
case TWO_BYTE_INT_SIGNED:
|
||||
case TWO_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case TWO_BYTE_INT_SIGNED_SWAPPED:
|
||||
case TWO_BYTE_BCD:
|
||||
case ONE_BYTE_INT_UNSIGNED_LOWER:
|
||||
case ONE_BYTE_INT_UNSIGNED_UPPER:
|
||||
return 1;
|
||||
case FOUR_BYTE_INT_UNSIGNED:
|
||||
case FOUR_BYTE_INT_SIGNED:
|
||||
case FOUR_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case FOUR_BYTE_INT_SIGNED_SWAPPED:
|
||||
case FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED:
|
||||
case FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED:
|
||||
case FOUR_BYTE_FLOAT:
|
||||
case FOUR_BYTE_FLOAT_SWAPPED:
|
||||
case FOUR_BYTE_FLOAT_SWAPPED_INVERTED:
|
||||
case FOUR_BYTE_BCD:
|
||||
case FOUR_BYTE_BCD_SWAPPED:
|
||||
case FOUR_BYTE_MOD_10K:
|
||||
case FOUR_BYTE_MOD_10K_SWAPPED:
|
||||
return 2;
|
||||
case SIX_BYTE_MOD_10K:
|
||||
case SIX_BYTE_MOD_10K_SWAPPED:
|
||||
return 3;
|
||||
case EIGHT_BYTE_INT_UNSIGNED:
|
||||
case EIGHT_BYTE_INT_SIGNED:
|
||||
case EIGHT_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case EIGHT_BYTE_INT_SIGNED_SWAPPED:
|
||||
case EIGHT_BYTE_FLOAT:
|
||||
case EIGHT_BYTE_FLOAT_SWAPPED:
|
||||
case EIGHT_BYTE_MOD_10K:
|
||||
case EIGHT_BYTE_MOD_10K_SWAPPED:
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getJavaType.
|
||||
* </p>
|
||||
*
|
||||
* @param id a int.
|
||||
* @return a {@link Class} object.
|
||||
*/
|
||||
public static Class<?> getJavaType(int id) {
|
||||
switch (id) {
|
||||
case ONE_BYTE_INT_UNSIGNED_LOWER:
|
||||
case ONE_BYTE_INT_UNSIGNED_UPPER:
|
||||
return Integer.class;
|
||||
case BINARY:
|
||||
return Boolean.class;
|
||||
case TWO_BYTE_INT_UNSIGNED:
|
||||
case TWO_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
return Integer.class;
|
||||
case TWO_BYTE_INT_SIGNED:
|
||||
case TWO_BYTE_INT_SIGNED_SWAPPED:
|
||||
return Short.class;
|
||||
case FOUR_BYTE_INT_UNSIGNED:
|
||||
return Long.class;
|
||||
case FOUR_BYTE_INT_SIGNED:
|
||||
return Integer.class;
|
||||
case FOUR_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED:
|
||||
return Long.class;
|
||||
case FOUR_BYTE_INT_SIGNED_SWAPPED:
|
||||
case FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED:
|
||||
return Integer.class;
|
||||
case FOUR_BYTE_FLOAT:
|
||||
return Float.class;
|
||||
case FOUR_BYTE_FLOAT_SWAPPED:
|
||||
return Float.class;
|
||||
case FOUR_BYTE_FLOAT_SWAPPED_INVERTED:
|
||||
return Float.class;
|
||||
case EIGHT_BYTE_INT_UNSIGNED:
|
||||
return BigInteger.class;
|
||||
case EIGHT_BYTE_INT_SIGNED:
|
||||
return Long.class;
|
||||
case EIGHT_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
return BigInteger.class;
|
||||
case EIGHT_BYTE_INT_SIGNED_SWAPPED:
|
||||
return Long.class;
|
||||
case EIGHT_BYTE_FLOAT:
|
||||
return Double.class;
|
||||
case EIGHT_BYTE_FLOAT_SWAPPED:
|
||||
return Double.class;
|
||||
case TWO_BYTE_BCD:
|
||||
return Short.class;
|
||||
case FOUR_BYTE_BCD:
|
||||
case FOUR_BYTE_BCD_SWAPPED:
|
||||
return Integer.class;
|
||||
case CHAR:
|
||||
case VARCHAR:
|
||||
return String.class;
|
||||
case FOUR_BYTE_MOD_10K:
|
||||
case SIX_BYTE_MOD_10K:
|
||||
case EIGHT_BYTE_MOD_10K:
|
||||
case FOUR_BYTE_MOD_10K_SWAPPED:
|
||||
case SIX_BYTE_MOD_10K_SWAPPED:
|
||||
case EIGHT_BYTE_MOD_10K_SWAPPED:
|
||||
return BigInteger.class;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.code;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ExceptionCode class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ExceptionCode {
|
||||
|
||||
/**
|
||||
* Constant <code>ILLEGAL_FUNCTION=0x1</code>
|
||||
*/
|
||||
public static final byte ILLEGAL_FUNCTION = 0x1;
|
||||
|
||||
/**
|
||||
* Constant <code>ILLEGAL_DATA_ADDRESS=0x2</code>
|
||||
*/
|
||||
public static final byte ILLEGAL_DATA_ADDRESS = 0x2;
|
||||
|
||||
/**
|
||||
* Constant <code>ILLEGAL_DATA_VALUE=0x3</code>
|
||||
*/
|
||||
public static final byte ILLEGAL_DATA_VALUE = 0x3;
|
||||
|
||||
/**
|
||||
* Constant <code>SLAVE_DEVICE_FAILURE=0x4</code>
|
||||
*/
|
||||
public static final byte SLAVE_DEVICE_FAILURE = 0x4;
|
||||
|
||||
/**
|
||||
* Constant <code>ACKNOWLEDGE=0x5</code>
|
||||
*/
|
||||
public static final byte ACKNOWLEDGE = 0x5;
|
||||
|
||||
/**
|
||||
* Constant <code>SLAVE_DEVICE_BUSY=0x6</code>
|
||||
*/
|
||||
public static final byte SLAVE_DEVICE_BUSY = 0x6;
|
||||
|
||||
/**
|
||||
* Constant <code>MEMORY_PARITY_ERROR=0x8</code>
|
||||
*/
|
||||
public static final byte MEMORY_PARITY_ERROR = 0x8;
|
||||
|
||||
/**
|
||||
* Constant <code>GATEWAY_PATH_UNAVAILABLE=0xa</code>
|
||||
*/
|
||||
public static final byte GATEWAY_PATH_UNAVAILABLE = 0xa;
|
||||
|
||||
/**
|
||||
* Constant <code>GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND=0xb</code>
|
||||
*/
|
||||
public static final byte GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0xb;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getExceptionMessage.
|
||||
* </p>
|
||||
*
|
||||
* @param id a byte.
|
||||
* @return a {@link String} object.
|
||||
*/
|
||||
public static String getExceptionMessage(byte id) {
|
||||
switch (id) {
|
||||
case ILLEGAL_FUNCTION:
|
||||
return "Illegal function";
|
||||
case ILLEGAL_DATA_ADDRESS:
|
||||
return "Illegal data address";
|
||||
case ILLEGAL_DATA_VALUE:
|
||||
return "Illegal data value";
|
||||
case SLAVE_DEVICE_FAILURE:
|
||||
return "Slave device failure";
|
||||
case ACKNOWLEDGE:
|
||||
return "Acknowledge";
|
||||
case SLAVE_DEVICE_BUSY:
|
||||
return "Slave device busy";
|
||||
case MEMORY_PARITY_ERROR:
|
||||
return "Memory parity error";
|
||||
case GATEWAY_PATH_UNAVAILABLE:
|
||||
return "Gateway path unavailable";
|
||||
case GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND:
|
||||
return "Gateway target device failed to respond";
|
||||
}
|
||||
return "Unknown exception code: " + id;
|
||||
}
|
||||
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.code;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* FunctionCode class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class FunctionCode {
|
||||
|
||||
/**
|
||||
* Constant <code>READ_COILS=1</code>
|
||||
*/
|
||||
public static final byte READ_COILS = 1;
|
||||
|
||||
/**
|
||||
* Constant <code>READ_DISCRETE_INPUTS=2</code>
|
||||
*/
|
||||
public static final byte READ_DISCRETE_INPUTS = 2;
|
||||
|
||||
/**
|
||||
* Constant <code>READ_HOLDING_REGISTERS=3</code>
|
||||
*/
|
||||
public static final byte READ_HOLDING_REGISTERS = 3;
|
||||
|
||||
/**
|
||||
* Constant <code>READ_INPUT_REGISTERS=4</code>
|
||||
*/
|
||||
public static final byte READ_INPUT_REGISTERS = 4;
|
||||
|
||||
/**
|
||||
* Constant <code>WRITE_COIL=5</code>
|
||||
*/
|
||||
public static final byte WRITE_COIL = 5;
|
||||
|
||||
/**
|
||||
* Constant <code>WRITE_REGISTER=6</code>
|
||||
*/
|
||||
public static final byte WRITE_REGISTER = 6;
|
||||
|
||||
/**
|
||||
* Constant <code>READ_EXCEPTION_STATUS=7</code>
|
||||
*/
|
||||
public static final byte READ_EXCEPTION_STATUS = 7;
|
||||
|
||||
/**
|
||||
* Constant <code>WRITE_COILS=15</code>
|
||||
*/
|
||||
public static final byte WRITE_COILS = 15;
|
||||
|
||||
/**
|
||||
* Constant <code>WRITE_REGISTERS=16</code>
|
||||
*/
|
||||
public static final byte WRITE_REGISTERS = 16;
|
||||
|
||||
/**
|
||||
* Constant <code>REPORT_SLAVE_ID=17</code>
|
||||
*/
|
||||
public static final byte REPORT_SLAVE_ID = 17;
|
||||
|
||||
/**
|
||||
* Constant <code>WRITE_MASK_REGISTER=22</code>
|
||||
*/
|
||||
public static final byte WRITE_MASK_REGISTER = 22;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* toString.
|
||||
* </p>
|
||||
*
|
||||
* @param code a byte.
|
||||
* @return a {@link String} object.
|
||||
*/
|
||||
public static String toString(byte code) {
|
||||
return Integer.toString(code & 0xff);
|
||||
}
|
||||
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.code;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* RegisterRange class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class RegisterRange {
|
||||
|
||||
/**
|
||||
* Constant <code>COIL_STATUS=1</code>
|
||||
*/
|
||||
public static final int COIL_STATUS = 1;
|
||||
|
||||
/**
|
||||
* Constant <code>INPUT_STATUS=2</code>
|
||||
*/
|
||||
public static final int INPUT_STATUS = 2;
|
||||
|
||||
/**
|
||||
* Constant <code>HOLDING_REGISTER=3</code>
|
||||
*/
|
||||
public static final int HOLDING_REGISTER = 3;
|
||||
|
||||
/**
|
||||
* Constant <code>INPUT_REGISTER=4</code>
|
||||
*/
|
||||
public static final int INPUT_REGISTER = 4;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getFrom.
|
||||
* </p>
|
||||
*
|
||||
* @param id a int.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int getFrom(int id) {
|
||||
switch (id) {
|
||||
case COIL_STATUS:
|
||||
return 0;
|
||||
case INPUT_STATUS:
|
||||
return 0x10000;
|
||||
case HOLDING_REGISTER:
|
||||
return 0x40000;
|
||||
case INPUT_REGISTER:
|
||||
return 0x30000;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getTo.
|
||||
* </p>
|
||||
*
|
||||
* @param id a int.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int getTo(int id) {
|
||||
switch (id) {
|
||||
case COIL_STATUS:
|
||||
return 0xffff;
|
||||
case INPUT_STATUS:
|
||||
return 0x1ffff;
|
||||
case HOLDING_REGISTER:
|
||||
return 0x4ffff;
|
||||
case INPUT_REGISTER:
|
||||
return 0x3ffff;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getReadFunctionCode.
|
||||
* </p>
|
||||
*
|
||||
* @param id a int.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int getReadFunctionCode(int id) {
|
||||
switch (id) {
|
||||
case COIL_STATUS:
|
||||
return FunctionCode.READ_COILS;
|
||||
case INPUT_STATUS:
|
||||
return FunctionCode.READ_DISCRETE_INPUTS;
|
||||
case HOLDING_REGISTER:
|
||||
return FunctionCode.READ_HOLDING_REGISTERS;
|
||||
case INPUT_REGISTER:
|
||||
return FunctionCode.READ_INPUT_REGISTERS;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ErrorResponseException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ErrorResponseException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
private final ModbusRequest originalRequest;
|
||||
|
||||
private final ModbusResponse errorResponse;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ErrorResponseException.
|
||||
* </p>
|
||||
*
|
||||
* @param originalRequest a {@link ModbusRequest} object.
|
||||
* @param errorResponse a {@link ModbusResponse} object.
|
||||
*/
|
||||
public ErrorResponseException(ModbusRequest originalRequest, ModbusResponse errorResponse) {
|
||||
this.originalRequest = originalRequest;
|
||||
this.errorResponse = errorResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>errorResponse</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusResponse} object.
|
||||
*/
|
||||
public ModbusResponse getErrorResponse() {
|
||||
return errorResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>originalRequest</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusRequest} object.
|
||||
*/
|
||||
public ModbusRequest getOriginalRequest() {
|
||||
return originalRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return errorResponse.getExceptionMessage();
|
||||
}
|
||||
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* IllegalDataAddressException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class IllegalDataAddressException extends ModbusTransportException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for IllegalDataAddressException.
|
||||
* </p>
|
||||
*/
|
||||
public IllegalDataAddressException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for IllegalDataAddressException.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public IllegalDataAddressException(int slaveId) {
|
||||
super(slaveId);
|
||||
}
|
||||
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* IllegalDataTypeException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class IllegalDataTypeException extends ModbusIdException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for IllegalDataTypeException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
*/
|
||||
public IllegalDataTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* IllegalFunctionException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class IllegalFunctionException extends ModbusTransportException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
private final byte functionCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for IllegalFunctionException.
|
||||
* </p>
|
||||
*
|
||||
* @param functionCode a byte.
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public IllegalFunctionException(byte functionCode, int slaveId) {
|
||||
super("Function code: 0x" + Integer.toHexString(functionCode & 0xff), slaveId);
|
||||
this.functionCode = functionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>functionCode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a byte.
|
||||
*/
|
||||
public byte getFunctionCode() {
|
||||
return functionCode;
|
||||
}
|
||||
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* IllegalSlaveIdException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class IllegalSlaveIdException extends ModbusIdException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for IllegalSlaveIdException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
*/
|
||||
public IllegalSlaveIdException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* InvalidDataConversionException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class InvalidDataConversionException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for InvalidDataConversionException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
*/
|
||||
public InvalidDataConversionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ModbusIdException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ModbusIdException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusIdException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
*/
|
||||
public ModbusIdException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusIdException.
|
||||
* </p>
|
||||
*
|
||||
* @param cause a {@link Throwable} object.
|
||||
*/
|
||||
public ModbusIdException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ModbusInitException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ModbusInitException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusInitException.
|
||||
* </p>
|
||||
*/
|
||||
public ModbusInitException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusInitException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
* @param cause a {@link Throwable} object.
|
||||
*/
|
||||
public ModbusInitException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusInitException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
*/
|
||||
public ModbusInitException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusInitException.
|
||||
* </p>
|
||||
*
|
||||
* @param cause a {@link Throwable} object.
|
||||
*/
|
||||
public ModbusInitException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.exception;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ModbusTransportException class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ModbusTransportException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
private final int slaveId;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*/
|
||||
public ModbusTransportException() {
|
||||
this.slaveId = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public ModbusTransportException(int slaveId) {
|
||||
this.slaveId = slaveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
* @param cause a {@link Throwable} object.
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public ModbusTransportException(String message, Throwable cause, int slaveId) {
|
||||
super(message, cause);
|
||||
this.slaveId = slaveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public ModbusTransportException(String message, int slaveId) {
|
||||
super(message);
|
||||
this.slaveId = slaveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*
|
||||
* @param message a {@link String} object.
|
||||
*/
|
||||
public ModbusTransportException(String message) {
|
||||
super(message);
|
||||
this.slaveId = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*
|
||||
* @param cause a {@link Throwable} object.
|
||||
*/
|
||||
public ModbusTransportException(Throwable cause) {
|
||||
super(cause);
|
||||
this.slaveId = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ModbusTransportException.
|
||||
* </p>
|
||||
*
|
||||
* @param cause a {@link Throwable} object.
|
||||
* @param slaveId a int.
|
||||
*/
|
||||
public ModbusTransportException(Throwable cause, int slaveId) {
|
||||
super(cause);
|
||||
this.slaveId = slaveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>slaveId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getSlaveId() {
|
||||
return slaveId;
|
||||
}
|
||||
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip;
|
||||
|
||||
import com.serotonin.modbus4j.msg.ModbusMessage;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract IpMessage class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public abstract class IpMessage {
|
||||
|
||||
protected final ModbusMessage modbusMessage;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for IpMessage.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusMessage a {@link ModbusMessage} object.
|
||||
*/
|
||||
public IpMessage(ModbusMessage modbusMessage) {
|
||||
this.modbusMessage = modbusMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>modbusMessage</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusMessage} object.
|
||||
*/
|
||||
public ModbusMessage getModbusMessage() {
|
||||
return modbusMessage;
|
||||
}
|
||||
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip;
|
||||
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingResponseMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingResponseMessage;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* IpMessageResponse interface.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public interface IpMessageResponse extends OutgoingResponseMessage, IncomingResponseMessage {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getModbusResponse.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusResponse} object.
|
||||
*/
|
||||
ModbusResponse getModbusResponse();
|
||||
|
||||
}
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* IpParameters class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class IpParameters {
|
||||
|
||||
private String host;
|
||||
|
||||
private int port = ModbusUtils.TCP_PORT;
|
||||
|
||||
private boolean encapsulated;
|
||||
|
||||
private Integer lingerTime = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>host</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link String} object.
|
||||
*/
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>host</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param host a {@link String} object.
|
||||
*/
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>port</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>port</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param port a int.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isEncapsulated.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isEncapsulated() {
|
||||
return encapsulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>encapsulated</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param encapsulated a boolean.
|
||||
*/
|
||||
public void setEncapsulated(boolean encapsulated) {
|
||||
this.encapsulated = encapsulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>linger</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public Integer getLingerTime() {
|
||||
return lingerTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>linger</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param lingerTime a int.
|
||||
*/
|
||||
public void setLingerTime(Integer lingerTime) {
|
||||
this.lingerTime = lingerTime;
|
||||
}
|
||||
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.encap;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.ip.IpMessage;
|
||||
import com.serotonin.modbus4j.msg.ModbusMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* EncapMessage class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class EncapMessage extends IpMessage {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for EncapMessage.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusMessage a {@link ModbusMessage} object.
|
||||
*/
|
||||
public EncapMessage(ModbusMessage modbusMessage) {
|
||||
super(modbusMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMessageData.
|
||||
* </p>
|
||||
*
|
||||
* @return an array of {@link byte} objects.
|
||||
*/
|
||||
public byte[] getMessageData() {
|
||||
ByteQueue msgQueue = new ByteQueue();
|
||||
|
||||
// Write the particular message.
|
||||
modbusMessage.write(msgQueue);
|
||||
|
||||
// Write the CRC
|
||||
ModbusUtils.pushShort(msgQueue, ModbusUtils.calculateCRC(modbusMessage));
|
||||
|
||||
// Return the data.
|
||||
return msgQueue.popAll();
|
||||
}
|
||||
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.encap;
|
||||
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* EncapMessageParser class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class EncapMessageParser extends BaseMessageParser {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for EncapMessageParser.
|
||||
* </p>
|
||||
*
|
||||
* @param master a boolean.
|
||||
*/
|
||||
public EncapMessageParser(boolean master) {
|
||||
super(master);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IncomingMessage parseMessageImpl(ByteQueue queue) throws Exception {
|
||||
if (master)
|
||||
return EncapMessageResponse.createEncapMessageResponse(queue);
|
||||
return EncapMessageRequest.createEncapMessageRequest(queue);
|
||||
}
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.encap;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* EncapMessageRequest class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class EncapMessageRequest extends EncapMessage implements OutgoingRequestMessage, IncomingRequestMessage {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for EncapMessageRequest.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusRequest a {@link ModbusRequest} object.
|
||||
*/
|
||||
public EncapMessageRequest(ModbusRequest modbusRequest) {
|
||||
super(modbusRequest);
|
||||
}
|
||||
|
||||
static EncapMessageRequest createEncapMessageRequest(ByteQueue queue) throws ModbusTransportException {
|
||||
// Create the modbus response.
|
||||
ModbusRequest request = ModbusRequest.createModbusRequest(queue);
|
||||
EncapMessageRequest encapRequest = new EncapMessageRequest(request);
|
||||
|
||||
// Check the CRC
|
||||
ModbusUtils.checkCRC(encapRequest.modbusMessage, queue);
|
||||
|
||||
return encapRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expectsResponse() {
|
||||
return modbusMessage.getSlaveId() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getModbusRequest.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusRequest} object.
|
||||
*/
|
||||
public ModbusRequest getModbusRequest() {
|
||||
return (ModbusRequest) modbusMessage;
|
||||
}
|
||||
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.encap;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.ip.IpMessageResponse;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* EncapMessageResponse class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class EncapMessageResponse extends EncapMessage implements IpMessageResponse {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for EncapMessageResponse.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusResponse a {@link ModbusResponse} object.
|
||||
*/
|
||||
public EncapMessageResponse(ModbusResponse modbusResponse) {
|
||||
super(modbusResponse);
|
||||
}
|
||||
|
||||
static EncapMessageResponse createEncapMessageResponse(ByteQueue queue) throws ModbusTransportException {
|
||||
// Create the modbus response.
|
||||
ModbusResponse response = ModbusResponse.createModbusResponse(queue);
|
||||
EncapMessageResponse encapResponse = new EncapMessageResponse(response);
|
||||
|
||||
// Check the CRC
|
||||
ModbusUtils.checkCRC(encapResponse.modbusMessage, queue);
|
||||
|
||||
return encapResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getModbusResponse.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusResponse} object.
|
||||
*/
|
||||
public ModbusResponse getModbusResponse() {
|
||||
return (ModbusResponse) modbusMessage;
|
||||
}
|
||||
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.encap;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusSlaveSet;
|
||||
import com.serotonin.modbus4j.base.BaseRequestHandler;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingResponseMessage;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* EncapRequestHandler class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class EncapRequestHandler extends BaseRequestHandler {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for EncapRequestHandler.
|
||||
* </p>
|
||||
*
|
||||
* @param slave a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public EncapRequestHandler(ModbusSlaveSet slave) {
|
||||
super(slave);
|
||||
}
|
||||
|
||||
public OutgoingResponseMessage handleRequest(IncomingRequestMessage req) throws Exception {
|
||||
EncapMessageRequest tcpRequest = (EncapMessageRequest) req;
|
||||
ModbusRequest request = tcpRequest.getModbusRequest();
|
||||
ModbusResponse response = handleRequestImpl(request);
|
||||
if (response == null)
|
||||
return null;
|
||||
return new EncapMessageResponse(response);
|
||||
}
|
||||
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.ip.encap;
|
||||
|
||||
import com.serotonin.modbus4j.ip.IpMessage;
|
||||
import com.serotonin.modbus4j.msg.ModbusMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingResponseMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.WaitingRoomKey;
|
||||
import com.serotonin.modbus4j.sero.messaging.WaitingRoomKeyFactory;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* EncapWaitingRoomKeyFactory class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class EncapWaitingRoomKeyFactory implements WaitingRoomKeyFactory {
|
||||
|
||||
@Override
|
||||
public WaitingRoomKey createWaitingRoomKey(OutgoingRequestMessage request) {
|
||||
return createWaitingRoomKey(((IpMessage) request).getModbusMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaitingRoomKey createWaitingRoomKey(IncomingResponseMessage response) {
|
||||
return createWaitingRoomKey(((IpMessage) response).getModbusMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createWaitingRoomKey.
|
||||
* </p>
|
||||
*
|
||||
* @param msg a {@link ModbusMessage} object.
|
||||
* @return a {@link WaitingRoomKey} object.
|
||||
*/
|
||||
public WaitingRoomKey createWaitingRoomKey(ModbusMessage msg) {
|
||||
return new EncapWaitingRoomKey(msg.getSlaveId(), msg.getFunctionCode());
|
||||
}
|
||||
|
||||
class EncapWaitingRoomKey implements WaitingRoomKey {
|
||||
|
||||
private final int slaveId;
|
||||
|
||||
private final byte functionCode;
|
||||
|
||||
public EncapWaitingRoomKey(int slaveId, byte functionCode) {
|
||||
this.slaveId = slaveId;
|
||||
this.functionCode = functionCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + functionCode;
|
||||
result = prime * result + slaveId;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
EncapWaitingRoomKey other = (EncapWaitingRoomKey) obj;
|
||||
if (functionCode != other.functionCode)
|
||||
return false;
|
||||
if (slaveId != other.slaveId)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-382
@@ -1,382 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.ip.listener;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusMaster;
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.ip.IpMessageResponse;
|
||||
import com.serotonin.modbus4j.ip.IpParameters;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageRequest;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapWaitingRoomKeyFactory;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageParser;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageRequest;
|
||||
import com.serotonin.modbus4j.ip.xa.XaWaitingRoomKeyFactory;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.EpollStreamTransport;
|
||||
import com.serotonin.modbus4j.sero.messaging.MessageControl;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.StreamTransport;
|
||||
import com.serotonin.modbus4j.sero.messaging.Transport;
|
||||
import com.serotonin.modbus4j.sero.messaging.WaitingRoomKeyFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.BindException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* TcpListener class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class TcpListener extends ModbusMaster {
|
||||
|
||||
// Configuration fields.
|
||||
private final Log LOG = LogFactory.getLog(TcpListener.class);
|
||||
|
||||
private final IpParameters ipParameters;
|
||||
|
||||
private short nextTransactionId = 0;
|
||||
|
||||
private short retries = 0;
|
||||
|
||||
// Runtime fields.
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
private Socket socket;
|
||||
|
||||
private ExecutorService executorService;
|
||||
|
||||
private ListenerConnectionHandler handler;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpListener.
|
||||
* </p>
|
||||
* <p>
|
||||
* Will validate response to ensure that slaveId == response slaveId if encapsulated
|
||||
* is true
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
*/
|
||||
public TcpListener(IpParameters params) {
|
||||
LOG.debug("Creating TcpListener in port " + params.getPort());
|
||||
ipParameters = params;
|
||||
connected = false;
|
||||
validateResponse = ipParameters.isEncapsulated();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("TcpListener created! Port: " + ipParameters.getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Control to validate response to ensure that slaveId == response slaveId
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param validateResponse a boolean.
|
||||
*/
|
||||
public TcpListener(IpParameters params, boolean validateResponse) {
|
||||
LOG.debug("Creating TcpListener in port " + params.getPort());
|
||||
ipParameters = params;
|
||||
connected = false;
|
||||
this.validateResponse = validateResponse;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("TcpListener created! Port: " + ipParameters.getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>nextTransactionId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a short.
|
||||
*/
|
||||
protected short getNextTransactionId() {
|
||||
return nextTransactionId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void init() throws ModbusInitException {
|
||||
LOG.debug("Init TcpListener Port: " + ipParameters.getPort());
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
startListener();
|
||||
initialized = true;
|
||||
LOG.warn("Initialized Port: " + ipParameters.getPort());
|
||||
}
|
||||
|
||||
private void startListener() throws ModbusInitException {
|
||||
try {
|
||||
if (handler != null) {
|
||||
LOG.debug("handler not null!!!");
|
||||
}
|
||||
handler = new ListenerConnectionHandler(socket);
|
||||
LOG.debug("Init handler thread");
|
||||
executorService.execute(handler);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Error initializing TcpListener ", e);
|
||||
throw new ModbusInitException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void destroy() {
|
||||
LOG.debug("Destroy TCPListener Port: " + ipParameters.getPort());
|
||||
// Close the serverSocket first to prevent new messages.
|
||||
try {
|
||||
if (serverSocket != null)
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Error closing socket" + e.getLocalizedMessage());
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
|
||||
// Close all open connections.
|
||||
if (handler != null) {
|
||||
handler.closeConnection();
|
||||
}
|
||||
|
||||
// Terminate Listener
|
||||
terminateListener();
|
||||
initialized = false;
|
||||
LOG.debug("TCPListener destroyed, Port: " + ipParameters.getPort());
|
||||
}
|
||||
|
||||
private void terminateListener() {
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(300, TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Handler Thread terminated, Port: " + ipParameters.getPort());
|
||||
} catch (InterruptedException e) {
|
||||
LOG.debug("Error terminating executorService - " + e.getLocalizedMessage());
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
handler = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException {
|
||||
|
||||
if (!connected) {
|
||||
LOG.debug("No connection in Port: " + ipParameters.getPort());
|
||||
throw new ModbusTransportException(new Exception("TCP Listener has no active connection!"),
|
||||
request.getSlaveId());
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
LOG.debug("Listener already terminated " + ipParameters.getPort());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the modbus request in a ip request.
|
||||
OutgoingRequestMessage ipRequest;
|
||||
if (ipParameters.isEncapsulated()) {
|
||||
ipRequest = new EncapMessageRequest(request);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : Arrays.copyOfRange(ipRequest.getMessageData(), 0, ipRequest.getMessageData().length)) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
LOG.debug("Encap Request: " + sb.toString());
|
||||
} else {
|
||||
ipRequest = new XaMessageRequest(request, getNextTransactionId());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : Arrays.copyOfRange(ipRequest.getMessageData(), 0, ipRequest.getMessageData().length)) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
LOG.debug("Xa Request: " + sb.toString());
|
||||
}
|
||||
|
||||
// Send the request to get the response.
|
||||
IpMessageResponse ipResponse;
|
||||
try {
|
||||
// Send data via handler!
|
||||
handler.conn.DEBUG = true;
|
||||
ipResponse = (IpMessageResponse) handler.conn.send(ipRequest);
|
||||
if (ipResponse == null) {
|
||||
throw new ModbusTransportException(new Exception("No valid response from slave!"),
|
||||
request.getSlaveId());
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(), 0, ipResponse.getMessageData().length)) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
LOG.debug("Response: " + sb.toString());
|
||||
return ipResponse.getModbusResponse();
|
||||
} catch (Exception e) {
|
||||
LOG.debug(e.getLocalizedMessage() + ", Port: " + ipParameters.getPort() + ", retries: " + retries);
|
||||
if (retries < 10 && !e.getLocalizedMessage().contains("Broken")) {
|
||||
retries++;
|
||||
} else {
|
||||
/*
|
||||
* To recover from a Broken Pipe, the only way is to restart serverSocket
|
||||
*/
|
||||
LOG.debug("Restarting Socket, Port: " + ipParameters.getPort() + ", retries: " + retries);
|
||||
|
||||
// Close the serverSocket first to prevent new messages.
|
||||
try {
|
||||
if (serverSocket != null)
|
||||
serverSocket.close();
|
||||
} catch (IOException e2) {
|
||||
LOG.debug("Error closing socket" + e2.getLocalizedMessage(), e);
|
||||
getExceptionHandler().receivedException(e2);
|
||||
}
|
||||
|
||||
// Close all open connections.
|
||||
if (handler != null) {
|
||||
handler.closeConnection();
|
||||
terminateListener();
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
LOG.debug("Listener already terminated " + ipParameters.getPort());
|
||||
return null;
|
||||
}
|
||||
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
try {
|
||||
startListener();
|
||||
} catch (Exception e2) {
|
||||
LOG.warn("Error trying to restart socket" + e2.getLocalizedMessage(), e);
|
||||
throw new ModbusTransportException(e2, request.getSlaveId());
|
||||
}
|
||||
retries = 0;
|
||||
}
|
||||
LOG.warn("Error sending request, Port: " + ipParameters.getPort() + ", msg: " + e.getMessage());
|
||||
// Simple send error!
|
||||
throw new ModbusTransportException(e, request.getSlaveId());
|
||||
}
|
||||
}
|
||||
|
||||
class ListenerConnectionHandler implements Runnable {
|
||||
|
||||
private Socket socket;
|
||||
|
||||
private Transport transport;
|
||||
|
||||
private MessageControl conn;
|
||||
|
||||
private BaseMessageParser ipMessageParser;
|
||||
|
||||
private WaitingRoomKeyFactory waitingRoomKeyFactory;
|
||||
|
||||
public ListenerConnectionHandler(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.debug(" ListenerConnectionHandler::run() ");
|
||||
|
||||
if (ipParameters.isEncapsulated()) {
|
||||
ipMessageParser = new EncapMessageParser(true);
|
||||
waitingRoomKeyFactory = new EncapWaitingRoomKeyFactory();
|
||||
} else {
|
||||
ipMessageParser = new XaMessageParser(true);
|
||||
waitingRoomKeyFactory = new XaWaitingRoomKeyFactory();
|
||||
}
|
||||
|
||||
try {
|
||||
acceptConnection();
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Error in TCP Listener! - " + e.getLocalizedMessage(), e);
|
||||
conn.close();
|
||||
closeConnection();
|
||||
getExceptionHandler().receivedException(new ModbusInitException(e));
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptConnection() throws IOException, BindException {
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
try {
|
||||
serverSocket = new ServerSocket(ipParameters.getPort());
|
||||
LOG.debug("Start Accept on port: " + ipParameters.getPort());
|
||||
socket = serverSocket.accept();
|
||||
LOG.info("Connected: " + socket.getInetAddress() + ":" + ipParameters.getPort());
|
||||
|
||||
if (getePoll() != null)
|
||||
transport = new EpollStreamTransport(socket.getInputStream(), socket.getOutputStream(),
|
||||
getePoll());
|
||||
else
|
||||
transport = new StreamTransport(socket.getInputStream(), socket.getOutputStream());
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Open connection failed on port " + ipParameters.getPort() + ", caused by "
|
||||
+ e.getLocalizedMessage(), e);
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
continue;
|
||||
} else if (e.getLocalizedMessage().contains("closed")) {
|
||||
return;
|
||||
} else if (e instanceof BindException) {
|
||||
closeConnection();
|
||||
throw (BindException) e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn = getMessageControl();
|
||||
conn.setExceptionHandler(getExceptionHandler());
|
||||
conn.DEBUG = true;
|
||||
conn.start(transport, ipMessageParser, null, waitingRoomKeyFactory);
|
||||
if (getePoll() == null)
|
||||
((StreamTransport) transport).start("Modbus4J TcpMaster");
|
||||
connected = true;
|
||||
}
|
||||
|
||||
void closeConnection() {
|
||||
if (conn != null) {
|
||||
LOG.debug("Closing Message Control on port: " + ipParameters.getPort());
|
||||
closeMessageControl(conn);
|
||||
}
|
||||
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Error closing socket on port " + ipParameters.getPort() + ". " + e.getLocalizedMessage());
|
||||
getExceptionHandler().receivedException(new ModbusInitException(e));
|
||||
}
|
||||
connected = false;
|
||||
conn = null;
|
||||
socket = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-362
@@ -1,362 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.tcp;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusMaster;
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.ip.IpMessageResponse;
|
||||
import com.serotonin.modbus4j.ip.IpParameters;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageRequest;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapWaitingRoomKeyFactory;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageParser;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageRequest;
|
||||
import com.serotonin.modbus4j.ip.xa.XaWaitingRoomKeyFactory;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.EpollStreamTransport;
|
||||
import com.serotonin.modbus4j.sero.messaging.MessageControl;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.StreamTransport;
|
||||
import com.serotonin.modbus4j.sero.messaging.Transport;
|
||||
import com.serotonin.modbus4j.sero.messaging.WaitingRoomKeyFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* TcpMaster class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class TcpMaster extends ModbusMaster {
|
||||
|
||||
// Configuration fields.
|
||||
private final Log LOG = LogFactory.getLog(TcpMaster.class);
|
||||
|
||||
private final IpParameters ipParameters;
|
||||
|
||||
private final boolean keepAlive;
|
||||
|
||||
private final boolean autoIncrementTransactionId;
|
||||
|
||||
private final Integer lingerTime;
|
||||
|
||||
private short nextTransactionId = 0;
|
||||
|
||||
// Runtime fields.
|
||||
private Socket socket;
|
||||
|
||||
private Transport transport;
|
||||
|
||||
private MessageControl conn;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
* @param autoIncrementTransactionId a boolean.
|
||||
* @param validateResponse - confirm that requested slave id is the same in the
|
||||
* response
|
||||
* @param lingerTime The setting only affects socket close.
|
||||
*/
|
||||
public TcpMaster(IpParameters params, boolean keepAlive, boolean autoIncrementTransactionId,
|
||||
boolean validateResponse, Integer lingerTime) {
|
||||
this.ipParameters = params;
|
||||
this.keepAlive = keepAlive;
|
||||
this.autoIncrementTransactionId = autoIncrementTransactionId;
|
||||
this.lingerTime = lingerTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpMaster.
|
||||
* </p>
|
||||
* <p>
|
||||
* Default to lingerTime disabled
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
* @param autoIncrementTransactionId a boolean.
|
||||
* @param validateResponse - confirm that requested slave id is the same in the
|
||||
* response
|
||||
*/
|
||||
public TcpMaster(IpParameters params, boolean keepAlive, boolean autoIncrementTransactionId,
|
||||
boolean validateResponse) {
|
||||
this(params, keepAlive, autoIncrementTransactionId, validateResponse, -1);
|
||||
// this.ipParameters = params;
|
||||
// this.keepAlive = keepAlive;
|
||||
// this.autoIncrementTransactionId = autoIncrementTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpMaster.
|
||||
* </p>
|
||||
* Default to not validating the slave id in responses Default to lingerTime disabled
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
* @param autoIncrementTransactionId a boolean.
|
||||
*/
|
||||
public TcpMaster(IpParameters params, boolean keepAlive, boolean autoIncrementTransactionId) {
|
||||
this(params, keepAlive, autoIncrementTransactionId, false, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpMaster.
|
||||
* </p>
|
||||
* <p>
|
||||
* Default to auto increment transaction id Default to not validating the slave id in
|
||||
* responses Default to lingerTime disabled
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
* @param lingerTime an Integer. The setting only affects socket close.
|
||||
*/
|
||||
public TcpMaster(IpParameters params, boolean keepAlive, Integer lingerTime) {
|
||||
this(params, keepAlive, true, false, lingerTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpMaster.
|
||||
* </p>
|
||||
* <p>
|
||||
* Default to auto increment transaction id Default to not validating the slave id in
|
||||
* responses Default to lingerTime disabled
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
* @param keepAlive a boolean.
|
||||
*/
|
||||
public TcpMaster(IpParameters params, boolean keepAlive) {
|
||||
this(params, keepAlive, true, false, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>nextTransactionId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a short.
|
||||
*/
|
||||
protected short getNextTransactionId() {
|
||||
return nextTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>nextTransactionId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param id a short.
|
||||
*/
|
||||
public void setNextTransactionId(short id) {
|
||||
this.nextTransactionId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void init() throws ModbusInitException {
|
||||
try {
|
||||
if (keepAlive)
|
||||
openConnection();
|
||||
} catch (Exception e) {
|
||||
throw new ModbusInitException(e);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void destroy() {
|
||||
closeConnection();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException {
|
||||
try {
|
||||
// Check if we need to open the connection.
|
||||
if (!keepAlive)
|
||||
openConnection();
|
||||
|
||||
if (conn == null) {
|
||||
LOG.debug("Connection null: " + ipParameters.getPort());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
closeConnection();
|
||||
throw new ModbusTransportException(e, request.getSlaveId());
|
||||
}
|
||||
|
||||
// Wrap the modbus request in a ip request.
|
||||
OutgoingRequestMessage ipRequest;
|
||||
if (ipParameters.isEncapsulated())
|
||||
ipRequest = new EncapMessageRequest(request);
|
||||
else {
|
||||
if (autoIncrementTransactionId)
|
||||
this.nextTransactionId++;
|
||||
ipRequest = new XaMessageRequest(request, getNextTransactionId());
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : Arrays.copyOfRange(ipRequest.getMessageData(), 0, ipRequest.getMessageData().length)) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
LOG.debug("Encap Request: " + sb.toString());
|
||||
}
|
||||
|
||||
// Send the request to get the response.
|
||||
IpMessageResponse ipResponse;
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Sending on port: " + ipParameters.getPort());
|
||||
}
|
||||
try {
|
||||
if (conn == null) {
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Connection null: " + ipParameters.getPort());
|
||||
}
|
||||
ipResponse = (IpMessageResponse) conn.send(ipRequest);
|
||||
if (ipResponse == null)
|
||||
return null;
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(), 0, ipResponse.getMessageData().length)) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
LOG.debug("Response: " + sb.toString());
|
||||
}
|
||||
return ipResponse.getModbusResponse();
|
||||
} catch (Exception e) {
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Exception sending message", e);
|
||||
if (keepAlive) {
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("KeepAlive - reconnect!");
|
||||
// The connection may have been reset, so try to reopen it and attempt the
|
||||
// message again.
|
||||
try {
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Modbus4J: Keep-alive connection may have been reset. Attempting to re-open.");
|
||||
openConnection();
|
||||
ipResponse = (IpMessageResponse) conn.send(ipRequest);
|
||||
if (ipResponse == null)
|
||||
return null;
|
||||
if (LOG.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(), 0,
|
||||
ipResponse.getMessageData().length)) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
LOG.debug("Response: " + sb.toString());
|
||||
}
|
||||
return ipResponse.getModbusResponse();
|
||||
} catch (Exception e2) {
|
||||
closeConnection();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Exception re-sending message", e);
|
||||
throw new ModbusTransportException(e2, request.getSlaveId());
|
||||
}
|
||||
}
|
||||
|
||||
throw new ModbusTransportException(e, request.getSlaveId());
|
||||
} finally {
|
||||
// Check if we should close the connection.
|
||||
if (!keepAlive)
|
||||
closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
private void openConnection() throws IOException {
|
||||
// Make sure any existing connection is closed.
|
||||
closeConnection();
|
||||
|
||||
Integer soLinger = getLingerTime();
|
||||
|
||||
socket = new Socket();
|
||||
socket.setSoTimeout(getTimeout());
|
||||
if (soLinger == null || soLinger < 0)// any null or negative will disable
|
||||
// SO_Linger
|
||||
socket.setSoLinger(false, 0);
|
||||
else
|
||||
socket.setSoLinger(true, soLinger);
|
||||
socket.connect(new InetSocketAddress(ipParameters.getHost(), ipParameters.getPort()), getTimeout());
|
||||
if (getePoll() != null)
|
||||
transport = new EpollStreamTransport(socket.getInputStream(), socket.getOutputStream(), getePoll());
|
||||
else
|
||||
transport = new StreamTransport(socket.getInputStream(), socket.getOutputStream());
|
||||
|
||||
BaseMessageParser ipMessageParser;
|
||||
WaitingRoomKeyFactory waitingRoomKeyFactory;
|
||||
if (ipParameters.isEncapsulated()) {
|
||||
ipMessageParser = new EncapMessageParser(true);
|
||||
waitingRoomKeyFactory = new EncapWaitingRoomKeyFactory();
|
||||
} else {
|
||||
ipMessageParser = new XaMessageParser(true);
|
||||
waitingRoomKeyFactory = new XaWaitingRoomKeyFactory();
|
||||
}
|
||||
|
||||
conn = getMessageControl();
|
||||
conn.start(transport, ipMessageParser, null, waitingRoomKeyFactory);
|
||||
if (getePoll() == null)
|
||||
((StreamTransport) transport).start("Modbus4J TcpMaster");
|
||||
}
|
||||
|
||||
private void closeConnection() {
|
||||
closeMessageControl(conn);
|
||||
try {
|
||||
if (socket != null)
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
|
||||
conn = null;
|
||||
socket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>lingerTime</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return an Integer.
|
||||
*/
|
||||
public Integer getLingerTime() {
|
||||
return lingerTime;
|
||||
}
|
||||
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.tcp;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusSlaveSet;
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.base.BaseRequestHandler;
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapRequestHandler;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageParser;
|
||||
import com.serotonin.modbus4j.ip.xa.XaRequestHandler;
|
||||
import com.serotonin.modbus4j.sero.messaging.MessageControl;
|
||||
import com.serotonin.modbus4j.sero.messaging.TestableTransport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* TcpSlave class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class TcpSlave extends ModbusSlaveSet {
|
||||
|
||||
final boolean encapsulated;
|
||||
|
||||
final ExecutorService executorService;
|
||||
|
||||
final List<TcpConnectionHandler> listConnections = new ArrayList<>();
|
||||
|
||||
// Configuration fields
|
||||
private final int port;
|
||||
|
||||
// Runtime fields.
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param encapsulated a boolean.
|
||||
*/
|
||||
public TcpSlave(boolean encapsulated) {
|
||||
this(ModbusUtils.TCP_PORT, encapsulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for TcpSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param port a int.
|
||||
* @param encapsulated a boolean.
|
||||
*/
|
||||
public TcpSlave(int port, boolean encapsulated) {
|
||||
this.port = port;
|
||||
this.encapsulated = encapsulated;
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws ModbusInitException {
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
|
||||
Socket socket;
|
||||
while (true) {
|
||||
socket = serverSocket.accept();
|
||||
TcpConnectionHandler handler = new TcpConnectionHandler(socket);
|
||||
executorService.execute(handler);
|
||||
synchronized (listConnections) {
|
||||
listConnections.add(handler);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ModbusInitException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// Close the socket first to prevent new messages.
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
|
||||
// Close all open connections.
|
||||
synchronized (listConnections) {
|
||||
for (TcpConnectionHandler tch : listConnections)
|
||||
tch.kill();
|
||||
listConnections.clear();
|
||||
}
|
||||
|
||||
// Now close the executor service.
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(3, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
class TcpConnectionHandler implements Runnable {
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private TestableTransport transport;
|
||||
|
||||
private MessageControl conn;
|
||||
|
||||
TcpConnectionHandler(Socket socket) throws ModbusInitException {
|
||||
this.socket = socket;
|
||||
try {
|
||||
transport = new TestableTransport(socket.getInputStream(), socket.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new ModbusInitException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
BaseMessageParser messageParser;
|
||||
BaseRequestHandler requestHandler;
|
||||
|
||||
if (encapsulated) {
|
||||
messageParser = new EncapMessageParser(false);
|
||||
requestHandler = new EncapRequestHandler(TcpSlave.this);
|
||||
} else {
|
||||
messageParser = new XaMessageParser(false);
|
||||
requestHandler = new XaRequestHandler(TcpSlave.this);
|
||||
}
|
||||
|
||||
conn = new MessageControl();
|
||||
conn.setExceptionHandler(getExceptionHandler());
|
||||
|
||||
try {
|
||||
conn.start(transport, messageParser, requestHandler, null);
|
||||
executorService.execute(transport);
|
||||
} catch (IOException e) {
|
||||
getExceptionHandler().receivedException(new ModbusInitException(e));
|
||||
}
|
||||
|
||||
// Monitor the socket to detect when it gets closed.
|
||||
while (true) {
|
||||
try {
|
||||
transport.testInputStream();
|
||||
} catch (IOException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
conn.close();
|
||||
kill();
|
||||
synchronized (listConnections) {
|
||||
listConnections.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
void kill() {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
getExceptionHandler().receivedException(new ModbusInitException(e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-195
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.udp;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusMaster;
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.ip.IpMessageResponse;
|
||||
import com.serotonin.modbus4j.ip.IpParameters;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageRequest;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageParser;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* UdpMaster class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class UdpMaster extends ModbusMaster {
|
||||
|
||||
private static final int MESSAGE_LENGTH = 1024;
|
||||
|
||||
private final IpParameters ipParameters;
|
||||
|
||||
private short nextTransactionId = 0;
|
||||
|
||||
// Runtime fields.
|
||||
private BaseMessageParser messageParser;
|
||||
|
||||
private DatagramSocket socket;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for UdpMaster.
|
||||
* </p>
|
||||
* <p>
|
||||
* Default to not validating the slave id in responses
|
||||
*
|
||||
* @param params a {@link IpParameters} object.
|
||||
*/
|
||||
public UdpMaster(IpParameters params) {
|
||||
this(params, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for UdpMaster.
|
||||
* </p>
|
||||
*
|
||||
* @param params
|
||||
* @param validateResponse - confirm that requested slave id is the same in the
|
||||
* response
|
||||
*/
|
||||
public UdpMaster(IpParameters params, boolean validateResponse) {
|
||||
ipParameters = params;
|
||||
this.validateResponse = validateResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>nextTransactionId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a short.
|
||||
*/
|
||||
protected short getNextTransactionId() {
|
||||
return nextTransactionId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws ModbusInitException {
|
||||
if (ipParameters.isEncapsulated())
|
||||
messageParser = new EncapMessageParser(true);
|
||||
else
|
||||
messageParser = new XaMessageParser(true);
|
||||
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
socket.setSoTimeout(getTimeout());
|
||||
} catch (SocketException e) {
|
||||
throw new ModbusInitException(e);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
socket.close();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException {
|
||||
// Wrap the modbus request in an ip request.
|
||||
OutgoingRequestMessage ipRequest;
|
||||
if (ipParameters.isEncapsulated())
|
||||
ipRequest = new EncapMessageRequest(request);
|
||||
else
|
||||
ipRequest = new XaMessageRequest(request, getNextTransactionId());
|
||||
|
||||
IpMessageResponse ipResponse;
|
||||
|
||||
try {
|
||||
int attempts = getRetries() + 1;
|
||||
|
||||
while (true) {
|
||||
// Send the request.
|
||||
sendImpl(ipRequest);
|
||||
|
||||
if (!ipRequest.expectsResponse())
|
||||
return null;
|
||||
|
||||
// Receive the response.
|
||||
try {
|
||||
ipResponse = receiveImpl();
|
||||
} catch (SocketTimeoutException e) {
|
||||
attempts--;
|
||||
if (attempts > 0)
|
||||
// Try again.
|
||||
continue;
|
||||
|
||||
throw new ModbusTransportException(e, request.getSlaveId());
|
||||
}
|
||||
|
||||
// We got the response
|
||||
break;
|
||||
}
|
||||
|
||||
return ipResponse.getModbusResponse();
|
||||
} catch (IOException e) {
|
||||
throw new ModbusTransportException(e, request.getSlaveId());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendImpl(OutgoingRequestMessage request) throws IOException {
|
||||
byte[] data = request.getMessageData();
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(ipParameters.getHost()),
|
||||
ipParameters.getPort());
|
||||
socket.send(packet);
|
||||
}
|
||||
|
||||
private IpMessageResponse receiveImpl() throws IOException, ModbusTransportException {
|
||||
DatagramPacket packet = new DatagramPacket(new byte[MESSAGE_LENGTH], MESSAGE_LENGTH);
|
||||
socket.receive(packet);
|
||||
|
||||
// We could verify that the packet was received from the same address to which the
|
||||
// request was sent,
|
||||
// but let's not bother with that yet.
|
||||
|
||||
ByteQueue queue = new ByteQueue(packet.getData(), 0, packet.getLength());
|
||||
IpMessageResponse response;
|
||||
try {
|
||||
response = (IpMessageResponse) messageParser.parseMessage(queue);
|
||||
} catch (Exception e) {
|
||||
throw new ModbusTransportException(e);
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
throw new ModbusTransportException("Invalid response received");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.udp;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusSlaveSet;
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.base.BaseRequestHandler;
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.exception.ModbusInitException;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapMessageParser;
|
||||
import com.serotonin.modbus4j.ip.encap.EncapRequestHandler;
|
||||
import com.serotonin.modbus4j.ip.xa.XaMessageParser;
|
||||
import com.serotonin.modbus4j.ip.xa.XaRequestHandler;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingResponseMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* UdpSlave class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class UdpSlave extends ModbusSlaveSet {
|
||||
|
||||
final BaseMessageParser messageParser;
|
||||
|
||||
final BaseRequestHandler requestHandler;
|
||||
|
||||
// Configuration fields
|
||||
private final int port;
|
||||
|
||||
private final ExecutorService executorService;
|
||||
|
||||
// Runtime fields.
|
||||
DatagramSocket datagramSocket;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for UdpSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param encapsulated a boolean.
|
||||
*/
|
||||
public UdpSlave(boolean encapsulated) {
|
||||
this(ModbusUtils.TCP_PORT, encapsulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for UdpSlave.
|
||||
* </p>
|
||||
*
|
||||
* @param port a int.
|
||||
* @param encapsulated a boolean.
|
||||
*/
|
||||
public UdpSlave(int port, boolean encapsulated) {
|
||||
this.port = port;
|
||||
|
||||
if (encapsulated) {
|
||||
messageParser = new EncapMessageParser(false);
|
||||
requestHandler = new EncapRequestHandler(this);
|
||||
} else {
|
||||
messageParser = new XaMessageParser(false);
|
||||
requestHandler = new XaRequestHandler(this);
|
||||
}
|
||||
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws ModbusInitException {
|
||||
try {
|
||||
datagramSocket = new DatagramSocket(port);
|
||||
|
||||
DatagramPacket datagramPacket;
|
||||
while (true) {
|
||||
datagramPacket = new DatagramPacket(new byte[1028], 1028);
|
||||
datagramSocket.receive(datagramPacket);
|
||||
|
||||
UdpConnectionHandler handler = new UdpConnectionHandler(datagramPacket);
|
||||
executorService.execute(handler);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ModbusInitException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// Close the socket first to prevent new messages.
|
||||
datagramSocket.close();
|
||||
|
||||
// Close the executor service.
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(3, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// int getSlaveId() {
|
||||
// return slaveId;
|
||||
// }
|
||||
//
|
||||
// ProcessImage getProcessImage() {
|
||||
// return processImage;
|
||||
// }
|
||||
|
||||
class UdpConnectionHandler implements Runnable {
|
||||
|
||||
private final DatagramPacket requestPacket;
|
||||
|
||||
UdpConnectionHandler(DatagramPacket requestPacket) {
|
||||
this.requestPacket = requestPacket;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
ByteQueue requestQueue = new ByteQueue(requestPacket.getData(), 0, requestPacket.getLength());
|
||||
|
||||
// Parse the request data and get the response.
|
||||
IncomingMessage request = messageParser.parseMessage(requestQueue);
|
||||
OutgoingResponseMessage response = requestHandler.handleRequest((IncomingRequestMessage) request);
|
||||
|
||||
if (response == null)
|
||||
return;
|
||||
|
||||
// Create a response packet.
|
||||
byte[] responseData = response.getMessageData();
|
||||
DatagramPacket responsePacket = new DatagramPacket(responseData, responseData.length,
|
||||
requestPacket.getAddress(), requestPacket.getPort());
|
||||
|
||||
// Send the response back.
|
||||
datagramSocket.send(responsePacket);
|
||||
} catch (Exception e) {
|
||||
getExceptionHandler().receivedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.xa;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.ip.IpMessage;
|
||||
import com.serotonin.modbus4j.msg.ModbusMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XaMessage class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class XaMessage extends IpMessage {
|
||||
|
||||
protected final int transactionId;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for XaMessage.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusMessage a {@link ModbusMessage} object.
|
||||
* @param transactionId a int.
|
||||
*/
|
||||
public XaMessage(ModbusMessage modbusMessage, int transactionId) {
|
||||
super(modbusMessage);
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMessageData.
|
||||
* </p>
|
||||
*
|
||||
* @return an array of {@link byte} objects.
|
||||
*/
|
||||
public byte[] getMessageData() {
|
||||
ByteQueue msgQueue = new ByteQueue();
|
||||
|
||||
// Write the particular message.
|
||||
modbusMessage.write(msgQueue);
|
||||
|
||||
// Create the XA message
|
||||
ByteQueue xaQueue = new ByteQueue();
|
||||
ModbusUtils.pushShort(xaQueue, transactionId);
|
||||
ModbusUtils.pushShort(xaQueue, ModbusUtils.IP_PROTOCOL_ID);
|
||||
ModbusUtils.pushShort(xaQueue, msgQueue.size());
|
||||
xaQueue.push(msgQueue);
|
||||
|
||||
// Return the data.
|
||||
return xaQueue.popAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>transactionId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModbusMessage getModbusMessage() {
|
||||
return modbusMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "XaMessage [transactionId=" + transactionId + ", message=" + modbusMessage + "]";
|
||||
}
|
||||
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.xa;
|
||||
|
||||
import com.serotonin.modbus4j.base.BaseMessageParser;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XaMessageParser class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class XaMessageParser extends BaseMessageParser {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for XaMessageParser.
|
||||
* </p>
|
||||
*
|
||||
* @param master a boolean.
|
||||
*/
|
||||
public XaMessageParser(boolean master) {
|
||||
super(master);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IncomingMessage parseMessageImpl(ByteQueue queue) throws Exception {
|
||||
if (master)
|
||||
return XaMessageResponse.createXaMessageResponse(queue);
|
||||
return XaMessageRequest.createXaMessageRequest(queue);
|
||||
}
|
||||
|
||||
}
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.xa;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XaMessageRequest class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class XaMessageRequest extends XaMessage implements OutgoingRequestMessage, IncomingRequestMessage {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for XaMessageRequest.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusRequest a {@link ModbusRequest} object.
|
||||
* @param transactionId a int.
|
||||
*/
|
||||
public XaMessageRequest(ModbusRequest modbusRequest, int transactionId) {
|
||||
super(modbusRequest, transactionId);
|
||||
}
|
||||
|
||||
static XaMessageRequest createXaMessageRequest(ByteQueue queue) throws ModbusTransportException {
|
||||
// Remove the XA header
|
||||
int transactionId = ModbusUtils.popShort(queue);
|
||||
int protocolId = ModbusUtils.popShort(queue);
|
||||
if (protocolId != ModbusUtils.IP_PROTOCOL_ID)
|
||||
throw new ModbusTransportException("Unsupported IP protocol id: " + protocolId);
|
||||
ModbusUtils.popShort(queue); // Length, which we don't care about.
|
||||
|
||||
// Create the modbus response.
|
||||
ModbusRequest request = ModbusRequest.createModbusRequest(queue);
|
||||
return new XaMessageRequest(request, transactionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expectsResponse() {
|
||||
return modbusMessage.getSlaveId() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getModbusRequest.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusRequest} object.
|
||||
*/
|
||||
public ModbusRequest getModbusRequest() {
|
||||
return (ModbusRequest) modbusMessage;
|
||||
}
|
||||
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.xa;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.ip.IpMessageResponse;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XaMessageResponse class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class XaMessageResponse extends XaMessage implements IpMessageResponse {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for XaMessageResponse.
|
||||
* </p>
|
||||
*
|
||||
* @param modbusResponse a {@link ModbusResponse} object.
|
||||
* @param transactionId a int.
|
||||
*/
|
||||
public XaMessageResponse(ModbusResponse modbusResponse, int transactionId) {
|
||||
super(modbusResponse, transactionId);
|
||||
}
|
||||
|
||||
static XaMessageResponse createXaMessageResponse(ByteQueue queue) throws ModbusTransportException {
|
||||
// Remove the XA header
|
||||
int transactionId = ModbusUtils.popShort(queue);
|
||||
int protocolId = ModbusUtils.popShort(queue);
|
||||
if (protocolId != ModbusUtils.IP_PROTOCOL_ID)
|
||||
throw new ModbusTransportException("Unsupported IP protocol id: " + protocolId);
|
||||
ModbusUtils.popShort(queue); // Length, which we don't care about.
|
||||
|
||||
// Create the modbus response.
|
||||
ModbusResponse response = ModbusResponse.createModbusResponse(queue);
|
||||
return new XaMessageResponse(response, transactionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getModbusResponse.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link ModbusResponse} object.
|
||||
*/
|
||||
public ModbusResponse getModbusResponse() {
|
||||
return (ModbusResponse) modbusMessage;
|
||||
}
|
||||
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.ip.xa;
|
||||
|
||||
import com.serotonin.modbus4j.ModbusSlaveSet;
|
||||
import com.serotonin.modbus4j.base.BaseRequestHandler;
|
||||
import com.serotonin.modbus4j.msg.ModbusRequest;
|
||||
import com.serotonin.modbus4j.msg.ModbusResponse;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingResponseMessage;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XaRequestHandler class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class XaRequestHandler extends BaseRequestHandler {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for XaRequestHandler.
|
||||
* </p>
|
||||
*
|
||||
* @param slave a {@link ModbusSlaveSet} object.
|
||||
*/
|
||||
public XaRequestHandler(ModbusSlaveSet slave) {
|
||||
super(slave);
|
||||
}
|
||||
|
||||
public OutgoingResponseMessage handleRequest(IncomingRequestMessage req) throws Exception {
|
||||
XaMessageRequest tcpRequest = (XaMessageRequest) req;
|
||||
ModbusRequest request = tcpRequest.getModbusRequest();
|
||||
ModbusResponse response = handleRequestImpl(request);
|
||||
if (response == null)
|
||||
return null;
|
||||
return new XaMessageResponse(response, tcpRequest.transactionId);
|
||||
}
|
||||
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.ip.xa;
|
||||
|
||||
import com.serotonin.modbus4j.msg.ModbusMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.IncomingResponseMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.OutgoingRequestMessage;
|
||||
import com.serotonin.modbus4j.sero.messaging.WaitingRoomKey;
|
||||
import com.serotonin.modbus4j.sero.messaging.WaitingRoomKeyFactory;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XaWaitingRoomKeyFactory class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class XaWaitingRoomKeyFactory implements WaitingRoomKeyFactory {
|
||||
|
||||
@Override
|
||||
public WaitingRoomKey createWaitingRoomKey(OutgoingRequestMessage request) {
|
||||
return createWaitingRoomKey((XaMessage) request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaitingRoomKey createWaitingRoomKey(IncomingResponseMessage response) {
|
||||
return createWaitingRoomKey((XaMessage) response);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createWaitingRoomKey.
|
||||
* </p>
|
||||
*
|
||||
* @param msg a {@link XaMessage} object.
|
||||
* @return a {@link WaitingRoomKey} object.
|
||||
*/
|
||||
public WaitingRoomKey createWaitingRoomKey(XaMessage msg) {
|
||||
return new XaWaitingRoomKey(msg.getTransactionId(), msg.getModbusMessage());
|
||||
}
|
||||
|
||||
class XaWaitingRoomKey implements WaitingRoomKey {
|
||||
|
||||
private final int transactionId;
|
||||
|
||||
private final int slaveId;
|
||||
|
||||
private final byte functionCode;
|
||||
|
||||
public XaWaitingRoomKey(int transactionId, ModbusMessage msg) {
|
||||
this.transactionId = transactionId;
|
||||
this.slaveId = msg.getSlaveId();
|
||||
this.functionCode = msg.getFunctionCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + functionCode;
|
||||
result = prime * result + slaveId;
|
||||
result = prime * result + transactionId;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
XaWaitingRoomKey other = (XaWaitingRoomKey) obj;
|
||||
if (functionCode != other.functionCode)
|
||||
return false;
|
||||
if (slaveId != other.slaveId)
|
||||
return false;
|
||||
if (transactionId != other.transactionId)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-346
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.locator;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.base.RangeAndOffset;
|
||||
import com.serotonin.modbus4j.code.DataType;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.ModbusIdException;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract BaseLocator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public abstract class BaseLocator<T> {
|
||||
|
||||
//
|
||||
//
|
||||
// Factory methods
|
||||
//
|
||||
|
||||
protected final int range;
|
||||
|
||||
protected final int offset;
|
||||
|
||||
private final int slaveId;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for BaseLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
*/
|
||||
public BaseLocator(int slaveId, int range, int offset) {
|
||||
this.slaveId = slaveId;
|
||||
this.range = range;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* coilStatus.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param offset a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<Boolean> coilStatus(int slaveId, int offset) {
|
||||
return new BinaryLocator(slaveId, RegisterRange.COIL_STATUS, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* inputStatus.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param offset a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<Boolean> inputStatus(int slaveId, int offset) {
|
||||
return new BinaryLocator(slaveId, RegisterRange.INPUT_STATUS, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* inputRegister.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<Number> inputRegister(int slaveId, int offset, int dataType) {
|
||||
return new NumericLocator(slaveId, RegisterRange.INPUT_REGISTER, offset, dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* inputRegisterBit.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<Boolean> inputRegisterBit(int slaveId, int offset, int bit) {
|
||||
return new BinaryLocator(slaveId, RegisterRange.INPUT_REGISTER, offset, bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* holdingRegister.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<Number> holdingRegister(int slaveId, int offset, int dataType) {
|
||||
return new NumericLocator(slaveId, RegisterRange.HOLDING_REGISTER, offset, dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* holdingRegisterBit.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<Boolean> holdingRegisterBit(int slaveId, int offset, int bit) {
|
||||
return new BinaryLocator(slaveId, RegisterRange.HOLDING_REGISTER, offset, bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param registerId a int.
|
||||
* @param dataType a int.
|
||||
* @param bit a int.
|
||||
* @param registerCount a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<?> createLocator(int slaveId, int registerId, int dataType, int bit, int registerCount) {
|
||||
RangeAndOffset rao = new RangeAndOffset(registerId);
|
||||
return createLocator(slaveId, rao.getRange(), rao.getOffset(), dataType, bit, registerCount,
|
||||
StringLocator.ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param registerId a int.
|
||||
* @param dataType a int.
|
||||
* @param bit a int.
|
||||
* @param registerCount a int.
|
||||
* @param charset a {@link Charset} object.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<?> createLocator(int slaveId, int registerId, int dataType, int bit, int registerCount,
|
||||
Charset charset) {
|
||||
RangeAndOffset rao = new RangeAndOffset(registerId);
|
||||
return createLocator(slaveId, rao.getRange(), rao.getOffset(), dataType, bit, registerCount, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param bit a int.
|
||||
* @param registerCount a int.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<?> createLocator(int slaveId, int range, int offset, int dataType, int bit,
|
||||
int registerCount) {
|
||||
return createLocator(slaveId, range, offset, dataType, bit, registerCount, StringLocator.ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* createLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param bit a int.
|
||||
* @param registerCount a int.
|
||||
* @param charset a {@link Charset} object.
|
||||
* @return a {@link BaseLocator} object.
|
||||
*/
|
||||
public static BaseLocator<?> createLocator(int slaveId, int range, int offset, int dataType, int bit,
|
||||
int registerCount, Charset charset) {
|
||||
if (dataType == DataType.BINARY) {
|
||||
if (BinaryLocator.isBinaryRange(range))
|
||||
return new BinaryLocator(slaveId, range, offset);
|
||||
return new BinaryLocator(slaveId, range, offset, bit);
|
||||
}
|
||||
if (dataType == DataType.CHAR || dataType == DataType.VARCHAR)
|
||||
return new StringLocator(slaveId, range, offset, dataType, registerCount, charset);
|
||||
return new NumericLocator(slaveId, range, offset, dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validate.
|
||||
* </p>
|
||||
*
|
||||
* @param registerCount a int.
|
||||
*/
|
||||
protected void validate(int registerCount) {
|
||||
try {
|
||||
ModbusUtils.validateOffset(offset);
|
||||
ModbusUtils.validateEndOffset(offset + registerCount - 1);
|
||||
} catch (ModbusTransportException e) {
|
||||
throw new ModbusIdException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getDataType.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
abstract public int getDataType();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getRegisterCount.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
abstract public int getRegisterCount();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>slaveId</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getSlaveId() {
|
||||
return slaveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>range</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>offset</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
// public SlaveAndRange getSlaveAndRange() {
|
||||
// return slaveAndRange;
|
||||
// }
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getEndOffset.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getEndOffset() {
|
||||
return offset + getRegisterCount() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* bytesToValue.
|
||||
* </p>
|
||||
*
|
||||
* @param data an array of {@link byte} objects.
|
||||
* @param requestOffset a int.
|
||||
* @return a T object.
|
||||
*/
|
||||
public T bytesToValue(byte[] data, int requestOffset) {
|
||||
// Determined the offset normalized to the response data.
|
||||
return bytesToValueRealOffset(data, offset - requestOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* bytesToValueRealOffset.
|
||||
* </p>
|
||||
*
|
||||
* @param data an array of {@link byte} objects.
|
||||
* @param offset a int.
|
||||
* @return a T object.
|
||||
*/
|
||||
abstract public T bytesToValueRealOffset(byte[] data, int offset);
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* valueToShorts.
|
||||
* </p>
|
||||
*
|
||||
* @param value a T object.
|
||||
* @return an array of {@link short} objects.
|
||||
*/
|
||||
abstract public short[] valueToShorts(T value);
|
||||
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.locator;
|
||||
|
||||
import com.serotonin.modbus4j.base.ModbusUtils;
|
||||
import com.serotonin.modbus4j.code.DataType;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.ModbusIdException;
|
||||
import com.serotonin.modbus4j.sero.NotImplementedException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* BinaryLocator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class BinaryLocator extends BaseLocator<Boolean> {
|
||||
|
||||
private int bit = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for BinaryLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
*/
|
||||
public BinaryLocator(int slaveId, int range, int offset) {
|
||||
super(slaveId, range, offset);
|
||||
if (!isBinaryRange(range))
|
||||
throw new ModbusIdException("Non-bit requests can only be made from coil status and input status ranges");
|
||||
validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for BinaryLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param bit a int.
|
||||
*/
|
||||
public BinaryLocator(int slaveId, int range, int offset, int bit) {
|
||||
super(slaveId, range, offset);
|
||||
if (isBinaryRange(range))
|
||||
throw new ModbusIdException("Bit requests can only be made from holding registers and input registers");
|
||||
this.bit = bit;
|
||||
validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isBinaryRange.
|
||||
* </p>
|
||||
*
|
||||
* @param range a int.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean isBinaryRange(int range) {
|
||||
return range == RegisterRange.COIL_STATUS || range == RegisterRange.INPUT_STATUS;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* validate.
|
||||
* </p>
|
||||
*/
|
||||
protected void validate() {
|
||||
super.validate(1);
|
||||
|
||||
if (!isBinaryRange(range))
|
||||
ModbusUtils.validateBit(bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>bit</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getBit() {
|
||||
return bit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataType() {
|
||||
return DataType.BINARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegisterCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BinaryLocator(slaveId=" + getSlaveId() + ", range=" + range + ", offset=" + offset + ", bit=" + bit
|
||||
+ ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean bytesToValueRealOffset(byte[] data, int offset) {
|
||||
// If this is a coil or input, convert to boolean.
|
||||
if (range == RegisterRange.COIL_STATUS || range == RegisterRange.INPUT_STATUS)
|
||||
return (((data[offset / 8] & 0xff) >> (offset % 8)) & 0x1) == 1;
|
||||
|
||||
// For the rest of the types, we double the normalized offset to account for short
|
||||
// to byte.
|
||||
offset *= 2;
|
||||
|
||||
// We could still be asking for a binary if it's a bit in a register.
|
||||
return (((data[offset + 1 - bit / 8] & 0xff) >> (bit % 8)) & 0x1) == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short[] valueToShorts(Boolean value) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
-488
@@ -1,488 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.locator;
|
||||
|
||||
import com.serotonin.modbus4j.code.DataType;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.IllegalDataTypeException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* NumericLocator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class NumericLocator extends BaseLocator<Number> {
|
||||
|
||||
private static final int[] DATA_TYPES = { //
|
||||
DataType.TWO_BYTE_INT_UNSIGNED, //
|
||||
DataType.TWO_BYTE_INT_SIGNED, //
|
||||
DataType.TWO_BYTE_INT_UNSIGNED_SWAPPED, //
|
||||
DataType.TWO_BYTE_INT_SIGNED_SWAPPED, //
|
||||
DataType.FOUR_BYTE_INT_UNSIGNED, //
|
||||
DataType.FOUR_BYTE_INT_SIGNED, //
|
||||
DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED, //
|
||||
DataType.FOUR_BYTE_INT_SIGNED_SWAPPED, //
|
||||
DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED, //
|
||||
DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED, //
|
||||
DataType.FOUR_BYTE_FLOAT, //
|
||||
DataType.FOUR_BYTE_FLOAT_SWAPPED, //
|
||||
DataType.EIGHT_BYTE_INT_UNSIGNED, //
|
||||
DataType.EIGHT_BYTE_INT_SIGNED, //
|
||||
DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED, //
|
||||
DataType.EIGHT_BYTE_INT_SIGNED_SWAPPED, //
|
||||
DataType.EIGHT_BYTE_FLOAT, //
|
||||
DataType.EIGHT_BYTE_FLOAT_SWAPPED, //
|
||||
DataType.TWO_BYTE_BCD, //
|
||||
DataType.FOUR_BYTE_BCD, //
|
||||
DataType.FOUR_BYTE_BCD_SWAPPED, //
|
||||
DataType.FOUR_BYTE_MOD_10K, //
|
||||
DataType.FOUR_BYTE_MOD_10K_SWAPPED, //
|
||||
DataType.SIX_BYTE_MOD_10K, DataType.SIX_BYTE_MOD_10K_SWAPPED, DataType.EIGHT_BYTE_MOD_10K, //
|
||||
DataType.EIGHT_BYTE_MOD_10K_SWAPPED, //
|
||||
DataType.ONE_BYTE_INT_UNSIGNED_LOWER, //
|
||||
DataType.ONE_BYTE_INT_UNSIGNED_UPPER};
|
||||
|
||||
private final int dataType;
|
||||
|
||||
private RoundingMode roundingMode = RoundingMode.HALF_UP;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for NumericLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
*/
|
||||
public NumericLocator(int slaveId, int range, int offset, int dataType) {
|
||||
super(slaveId, range, offset);
|
||||
this.dataType = dataType;
|
||||
validate();
|
||||
}
|
||||
|
||||
private static void appendBCD(StringBuilder sb, byte b) {
|
||||
sb.append(bcdNibbleToInt(b, true));
|
||||
sb.append(bcdNibbleToInt(b, false));
|
||||
}
|
||||
|
||||
private static int bcdNibbleToInt(byte b, boolean high) {
|
||||
int n;
|
||||
if (high)
|
||||
n = (b >> 4) & 0xf;
|
||||
else
|
||||
n = b & 0xf;
|
||||
if (n > 9)
|
||||
n = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
super.validate(getRegisterCount());
|
||||
|
||||
if (range == RegisterRange.COIL_STATUS || range == RegisterRange.INPUT_STATUS)
|
||||
throw new IllegalDataTypeException("Only binary values can be read from Coil and Input ranges");
|
||||
boolean b = Arrays.stream(DATA_TYPES).anyMatch(dt -> dt == dataType);
|
||||
if (!b)
|
||||
throw new IllegalDataTypeException("Invalid data type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>roundingMode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link RoundingMode} object.
|
||||
*/
|
||||
public RoundingMode getRoundingMode() {
|
||||
return roundingMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>roundingMode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param roundingMode a {@link RoundingMode} object.
|
||||
*/
|
||||
public void setRoundingMode(RoundingMode roundingMode) {
|
||||
this.roundingMode = roundingMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NumericLocator(slaveId=" + getSlaveId() + ", range=" + range + ", offset=" + offset + ", dataType="
|
||||
+ dataType + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegisterCount() {
|
||||
switch (dataType) {
|
||||
case DataType.TWO_BYTE_INT_UNSIGNED:
|
||||
case DataType.TWO_BYTE_INT_SIGNED:
|
||||
case DataType.TWO_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case DataType.TWO_BYTE_INT_SIGNED_SWAPPED:
|
||||
case DataType.TWO_BYTE_BCD:
|
||||
case DataType.ONE_BYTE_INT_UNSIGNED_LOWER:
|
||||
case DataType.ONE_BYTE_INT_UNSIGNED_UPPER:
|
||||
return 1;
|
||||
case DataType.FOUR_BYTE_INT_UNSIGNED:
|
||||
case DataType.FOUR_BYTE_INT_SIGNED:
|
||||
case DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case DataType.FOUR_BYTE_INT_SIGNED_SWAPPED:
|
||||
case DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED:
|
||||
case DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED:
|
||||
case DataType.FOUR_BYTE_FLOAT:
|
||||
case DataType.FOUR_BYTE_FLOAT_SWAPPED:
|
||||
case DataType.FOUR_BYTE_BCD:
|
||||
case DataType.FOUR_BYTE_BCD_SWAPPED:
|
||||
case DataType.FOUR_BYTE_MOD_10K:
|
||||
case DataType.FOUR_BYTE_MOD_10K_SWAPPED:
|
||||
return 2;
|
||||
case DataType.SIX_BYTE_MOD_10K:
|
||||
case DataType.SIX_BYTE_MOD_10K_SWAPPED:
|
||||
return 3;
|
||||
case DataType.EIGHT_BYTE_INT_UNSIGNED:
|
||||
case DataType.EIGHT_BYTE_INT_SIGNED:
|
||||
case DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED:
|
||||
case DataType.EIGHT_BYTE_INT_SIGNED_SWAPPED:
|
||||
case DataType.EIGHT_BYTE_FLOAT:
|
||||
case DataType.EIGHT_BYTE_FLOAT_SWAPPED:
|
||||
case DataType.EIGHT_BYTE_MOD_10K:
|
||||
case DataType.EIGHT_BYTE_MOD_10K_SWAPPED:
|
||||
return 4;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unsupported data type: " + dataType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number bytesToValueRealOffset(byte[] data, int offset) {
|
||||
offset *= 2;
|
||||
|
||||
// 2 bytes
|
||||
if (dataType == DataType.TWO_BYTE_INT_UNSIGNED)
|
||||
return ((data[offset] & 0xff) << 8) | (data[offset + 1] & 0xff);
|
||||
|
||||
if (dataType == DataType.TWO_BYTE_INT_SIGNED)
|
||||
return (short) (((data[offset] & 0xff) << 8) | (data[offset + 1] & 0xff));
|
||||
|
||||
if (dataType == DataType.TWO_BYTE_INT_UNSIGNED_SWAPPED)
|
||||
return ((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff);
|
||||
|
||||
if (dataType == DataType.TWO_BYTE_INT_SIGNED_SWAPPED)
|
||||
return (short) (((data[offset + 1] & 0xff) << 8) | (data[offset] & 0xff));
|
||||
|
||||
if (dataType == DataType.TWO_BYTE_BCD) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendBCD(sb, data[offset]);
|
||||
appendBCD(sb, data[offset + 1]);
|
||||
return Short.parseShort(sb.toString());
|
||||
}
|
||||
|
||||
// 1 byte
|
||||
if (dataType == DataType.ONE_BYTE_INT_UNSIGNED_LOWER)
|
||||
return data[offset + 1] & 0xff;
|
||||
if (dataType == DataType.ONE_BYTE_INT_UNSIGNED_UPPER)
|
||||
return data[offset] & 0xff;
|
||||
|
||||
// 4 bytes
|
||||
if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED)
|
||||
return (long) ((data[offset] & 0xff)) << 24 | ((long) ((data[offset + 1] & 0xff)) << 16)
|
||||
| ((long) ((data[offset + 2] & 0xff)) << 8) | ((data[offset + 3] & 0xff));
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_SIGNED)
|
||||
return ((data[offset] & 0xff) << 24) | ((data[offset + 1] & 0xff) << 16) | ((data[offset + 2] & 0xff) << 8)
|
||||
| (data[offset + 3] & 0xff);
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED)
|
||||
return ((long) ((data[offset + 2] & 0xff)) << 24) | ((long) ((data[offset + 3] & 0xff)) << 16)
|
||||
| ((long) ((data[offset] & 0xff)) << 8) | ((data[offset + 1] & 0xff));
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_SIGNED_SWAPPED)
|
||||
return ((data[offset + 2] & 0xff) << 24) | ((data[offset + 3] & 0xff) << 16) | ((data[offset] & 0xff) << 8)
|
||||
| (data[offset + 1] & 0xff);
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED)
|
||||
return ((long) ((data[offset + 3] & 0xff)) << 24) | (((data[offset + 2] & 0xff) << 16))
|
||||
| ((long) ((data[offset + 1] & 0xff)) << 8) | (data[offset] & 0xff);
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED)
|
||||
return ((data[offset + 3] & 0xff) << 24) | ((data[offset + 2] & 0xff) << 16)
|
||||
| ((data[offset + 1] & 0xff) << 8) | ((data[offset] & 0xff));
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_FLOAT)
|
||||
return Float.intBitsToFloat(((data[offset] & 0xff) << 24) | ((data[offset + 1] & 0xff) << 16)
|
||||
| ((data[offset + 2] & 0xff) << 8) | (data[offset + 3] & 0xff));
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_FLOAT_SWAPPED)
|
||||
return Float.intBitsToFloat(((data[offset + 2] & 0xff) << 24) | ((data[offset + 3] & 0xff) << 16)
|
||||
| ((data[offset] & 0xff) << 8) | (data[offset + 1] & 0xff));
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_BCD) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendBCD(sb, data[offset]);
|
||||
appendBCD(sb, data[offset + 1]);
|
||||
appendBCD(sb, data[offset + 2]);
|
||||
appendBCD(sb, data[offset + 3]);
|
||||
return Integer.parseInt(sb.toString());
|
||||
}
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_BCD_SWAPPED) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendBCD(sb, data[offset + 2]);
|
||||
appendBCD(sb, data[offset + 3]);
|
||||
appendBCD(sb, data[offset]);
|
||||
appendBCD(sb, data[offset + 1]);
|
||||
return Integer.parseInt(sb.toString());
|
||||
}
|
||||
|
||||
// MOD10K types
|
||||
if (dataType == DataType.FOUR_BYTE_MOD_10K_SWAPPED)
|
||||
return BigInteger.valueOf((((data[offset + 2] & 0xff) << 8) + (data[offset + 3] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(10000L))
|
||||
.add(BigInteger.valueOf((((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff))));
|
||||
if (dataType == DataType.FOUR_BYTE_MOD_10K)
|
||||
return BigInteger.valueOf((((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(10000L))
|
||||
.add(BigInteger.valueOf((((data[offset + 2] & 0xff) << 8) + (data[offset + 3] & 0xff))));
|
||||
if (dataType == DataType.SIX_BYTE_MOD_10K_SWAPPED)
|
||||
return BigInteger.valueOf((((data[offset + 4] & 0xff) << 8) + (data[offset + 5] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(100000000L))
|
||||
.add(BigInteger.valueOf((((data[offset + 2] & 0xff) << 8) + (data[offset + 3] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(10000L)))
|
||||
.add(BigInteger.valueOf((((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff))));
|
||||
if (dataType == DataType.SIX_BYTE_MOD_10K)
|
||||
return BigInteger.valueOf((((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(100000000L))
|
||||
.add(BigInteger.valueOf((((data[offset + 2] & 0xff) << 8) + (data[offset + 3] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(10000L)))
|
||||
.add(BigInteger.valueOf((((data[offset + 4] & 0xff) << 8) + (data[offset + 5] & 0xff))));
|
||||
if (dataType == DataType.EIGHT_BYTE_MOD_10K_SWAPPED)
|
||||
return BigInteger.valueOf((((data[offset + 6] & 0xff) << 8) + (data[offset + 7] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(1000000000000L))
|
||||
.add(BigInteger.valueOf((((data[offset + 4] & 0xff) << 8) + (data[offset + 5] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(100000000L)))
|
||||
.add(BigInteger.valueOf((((data[offset + 2] & 0xff) << 8) + (data[offset + 3] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(10000L)))
|
||||
.add(BigInteger.valueOf((((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff))));
|
||||
if (dataType == DataType.EIGHT_BYTE_MOD_10K)
|
||||
return BigInteger.valueOf((((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(1000000000000L))
|
||||
.add(BigInteger.valueOf((((data[offset + 2] & 0xff) << 8) + (data[offset + 3] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(100000000L)))
|
||||
.add(BigInteger.valueOf((((data[offset + 4] & 0xff) << 8) + (data[offset + 5] & 0xff)))
|
||||
.multiply(BigInteger.valueOf(10000L)))
|
||||
.add(BigInteger.valueOf((((data[offset + 6] & 0xff) << 8) + (data[offset + 7] & 0xff))));
|
||||
|
||||
// 8 bytes
|
||||
if (dataType == DataType.EIGHT_BYTE_INT_UNSIGNED) {
|
||||
byte[] b9 = new byte[9];
|
||||
System.arraycopy(data, offset, b9, 1, 8);
|
||||
return new BigInteger(b9);
|
||||
}
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_INT_SIGNED)
|
||||
return ((long) ((data[offset] & 0xff)) << 56) | ((long) ((data[offset + 1] & 0xff)) << 48)
|
||||
| ((long) ((data[offset + 2] & 0xff)) << 40) | ((long) ((data[offset + 3] & 0xff)) << 32)
|
||||
| ((long) ((data[offset + 4] & 0xff)) << 24) | ((long) ((data[offset + 5] & 0xff)) << 16)
|
||||
| ((long) ((data[offset + 6] & 0xff)) << 8) | ((data[offset + 7] & 0xff));
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED) {
|
||||
byte[] b9 = new byte[9];
|
||||
b9[1] = data[offset + 6];
|
||||
b9[2] = data[offset + 7];
|
||||
b9[3] = data[offset + 4];
|
||||
b9[4] = data[offset + 5];
|
||||
b9[5] = data[offset + 2];
|
||||
b9[6] = data[offset + 3];
|
||||
b9[7] = data[offset];
|
||||
b9[8] = data[offset + 1];
|
||||
return new BigInteger(b9);
|
||||
}
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_INT_SIGNED_SWAPPED)
|
||||
return ((long) ((data[offset + 6] & 0xff)) << 56) | ((long) ((data[offset + 7] & 0xff)) << 48)
|
||||
| ((long) ((data[offset + 4] & 0xff)) << 40) | ((long) ((data[offset + 5] & 0xff)) << 32)
|
||||
| ((long) ((data[offset + 2] & 0xff)) << 24) | ((long) ((data[offset + 3] & 0xff)) << 16)
|
||||
| ((long) ((data[offset] & 0xff)) << 8) | ((data[offset + 1] & 0xff));
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_FLOAT)
|
||||
return Double
|
||||
.longBitsToDouble(((long) ((data[offset] & 0xff)) << 56) | ((long) ((data[offset + 1] & 0xff)) << 48)
|
||||
| ((long) ((data[offset + 2] & 0xff)) << 40) | ((long) ((data[offset + 3] & 0xff)) << 32)
|
||||
| ((long) ((data[offset + 4] & 0xff)) << 24) | ((long) ((data[offset + 5] & 0xff)) << 16)
|
||||
| ((long) ((data[offset + 6] & 0xff)) << 8) | ((data[offset + 7] & 0xff)));
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_FLOAT_SWAPPED)
|
||||
return Double.longBitsToDouble(
|
||||
((long) ((data[offset + 6] & 0xff)) << 56) | ((long) ((data[offset + 7] & 0xff)) << 48)
|
||||
| ((long) ((data[offset + 4] & 0xff)) << 40) | ((long) ((data[offset + 5] & 0xff)) << 32)
|
||||
| ((long) ((data[offset + 2] & 0xff)) << 24) | ((long) ((data[offset + 3] & 0xff)) << 16)
|
||||
| ((long) ((data[offset] & 0xff)) << 8) | ((data[offset + 1] & 0xff)));
|
||||
|
||||
throw new RuntimeException("Unsupported data type: " + dataType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short[] valueToShorts(Number value) {
|
||||
// 2 bytes
|
||||
if (dataType == DataType.TWO_BYTE_INT_UNSIGNED || dataType == DataType.TWO_BYTE_INT_SIGNED)
|
||||
return new short[]{toShort(value)};
|
||||
|
||||
if (dataType == DataType.TWO_BYTE_INT_SIGNED_SWAPPED || dataType == DataType.TWO_BYTE_INT_UNSIGNED_SWAPPED) {
|
||||
short sval = toShort(value);
|
||||
// 0x1100
|
||||
return new short[]{(short) (((sval & 0xFF00) >> 8) | ((sval & 0x00FF) << 8))};
|
||||
}
|
||||
|
||||
if (dataType == DataType.TWO_BYTE_BCD) {
|
||||
short s = toShort(value);
|
||||
return new short[]{
|
||||
(short) ((((s / 1000) % 10) << 12) | (((s / 100) % 10) << 8) | (((s / 10) % 10) << 4) | (s % 10))};
|
||||
}
|
||||
|
||||
if (dataType == DataType.ONE_BYTE_INT_UNSIGNED_LOWER) {
|
||||
return new short[]{(short) (toShort(value) & 0x00FF)};
|
||||
}
|
||||
if (dataType == DataType.ONE_BYTE_INT_UNSIGNED_UPPER) {
|
||||
return new short[]{(short) ((toShort(value) << 8) & 0xFF00)};
|
||||
}
|
||||
|
||||
// 4 bytes
|
||||
if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED || dataType == DataType.FOUR_BYTE_INT_SIGNED) {
|
||||
int i = toInt(value);
|
||||
return new short[]{(short) (i >> 16), (short) i};
|
||||
}
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED || dataType == DataType.FOUR_BYTE_INT_SIGNED_SWAPPED) {
|
||||
int i = toInt(value);
|
||||
return new short[]{(short) i, (short) (i >> 16)};
|
||||
}
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED
|
||||
|| dataType == DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED) {
|
||||
int i = toInt(value);
|
||||
short topWord = (short) (((i & 0xFF) << 8) | ((i >> 8) & 0xFF));
|
||||
short bottomWord = (short) (((i >> 24) & 0x000000FF) | ((i >> 8) & 0x0000FF00));
|
||||
return new short[]{topWord, bottomWord};
|
||||
}
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_FLOAT) {
|
||||
int i = Float.floatToIntBits(value.floatValue());
|
||||
return new short[]{(short) (i >> 16), (short) i};
|
||||
}
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_FLOAT_SWAPPED) {
|
||||
int i = Float.floatToIntBits(value.floatValue());
|
||||
return new short[]{(short) i, (short) (i >> 16)};
|
||||
}
|
||||
|
||||
if (dataType == DataType.FOUR_BYTE_BCD) {
|
||||
int i = toInt(value);
|
||||
return new short[]{
|
||||
(short) ((((i / 10000000) % 10) << 12) | (((i / 1000000) % 10) << 8) | (((i / 100000) % 10) << 4)
|
||||
| ((i / 10000) % 10)),
|
||||
(short) ((((i / 1000) % 10) << 12) | (((i / 100) % 10) << 8) | (((i / 10) % 10) << 4) | (i % 10))};
|
||||
}
|
||||
|
||||
// MOD10K
|
||||
if (dataType == DataType.FOUR_BYTE_MOD_10K) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) ((l / 10000) % 10000), (short) (l % 10000)};
|
||||
}
|
||||
if (dataType == DataType.FOUR_BYTE_MOD_10K_SWAPPED) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) (l % 10000), (short) ((l / 10000) % 10000)};
|
||||
}
|
||||
if (dataType == DataType.SIX_BYTE_MOD_10K) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) ((l / 100000000L) % 10000), (short) ((l / 10000) % 10000),
|
||||
(short) (l % 10000)};
|
||||
}
|
||||
if (dataType == DataType.SIX_BYTE_MOD_10K_SWAPPED) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) (l % 10000), (short) ((l / 10000) % 10000),
|
||||
(short) ((l / 100000000L) % 10000)};
|
||||
}
|
||||
if (dataType == DataType.EIGHT_BYTE_MOD_10K) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) ((l / 1000000000000L) % 10000), (short) ((l / 100000000L) % 10000),
|
||||
(short) ((l / 10000) % 10000), (short) (l % 10000)};
|
||||
}
|
||||
if (dataType == DataType.EIGHT_BYTE_MOD_10K_SWAPPED) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) (l % 10000), (short) ((l / 10000) % 10000), (short) ((l / 100000000L) % 10000),
|
||||
(short) ((l / 1000000000000L) % 10000)};
|
||||
}
|
||||
|
||||
// 8 bytes
|
||||
if (dataType == DataType.EIGHT_BYTE_INT_UNSIGNED || dataType == DataType.EIGHT_BYTE_INT_SIGNED) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) (l >> 48), (short) (l >> 32), (short) (l >> 16), (short) l};
|
||||
}
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED
|
||||
|| dataType == DataType.EIGHT_BYTE_INT_SIGNED_SWAPPED) {
|
||||
long l = value.longValue();
|
||||
return new short[]{(short) l, (short) (l >> 16), (short) (l >> 32), (short) (l >> 48)};
|
||||
}
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_FLOAT) {
|
||||
long l = Double.doubleToLongBits(value.doubleValue());
|
||||
return new short[]{(short) (l >> 48), (short) (l >> 32), (short) (l >> 16), (short) l};
|
||||
}
|
||||
|
||||
if (dataType == DataType.EIGHT_BYTE_FLOAT_SWAPPED) {
|
||||
long l = Double.doubleToLongBits(value.doubleValue());
|
||||
return new short[]{(short) l, (short) (l >> 16), (short) (l >> 32), (short) (l >> 48)};
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unsupported data type: " + dataType);
|
||||
}
|
||||
|
||||
private short toShort(Number value) {
|
||||
return (short) toInt(value);
|
||||
}
|
||||
|
||||
private int toInt(Number value) {
|
||||
if (value instanceof Double)
|
||||
return new BigDecimal(value.doubleValue()).setScale(0, roundingMode).intValue();
|
||||
if (value instanceof Float)
|
||||
return new BigDecimal(value.floatValue()).setScale(0, roundingMode).intValue();
|
||||
if (value instanceof BigDecimal)
|
||||
return ((BigDecimal) value).setScale(0, roundingMode).intValue();
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
}
|
||||
-217
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.serotonin.modbus4j.locator;
|
||||
|
||||
import com.serotonin.modbus4j.code.DataType;
|
||||
import com.serotonin.modbus4j.code.RegisterRange;
|
||||
import com.serotonin.modbus4j.exception.IllegalDataTypeException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* StringLocator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class StringLocator extends BaseLocator<String> {
|
||||
|
||||
/**
|
||||
* Constant <code>ASCII</code>
|
||||
*/
|
||||
public static final Charset ASCII = Charset.forName("ASCII");
|
||||
|
||||
private final int dataType;
|
||||
|
||||
private final int registerCount;
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for StringLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param registerCount a int.
|
||||
*/
|
||||
public StringLocator(int slaveId, int range, int offset, int dataType, int registerCount) {
|
||||
this(slaveId, range, offset, dataType, registerCount, ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for StringLocator.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param range a int.
|
||||
* @param offset a int.
|
||||
* @param dataType a int.
|
||||
* @param registerCount a int.
|
||||
* @param charset a {@link Charset} object.
|
||||
*/
|
||||
public StringLocator(int slaveId, int range, int offset, int dataType, int registerCount, Charset charset) {
|
||||
super(slaveId, range, offset);
|
||||
this.dataType = dataType;
|
||||
this.registerCount = registerCount;
|
||||
this.charset = charset;
|
||||
validate();
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
super.validate(registerCount);
|
||||
|
||||
if (range == RegisterRange.COIL_STATUS || range == RegisterRange.INPUT_STATUS)
|
||||
throw new IllegalDataTypeException("Only binary values can be read from Coil and Input ranges");
|
||||
|
||||
if (dataType != DataType.CHAR && dataType != DataType.VARCHAR)
|
||||
throw new IllegalDataTypeException("Invalid data type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegisterCount() {
|
||||
return registerCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StringLocator(slaveId=" + getSlaveId() + ", range=" + range + ", offset=" + offset + ", dataType="
|
||||
+ dataType + ", registerCount=" + registerCount + ", charset=" + charset + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String bytesToValueRealOffset(byte[] data, int offset) {
|
||||
offset *= 2;
|
||||
int length = registerCount * 2;
|
||||
|
||||
if (dataType == DataType.CHAR)
|
||||
return new String(data, offset, length, charset);
|
||||
|
||||
if (dataType == DataType.VARCHAR) {
|
||||
int nullPos = -1;
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
if (data[i] == 0) {
|
||||
nullPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nullPos == -1)
|
||||
return new String(data, offset, length, charset);
|
||||
return new String(data, offset, nullPos, charset);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unsupported data type: " + dataType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short[] valueToShorts(String value) {
|
||||
short[] result = new short[registerCount];
|
||||
int resultByteLen = registerCount * 2;
|
||||
|
||||
int length;
|
||||
if (value != null) {
|
||||
byte[] bytes = value.getBytes(charset);
|
||||
|
||||
length = resultByteLen;
|
||||
if (length > bytes.length)
|
||||
length = bytes.length;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
setByte(result, i, bytes[i] & 0xff);
|
||||
} else
|
||||
length = 0;
|
||||
|
||||
if (dataType == DataType.CHAR) {
|
||||
// Pad the rest with spaces
|
||||
for (int i = length; i < resultByteLen; i++)
|
||||
setByte(result, i, 0x20);
|
||||
} else if (dataType == DataType.VARCHAR) {
|
||||
if (length >= resultByteLen)
|
||||
// Ensure the last byte is a null terminator.
|
||||
result[registerCount - 1] &= 0xff00;
|
||||
else {
|
||||
// Pad the rest with null.
|
||||
for (int i = length; i < resultByteLen; i++)
|
||||
setByte(result, i, 0);
|
||||
}
|
||||
} else
|
||||
throw new RuntimeException("Unsupported data type: " + dataType);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void setByte(short[] s, int byteIndex, int value) {
|
||||
if (byteIndex % 2 == 0)
|
||||
s[byteIndex / 2] |= value << 8;
|
||||
else
|
||||
s[byteIndex / 2] |= value;
|
||||
}
|
||||
//
|
||||
// public static void main(String[] args) {
|
||||
// StringLocator l1 = new StringLocator(1, RegisterRange.HOLDING_REGISTER, 0,
|
||||
// DataType.CHAR, 4);
|
||||
// StringLocator l2 = new StringLocator(1, RegisterRange.HOLDING_REGISTER, 0,
|
||||
// DataType.VARCHAR, 4);
|
||||
//
|
||||
// short[] s;
|
||||
//
|
||||
// s = l1.valueToShorts("abcdefg");
|
||||
// System.out.println(new String(l1.bytesToValue(toBytes(s), 0)));
|
||||
//
|
||||
// s = l1.valueToShorts("abcdefgh");
|
||||
// System.out.println(new String(l1.bytesToValue(toBytes(s), 0)));
|
||||
//
|
||||
// s = l1.valueToShorts("abcdefghi");
|
||||
// System.out.println(new String(l1.bytesToValue(toBytes(s), 0)));
|
||||
//
|
||||
// s = l2.valueToShorts("abcdef");
|
||||
// System.out.println(new String(l2.bytesToValue(toBytes(s), 0)));
|
||||
//
|
||||
// s = l2.valueToShorts("abcdefg");
|
||||
// System.out.println(new String(l2.bytesToValue(toBytes(s), 0)));
|
||||
//
|
||||
// s = l2.valueToShorts("abcdefgh");
|
||||
// System.out.println(new String(l2.bytesToValue(toBytes(s), 0)));
|
||||
//
|
||||
// s = l2.valueToShorts("abcdefghi");
|
||||
// System.out.println(new String(l2.bytesToValue(toBytes(s), 0)));
|
||||
// }
|
||||
//
|
||||
// private static byte[] toBytes(short[] s) {
|
||||
// byte[] b = new byte[s.length * 2];
|
||||
// for (int i = 0; i < s.length; i++) {
|
||||
// b[i * 2] = (byte) ((s[i] >> 8) & 0xff);
|
||||
// b[i * 2 + 1] = (byte) (s[i] & 0xff);
|
||||
// }
|
||||
// return b;
|
||||
// }
|
||||
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.msg;
|
||||
|
||||
import com.serotonin.modbus4j.Modbus;
|
||||
import com.serotonin.modbus4j.ProcessImage;
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.sero.ShouldNeverHappenException;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ExceptionRequest class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ExceptionRequest extends ModbusRequest {
|
||||
|
||||
private final byte functionCode;
|
||||
|
||||
private final byte exceptionCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ExceptionRequest.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param functionCode a byte.
|
||||
* @param exceptionCode a byte.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public ExceptionRequest(int slaveId, byte functionCode, byte exceptionCode) throws ModbusTransportException {
|
||||
super(slaveId);
|
||||
this.functionCode = functionCode;
|
||||
this.exceptionCode = exceptionCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Modbus modbus) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeRequest(ByteQueue queue) {
|
||||
throw new ShouldNeverHappenException("wha");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readRequest(ByteQueue queue) {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
ModbusResponse getResponseInstance(int slaveId) throws ModbusTransportException {
|
||||
return new ExceptionResponse(slaveId, functionCode, exceptionCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
ModbusResponse handleImpl(ProcessImage processImage) throws ModbusTransportException {
|
||||
return getResponseInstance(slaveId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getFunctionCode() {
|
||||
return functionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>exceptionCode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a byte.
|
||||
*/
|
||||
public byte getExceptionCode() {
|
||||
return exceptionCode;
|
||||
}
|
||||
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.serotonin.modbus4j.msg;
|
||||
|
||||
import com.serotonin.modbus4j.exception.ModbusTransportException;
|
||||
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ExceptionResponse class.
|
||||
* </p>
|
||||
*
|
||||
* @author Matthew Lohbihler
|
||||
* @version 2025.9.0
|
||||
* @since 2016.10.1
|
||||
*/
|
||||
public class ExceptionResponse extends ModbusResponse {
|
||||
|
||||
private final byte functionCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for ExceptionResponse.
|
||||
* </p>
|
||||
*
|
||||
* @param slaveId a int.
|
||||
* @param functionCode a byte.
|
||||
* @param exceptionCode a byte.
|
||||
* @throws ModbusTransportException if any.
|
||||
*/
|
||||
public ExceptionResponse(int slaveId, byte functionCode, byte exceptionCode) throws ModbusTransportException {
|
||||
super(slaveId);
|
||||
this.functionCode = functionCode;
|
||||
setException(exceptionCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getFunctionCode() {
|
||||
return functionCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readResponse(ByteQueue queue) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeResponse(ByteQueue queue) {
|
||||
// no op
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user