diff --git a/hertzbeat-collector/hertzbeat-collector-basic/pom.xml b/hertzbeat-collector/hertzbeat-collector-basic/pom.xml index 9f862f5005a..42a8fb6e8cf 100644 --- a/hertzbeat-collector/hertzbeat-collector-basic/pom.xml +++ b/hertzbeat-collector/hertzbeat-collector-basic/pom.xml @@ -144,5 +144,16 @@ hivemq-mqtt-client ${mqtt.version} + + + org.apache.plc4x + plc4j-api + 0.12.0 + + + org.apache.plc4x + plc4j-driver-modbus + 0.12.0 + \ No newline at end of file diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/modbus/ModbusCollectImpl.java b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/modbus/ModbusCollectImpl.java new file mode 100644 index 00000000000..b51f8b5da15 --- /dev/null +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/modbus/ModbusCollectImpl.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.collector.collect.modbus; + +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.collector.collect.plc.AbstractPlcCollectImpl; +import org.apache.hertzbeat.collector.dispatch.DispatchConstants; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.ModbusProtocol; +import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.springframework.beans.BeanUtils; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * plc collect + */ +@Slf4j +public class ModbusCollectImpl extends AbstractPlcCollectImpl { + + @Override + public void preCheck(Metrics metrics) throws IllegalArgumentException { + ModbusProtocol modbus = metrics.getModbus(); + List registerAddressList = modbus.getRegisterAddresses(); + // check slaveId + if (!StringUtils.hasText(modbus.getSlaveId())) { + modbus.setSlaveId("1"); + } + if (!StringUtils.hasText(modbus.getTimeout())) { + modbus.setTimeout("5000"); + } + PlcProtocol plc = metrics.getPlc() == null ? new PlcProtocol() : metrics.getPlc(); + plc.setRegisterAddresses(registerAddressList); + BeanUtils.copyProperties(modbus, plc); + metrics.setPlc(plc); + super.preCheck(metrics); + } + + @Override + public void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics) { + super.collect(builder, monitorId, app, metrics); + } + + @Override + public String supportProtocol() { + return DispatchConstants.PROTOCOL_MODBUS; + } + + @Override + protected String getConnectionString(Metrics metrics) { + ModbusProtocol plcProtocol = metrics.getModbus(); + return "modbus-tcp:tcp://" + plcProtocol.getHost() + ":" + plcProtocol.getPort() + "?unit-identifier=" + plcProtocol.getSlaveId(); + } + + @Override + protected PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection) { + ModbusProtocol modbus = metrics.getModbus(); + List registerAddressList = modbus.getRegisterAddresses(); + // Create a new read request: + PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder(); + for (int i = 0; i < registerAddressList.size(); i++) { + String s1 = modbus.getAddressSyntax() + ":" + registerAddressList.get(i); + requestBuilder.addTagAddress(metrics.getModbus().getAddressSyntax() + ":" + i, s1); + } + return requestBuilder.build(); + } +} \ No newline at end of file diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/plc/AbstractPlcCollectImpl.java b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/plc/AbstractPlcCollectImpl.java new file mode 100644 index 00000000000..201a6321651 --- /dev/null +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/plc/AbstractPlcCollectImpl.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.collector.collect.plc; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.hertzbeat.collector.collect.AbstractCollect; +import org.apache.hertzbeat.collector.constants.CollectorConstants; +import org.apache.hertzbeat.common.constants.CommonConstants; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.apache.hertzbeat.common.util.CommonUtil; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.PlcConnectionManager; +import org.apache.plc4x.java.api.PlcDriverManager; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.apache.plc4x.java.api.messages.PlcReadResponse; +import org.apache.plc4x.java.api.types.PlcResponseCode; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Slf4j +public abstract class AbstractPlcCollectImpl extends AbstractCollect { + private static final String[] DRIVER_LIST = {"s7", "modbus-tcp"}; + private static final String[] ADDRESS_SYNTAX = {"discrete-input", "coil", "input-register", "holding-register"}; + private static final String COIL = "coil"; + + private static final PlcConnectionManager CONNECTION_MANAGER; + + static { + CONNECTION_MANAGER = PlcDriverManager.getDefault().getConnectionManager(); + } + + + @Override + public void preCheck(Metrics metrics) throws IllegalArgumentException { + if (metrics == null || metrics.getPlc() == null) { + throw new IllegalArgumentException("PLC collect must have PLC params"); + } + // check driver name + if (metrics.getPlc().getDriverName() == null || !ArrayUtils.contains(DRIVER_LIST, metrics.getPlc().getDriverName())) { + throw new IllegalArgumentException("PLC collect must have valid driver name"); + } + // check address syntax + if (!ArrayUtils.contains(ADDRESS_SYNTAX, metrics.getPlc().getAddressSyntax())) { + throw new IllegalArgumentException("PLC collect must have valid address syntax"); + } + // check register address + if (metrics.getPlc().getRegisterAddresses() == null || metrics.getPlc().getRegisterAddresses().isEmpty()) { + throw new IllegalArgumentException("PLC collect must have register address"); + } + // check timeout is legal + if (Objects.nonNull(metrics.getPlc().getTimeout())) { + try { + Long.parseLong(metrics.getPlc().getTimeout()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("PLC collect must have valid timeout"); + } + } + + AtomicInteger addressCount = new AtomicInteger(); + metrics.getPlc().getRegisterAddresses().forEach(address -> { + if (address.contains("[") && address.contains("]")) { + String[] addressArray = address.split("\\["); + String num = addressArray[1].replace("]", ""); + addressCount.addAndGet(Integer.parseInt(num)); + } else { + addressCount.addAndGet(1); + } + }); + List aliasFields = metrics.getAliasFields(); + if (Objects.isNull(aliasFields)) { + throw new IllegalArgumentException("Please ensure that the number of aliasFields (tagName) in yml matches the number of registered addresses" + + "Number of AliasFields(tagList): 0 ,but Number of addresses:" + + addressCount.get()); + } + int tagListCount = aliasFields.size() - 1; + if (aliasFields.size() - 1 != addressCount.get()) { + throw new IllegalArgumentException("Please ensure that the number of aliasFields (tagName) in yml matches the number of registered addresses" + + "Number of AliasFields(tagList): " + + tagListCount + + " ,but Number of addresses:" + addressCount.get()); + } + } + + @Override + public void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics) { + + long startTime = System.currentTimeMillis(); + PlcProtocol plcProtocol = metrics.getPlc(); + PlcConnection plcConnection = null; + try { + String connectionString = getConnectionString(metrics); + plcConnection = CONNECTION_MANAGER.getConnection(connectionString); + if (!plcConnection.getMetadata().isReadSupported()) { + log.error("This connection doesn't support reading."); + } + // Check if this connection support reading of data. + if (!plcConnection.getMetadata().isWriteSupported()) { + log.error("This connection doesn't support writing."); + } + + PlcReadRequest readRequest = buildRequest(metrics, plcConnection); + PlcReadResponse response = readRequest.execute().get(Long.parseLong(plcProtocol.getTimeout()), TimeUnit.MILLISECONDS); + long responseTime = System.currentTimeMillis() - startTime; + Map resultMap = new HashMap<>(); + for (String tagName : response.getTagNames()) { + if (response.getResponseCode(tagName) == PlcResponseCode.OK) { + int numValues = response.getNumberOfValues(tagName); + // If it's just one element, output just one single line. + log.info("{}: {}", tagName, response.getPlcValue(tagName)); + if (numValues == 1) { + resultMap.put(tagName, response.getPlcValue(tagName).toString()); + } + // If it's more than one element, output each in a single row. + else { + for (int i = 0; i < numValues; i++) { + resultMap.put(tagName + "-" + i, response.getObject(tagName, i).toString()); + } + } + } else { + log.error("Error[{}]: {}", tagName, response.getResponseCode(tagName).name()); + } + } + if (COIL.equals(plcProtocol.getAddressSyntax())) { + resultMap = resultMap.entrySet() + .stream() + .peek(obj -> obj.setValue(String.valueOf(Boolean.TRUE.equals(Boolean.valueOf(obj.getValue())) ? 1 : 0))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + } + resultMap.put(CollectorConstants.RESPONSE_TIME, Long.toString(responseTime)); + List aliasFields = metrics.getAliasFields(); + CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder(); + for (String field : aliasFields) { + String fieldValue = resultMap.get(field); + valueRowBuilder.addColumns(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE)); + } + builder.addValues(valueRowBuilder.build()); + } catch (Exception e) { + builder.setCode(CollectRep.Code.FAIL); + String message = CommonUtil.getMessageFromThrowable(e); + builder.setMsg(message); + log.warn(message, e); + } finally { + if (plcConnection != null) { + try { + plcConnection.close(); + } catch (Exception e) { + log.warn(e.getMessage()); + } + } + } + + } + + protected abstract String getConnectionString(Metrics metrics); + + protected abstract PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection); +} \ No newline at end of file diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/modbus/ModbusCollectTest.java b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/modbus/ModbusCollectTest.java new file mode 100644 index 00000000000..53dc74ab8b5 --- /dev/null +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/modbus/ModbusCollectTest.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.collector.collect.modbus; + + +import org.apache.hertzbeat.collector.dispatch.DispatchConstants; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.ModbusProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test case for {@link ModbusCollectImpl} + */ +public class ModbusCollectTest { + private ModbusCollectImpl modbusCollect; + private Metrics metrics; + private CollectRep.MetricsData.Builder builder; + + @BeforeEach + public void setup() { + modbusCollect = new ModbusCollectImpl(); + ModbusProtocol modbus = ModbusProtocol.builder().build(); + metrics = Metrics.builder() + .modbus(modbus) + .build(); + builder = CollectRep.MetricsData.newBuilder(); + } + + @Test + void preCheck() { + // host is empty + assertThrows(IllegalArgumentException.class, () -> { + modbusCollect.preCheck(metrics); + }); + + // port is empty default add 502 + assertThrows(IllegalArgumentException.class, () -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + metrics.setModbus(modbus); + modbusCollect.preCheck(metrics); + }); + + // driverName version is empty + assertThrows(IllegalArgumentException.class, () -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + metrics.setModbus(modbus); + modbusCollect.preCheck(metrics); + }); + + // addressSyntax is empty + assertThrows(IllegalArgumentException.class, () -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + metrics.setModbus(modbus); + modbusCollect.preCheck(metrics); + }); + + // registerAddresses version is empty + assertThrows(IllegalArgumentException.class, () -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("holding-register"); + metrics.setModbus(modbus); + modbusCollect.preCheck(metrics); + }); + + // aliasFields is empty + assertThrows(IllegalArgumentException.class, () -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("holding-register"); + modbus.setRegisterAddresses(List.of("1")); + metrics.setModbus(modbus); + modbusCollect.preCheck(metrics); + }); + + // the number of aliasFields is not equal to registerAddresses(responseTime doesn't count) + assertThrows(IllegalArgumentException.class, () -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("holding-register"); + modbus.setRegisterAddresses(List.of("1", "2")); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0" + )); + metrics.setModbus(modbus); + modbusCollect.preCheck(metrics); + }); + + // everything is ok + assertDoesNotThrow(() -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("holding-register"); + modbus.setRegisterAddresses(List.of("1", "2", "3[2]")); + modbus.setSlaveId("2"); + modbus.setTimeout("500"); + metrics.setModbus(modbus); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0", + "holding-register:1", + "holding-register:2-0", + "holding-register:2-1" + )); + modbusCollect.preCheck(metrics); + }); + } + + @Test + void supportProtocol() { + Assertions.assertEquals(DispatchConstants.PROTOCOL_MODBUS, modbusCollect.supportProtocol()); + } + + @Test + void collect() { + // with holding-register + assertDoesNotThrow(() -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("holding-register"); + modbus.setRegisterAddresses(List.of("1", "2[3]")); + modbus.setSlaveId("1"); + modbus.setTimeout("500"); + + metrics.setModbus(modbus); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0", + "holding-register:1-0", + "holding-register:1-1", + "holding-register:1-2" + )); + modbusCollect.preCheck(metrics); + modbusCollect.collect(builder, 1L, "app", metrics); + }); + + // with coil + assertDoesNotThrow(() -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("coil"); + modbus.setRegisterAddresses(List.of("1", "2[3]")); + modbus.setSlaveId("1"); + modbus.setTimeout("500"); + + metrics.setModbus(modbus); + metrics.setAliasFields(List.of( + "responseTime", + "coil:0", + "coil:1-0", + "coil:1-1", + "coil:1-2" + )); + modbusCollect.preCheck(metrics); + modbusCollect.collect(builder, 1L, "app", metrics); + }); + + + // with slaveId2 + // with holding-register + assertDoesNotThrow(() -> { + ModbusProtocol modbus = ModbusProtocol.builder().build(); + modbus.setHost("127.0.0.1"); + modbus.setPort("502"); + modbus.setDriverName("modbus-tcp"); + modbus.setAddressSyntax("holding-register"); + modbus.setRegisterAddresses(List.of("1", "2[3]")); + modbus.setSlaveId("2"); + modbus.setTimeout("500"); + + metrics.setModbus(modbus); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0", + "holding-register:1-0", + "holding-register:1-1", + "holding-register:1-2" + )); + modbusCollect.preCheck(metrics); + modbusCollect.collect(builder, 1L, "app", metrics); + }); + + } + +} diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/plc/PlcCollectTest.java b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/plc/PlcCollectTest.java new file mode 100644 index 00000000000..f38e6b9f566 --- /dev/null +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/plc/PlcCollectTest.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.collector.collect.plc; + + +import org.apache.hertzbeat.collector.dispatch.DispatchConstants; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test case for {@link AbstractPlcCollectImpl} + */ +public class PlcCollectTest { + private AbstractPlcCollectImpl plcCollect; + private Metrics metrics; + private CollectRep.MetricsData.Builder builder; + + @BeforeEach + public void setup() { + plcCollect = new AbstractPlcCollectImpl() { + @Override + public String supportProtocol() { + return DispatchConstants.PROTOCOL_PLC; + } + + @Override + protected String getConnectionString(Metrics metrics) { + PlcProtocol plcProtocol = metrics.getPlc(); + return "modbus-tcp:tcp://" + plcProtocol.getHost() + ":" + plcProtocol.getPort() + "?unit-identifier=" + plcProtocol.getSlaveId(); + } + + @Override + protected PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection) { + PlcProtocol modbus = metrics.getPlc(); + List registerAddressList = modbus.getRegisterAddresses(); + // Create a new read request: + PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder(); + for (int i = 0; i < registerAddressList.size(); i++) { + String s1 = modbus.getAddressSyntax() + ":" + registerAddressList.get(i); + requestBuilder.addTagAddress(metrics.getPlc().getAddressSyntax() + ":" + i, s1); + } + return requestBuilder.build(); + } + }; + PlcProtocol plc = PlcProtocol.builder().build(); + metrics = Metrics.builder() + .plc(plc) + .build(); + builder = CollectRep.MetricsData.newBuilder(); + } + + @Test + void preCheck() { + // host is empty + assertThrows(IllegalArgumentException.class, () -> { + plcCollect.preCheck(metrics); + }); + + // port is empty default add 502 + assertThrows(IllegalArgumentException.class, () -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + metrics.setPlc(plc); + plcCollect.preCheck(metrics); + }); + + // driverName version is empty + assertThrows(IllegalArgumentException.class, () -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + metrics.setPlc(plc); + plcCollect.preCheck(metrics); + }); + + // addressSyntax is empty + assertThrows(IllegalArgumentException.class, () -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + metrics.setPlc(plc); + plcCollect.preCheck(metrics); + }); + + // registerAddresses version is empty + assertThrows(IllegalArgumentException.class, () -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("holding-register"); + metrics.setPlc(plc); + plcCollect.preCheck(metrics); + }); + + // aliasFields is empty + assertThrows(IllegalArgumentException.class, () -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("holding-register"); + plc.setRegisterAddresses(List.of("1")); + metrics.setPlc(plc); + plcCollect.preCheck(metrics); + }); + + // the number of aliasFields is not equal to registerAddresses(responseTime doesn't count) + assertThrows(IllegalArgumentException.class, () -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("holding-register"); + plc.setRegisterAddresses(List.of("1", "2")); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0" + )); + metrics.setPlc(plc); + plcCollect.preCheck(metrics); + }); + + // everything is ok + assertDoesNotThrow(() -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("holding-register"); + plc.setRegisterAddresses(List.of("1", "2", "3[2]")); + plc.setSlaveId("2"); + plc.setTimeout("500"); + metrics.setPlc(plc); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0", + "holding-register:1", + "holding-register:2-0", + "holding-register:2-1" + )); + plcCollect.preCheck(metrics); + }); + } + + @Test + void supportProtocol() { + Assertions.assertEquals(DispatchConstants.PROTOCOL_PLC, plcCollect.supportProtocol()); + } + + @Test + void collect() { + // with holding-register + assertDoesNotThrow(() -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("holding-register"); + plc.setRegisterAddresses(List.of("1", "2[3]")); + plc.setSlaveId("1"); + plc.setTimeout("500"); + + metrics.setPlc(plc); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0", + "holding-register:1-0", + "holding-register:1-1", + "holding-register:1-2" + )); + plcCollect.preCheck(metrics); + plcCollect.collect(builder, 1L, "app", metrics); + }); + + // with coil + assertDoesNotThrow(() -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("coil"); + plc.setRegisterAddresses(List.of("1", "2[3]")); + plc.setSlaveId("1"); + plc.setTimeout("500"); + + metrics.setPlc(plc); + metrics.setAliasFields(List.of( + "responseTime", + "coil:0", + "coil:1-0", + "coil:1-1", + "coil:1-2" + )); + plcCollect.preCheck(metrics); + plcCollect.collect(builder, 1L, "app", metrics); + }); + + + // with slaveId2 + // with holding-register + assertDoesNotThrow(() -> { + PlcProtocol plc = PlcProtocol.builder().build(); + plc.setHost("127.0.0.1"); + plc.setPort("502"); + plc.setDriverName("modbus-tcp"); + plc.setAddressSyntax("holding-register"); + plc.setRegisterAddresses(List.of("1", "2[3]")); + plc.setSlaveId("2"); + plc.setTimeout("500"); + + metrics.setPlc(plc); + metrics.setAliasFields(List.of( + "responseTime", + "holding-register:0", + "holding-register:1-0", + "holding-register:1-1", + "holding-register:1-2" + )); + plcCollect.preCheck(metrics); + plcCollect.collect(builder, 1L, "app", metrics); + }); + + } + +} diff --git a/hertzbeat-collector/hertzbeat-collector-collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect b/hertzbeat-collector/hertzbeat-collector-collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect index b6551797e2a..0cbd4f01ea8 100644 --- a/hertzbeat-collector/hertzbeat-collector-collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect +++ b/hertzbeat-collector/hertzbeat-collector-collector/src/main/resources/META-INF/services/org.apache.hertzbeat.collector.collect.AbstractCollect @@ -29,3 +29,4 @@ org.apache.hertzbeat.collector.collect.mqtt.MqttCollectImpl org.apache.hertzbeat.collector.collect.ipmi2.IpmiCollectImpl org.apache.hertzbeat.collector.collect.kafka.KafkaCollectImpl org.apache.hertzbeat.collector.collect.sd.HttpSdCollectImpl +org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl \ No newline at end of file diff --git a/hertzbeat-collector/hertzbeat-collector-common/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java b/hertzbeat-collector/hertzbeat-collector-common/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java index e6d00dfa179..569950cb218 100644 --- a/hertzbeat-collector/hertzbeat-collector-common/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java +++ b/hertzbeat-collector/hertzbeat-collector-common/src/main/java/org/apache/hertzbeat/collector/dispatch/DispatchConstants.java @@ -217,4 +217,14 @@ public interface DispatchConstants { * protocol kafka */ String PROTOCOL_KAFKA = "kclient"; + + /** + * protocol plc + */ + String PROTOCOL_PLC = "plc"; + + /** + * protocol modbus + */ + String PROTOCOL_MODBUS = "modbus"; } diff --git a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java index 261f3bfc48c..6a2838577e8 100644 --- a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java +++ b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java @@ -32,6 +32,8 @@ import org.apache.hertzbeat.common.entity.job.protocol.DnsProtocol; import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol; import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol; +import org.apache.hertzbeat.common.entity.job.protocol.ModbusProtocol; +import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol; import org.apache.hertzbeat.common.entity.job.protocol.RegistryProtocol; import org.apache.hertzbeat.common.entity.job.protocol.IcmpProtocol; import org.apache.hertzbeat.common.entity.job.protocol.ImapProtocol; @@ -255,7 +257,14 @@ public class Metrics { * Collect sd data protocol */ private ServiceDiscoveryProtocol sdProtocol; - + /** + * Monitoring configuration information using the public plc protocol + */ + private PlcProtocol plc; + /** + * Monitoring configuration information using the public modBus protocol + */ + private ModbusProtocol modbus; /** * collector use - Temporarily store subTask metrics response data */ diff --git a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/ModbusProtocol.java b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/ModbusProtocol.java new file mode 100644 index 00000000000..dff07ad4436 --- /dev/null +++ b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/ModbusProtocol.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.common.entity.job.protocol; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + + +/** + * Modbus Protocol + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ModbusProtocol { + + /** + * IP ADDRESS OR DOMAIN NAME OF THE PEER HOST + */ + private String host; + /** + * Port number + */ + private String port; + + private String driverName; + + private String addressSyntax; + + private String slaveId; + + private String timeout; + + private List registerAddresses; +} diff --git a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/PlcProtocol.java b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/PlcProtocol.java new file mode 100644 index 00000000000..bbc95872909 --- /dev/null +++ b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/job/protocol/PlcProtocol.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.common.entity.job.protocol; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Plc Protocol + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PlcProtocol { + /** + * IP ADDRESS OR DOMAIN NAME OF THE PEER HOST + */ + private String host; + /** + * Port number + */ + private String port; + + private String driverName; + + private String addressSyntax; + + private String slaveId; + + private String timeout; + + private List registerAddresses; + +} diff --git a/hertzbeat-manager/src/main/resources/define/app-modbus.yml b/hertzbeat-manager/src/main/resources/define/app-modbus.yml new file mode 100644 index 00000000000..4da9df377be --- /dev/null +++ b/hertzbeat-manager/src/main/resources/define/app-modbus.yml @@ -0,0 +1,236 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The monitoring type category:service-application service monitoring db-database monitoring mid-middleware custom-custom monitoring os-operating system monitoring +category: service +# The monitoring type eg: linux windows tomcat mysql aws... +app: modbus +# The app api i18n name +name: + zh-CN: ModBus服务器 + en-US: ModBus Server +# The description and help of this monitoring type +help: + zh-CN: HertzBeat对支持Modbus协议的服务进行(保持寄存器和线圈)相关指标进行采集 + en-US: HertzBeat collects metrics related to maintaining registers and coils for services that support Modbus protocol + zh-TW: HertzBeat對支持Modbus協定的服務進行(保持寄存器和線圈)相關名額進行採集 +# Input params define for monitoring(render web ui by the definition) +params: + # field-param field key + - field: host + # name-param field display i18n name + name: + zh-CN: ModBus服务Host + en-US: ModBus Server Host + # type-param field type(most mapping the html input type) + type: host + # required-true or false + required: true + # field-param field key + - field: port + # name-param field display i18n name + name: + zh-CN: 端口 + en-US: Port + # type-param field type(most mapping the html input type) + type: number + # when type is number, range is required + range: '[0,65535]' + # required-true or false + required: true + # default value + defaultValue: 502 + - field: slaveId + # name-param field display i18n name + name: + zh-CN: slaveId + en-US: slaveId + # type-param field type(most mapping the html input type) + type: text + # required-true or false + required: false + defaultValue: 1 + - field: timeout + name: + zh-CN: 请求超时时间(ms) + en-US: Request Timeout(ms) + type: number + # when type is number, range is required + range: '[400,200000]' + required: false + defaultValue: 6000 + hide: true + - field: holdingRegisterAddresses + # name-param field display i18n name + name: + zh-CN: 保持寄存器地址 + en-US: Holding Registers address + # type-param field type(most mapping the html input type) + type: array + # param field input placeholder + placeholder: 'Input RegisterAddress' + # required-true or false + required: true + # hide param-true or false + # hide: true + - field: coilRegisterAddresses + # name-param field display i18n name + name: + zh-CN: 线圈寄存器地址 + en-US: Coil Register address + # type-param field type(most mapping the html input type) + type: array + # param field input placeholder + placeholder: 'Input RegisterAddress' + # required-true or false + required: true + # hide param-true or false +# hide: true + +# collect metrics config list +metrics: + # metrics - summary + - name: holding-register + i18n: + zh-CN: 保持寄存器 统计信息 + en-US: holding-register stats + # metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel + # priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue + priority: 0 + # collect metrics content + fields: + - field: responseTime + type: 0 + unit: ms + i18n: + zh-CN: 响应时间 + en-US: Response Time + # field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field + - field: address-0 + type: 0 + i18n: + zh-CN: address-0 + en-US: address-0 + # field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field + - field: address-1-0 + type: 0 + i18n: + zh-CN: address-1-0 + en-US: address-1-0 + - field: address-1-1 + type: 0 + i18n: + zh-CN: address-1-1 + en-US: address-1-1 + - field: address-1-2 + type: 0 + i18n: + zh-CN: address-1-2 + en-US: address-1-2 + # 指标别名列表,按照寄存器地址来进行命名的 + # 例如 地址为下标为0 ,值为m holding-register:0 + # 地址为下标为1,值为m[2] holding-register:1-0 holding-register:1-1 + aliasFields: + - responseTime + - holding-register:0 + - holding-register:1-0 + - holding-register:1-1 + - holding-register:1-2 + # mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime + calculates: + - responseTime=responseTime + - address-0=holding-register:0 + - address-1-0=holding-register:1-0 + - address-1-1=holding-register:1-1 + - address-1-2=holding-register:1-2 + protocol: modbus + # the config content when protocol is http + modbus: + # host + host: ^_^host^_^ + # port + port: ^_^port^_^ + driverName: modbus-tcp + addressSyntax: holding-register + slaveId: ^_^slaveId^_^ + timeout: ^_^timeout^_^ + registerAddresses: [ ^_^holdingRegisterAddresses^_^ ] + + # metrics - summary + - name: coil + i18n: + zh-CN: 线圈 统计信息 + en-US: coil stats + # metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel + # priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue + priority: 1 + # collect metrics content + fields: + - field: responseTime + type: 0 + unit: ms + i18n: + zh-CN: 响应时间 + en-US: Response Time + # field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field + - field: address-0 + type: 0 + i18n: + zh-CN: address-0 + en-US: address-0 + # field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field + - field: address-1-0 + type: 0 + i18n: + zh-CN: address-1-0 + en-US: address-1-0 + - field: address-1-1 + type: 0 + i18n: + zh-CN: address-1-1 + en-US: address-1-1 + - field: address-1-2 + type: 0 + i18n: + zh-CN: address-1-2 + en-US: address-1-2 + # 指标别名列表,按照寄存器地址来进行命名的 + # 例如 地址为下标为0 ,值为m coil:0 + # 地址为下标为1,值为m[2] coil:1-0 coil:1-1 + aliasFields: + - responseTime + - coil:0 + - coil:1-0 + - coil:1-1 + - coil:1-2 + # mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime + calculates: + - responseTime=responseTime + - address-0=coil:0 + - address-1-0=coil:1-0 + - address-1-1=coil:1-1 + - address-1-2=coil:1-2 + protocol: modbus + # the config content when protocol is http + modbus: + # host + host: ^_^host^_^ + # port + port: ^_^port^_^ + driverName: modbus-tcp + addressSyntax: coil + slaveId: ^_^slaveId^_^ + timeout: ^_^timeout^_^ + registerAddresses: [ ^_^coilRegisterAddresses^_^ ] \ No newline at end of file diff --git a/home/docs/help/modbus.md b/home/docs/help/modbus.md new file mode 100644 index 00000000000..a26462cb410 --- /dev/null +++ b/home/docs/help/modbus.md @@ -0,0 +1,80 @@ +--- +id: modbus +title: Monitoring Modbus +sidebar_label: Modbus Monitor +keywords: [ open source monitoring tool, Modbus monitoring ] +--- + +> The response of Modbus service and other related indicators are monitored. + +### Configuration Parameters + +| Parameter Name | Parameter Help Description | +|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| Host of Modbus Service | The IPv4, IPv6, or domain name of the Modbus device to be monitored. Note ⚠️ do not include the protocol header (e.g., https://, http://). | +| Task Name | A name that identifies this monitoring task; the name must be unique. | +| Port | The port used for Modbus network communication. | +| Slave ID (slaveId) | The ID of the slave device in the Modbus network. | +| Holding Register Address | Used for categorizing and managing monitored resources. | +| Coil Register Address | Additional notes and descriptions for this monitoring task; users can add remarks here. | +| Timeout | The allowed time for collecting a response. | + +### Collected Metrics + +#### Metric Set: holding-register + +1. The number of parameters must match the total number of coil register addresses specified in the parameters. +2. Alias format for parameters: holding-register:m or holding-register:m-n + +Parameter example: + +Coil register addresses: + +```text +1,2[3] +``` + +Parameter alias names: + +```yaml +aliasFields: + - responseTime + - holding-register:0 + - holding-register:1-0 + - holding-register:1-1 + - holding-register:1-2 +``` + +| Metric Name | Metric Unit | Metric Help Description | +|----------------------------|--------------|-----------------------------------------------------------------| +| Response Time | Milliseconds | The time required by the Modbus server to respond to a request. | +| Holding Register Parameter | | Setpoint for analog output | + +#### Metric Set: coil + +1. The number of parameters must match the total number of coil register addresses specified in the parameters. +2. Alias format for parameters: coil:m or coil:m-n + +Parameter example: + +Coil register addresses: + +```text +1,2[3] +``` + +Parameter alias names: + +```yaml +aliasFields: + - responseTime + - coil:0 + - coil:1-0 + - coil:1-1 + - coil:1-2 +``` + +| Metric Name | Metric Unit | Metric Help Description | +|---------------|--------------|-----------------------------------------------------------------| +| Response Time | Milliseconds | The time required by the Modbus server to respond to a request. | +| Coil Status | | Coil status (0 or 1) | diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/modbus.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/modbus.md new file mode 100644 index 00000000000..d17a81bc620 --- /dev/null +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/modbus.md @@ -0,0 +1,80 @@ +--- +id: modbus +title: Monitoring Modbus +sidebar_label: Modbus Monitor +keywords: [ open source monitoring tool, Modbus监控 ] +--- + +> Modbus 服务的响应等相关指标进行监测。 + +### 配置参数 + +| 参数名称 | 参数帮助描述 | +|---------------|-----------------------------------------------------------| +| Modbus服务的Host | 被监控的Modbus的IPV4,IPV6或域名。注意⚠️不带协议头(eg: https://, http://)。 | +| 任务名称 | 标识此监控的名称,名称需要保证唯一性。 | +| 端口 | Modbus网络的端口。 | +| slaveId | Modbus网络中从机设备ID。 | +| 保持寄存器地址 | 用于对监控资源进行分类管理。 | +| 线圈寄存器地址 | 更多标识和描述此监控的备注信息,用户可以在这里备注信息。 | +| 超时 | 允许收集响应时间 | + +### 采集指标 + +#### 指标集合:holding-register + +1. 参数数量需要与参数中线圈寄存器地址的总数量一样 +2. 参数别名格式: holding-register:m 或 holding-register:m-n + +参数示例: + +线圈寄存器地址: + +```text +1,2[3] +``` + +参数别名名称: + +```yaml +aliasFields: + - responseTime + - holding-register:0 + - holding-register:1-0 + - holding-register:1-1 + - holding-register:1-2 +``` + +| 指标名称 | 指标单位 | 指标帮助描述 | +|---------|------|---------------------| +| 响应时间 | 毫秒 | Modbus服务器响应请求所需的时间。 | +| 保持寄存器参数 | | 模拟量输出设定值 | + +#### 指标集合:coil + +1. 参数数量需要与参数中线圈寄存器地址的总数量一样 +2. 参数别名格式: coil:m 或 coil:m-n + +参数示例: + +线圈寄存器地址: + +```text +1,2[3] +``` + +参数别名名称: + +```yaml +aliasFields: + - responseTime + - coil:0 + - coil:1-0 + - coil:1-1 + - coil:1-2 +``` + +| 指标名称 | 指标单位 | 指标帮助描述 | +|------|------|---------------------| +| 响应时间 | 毫秒 | Modbus服务器响应请求所需的时间。 | +| 线圈状态 | | 线圈状态 (0或1) | diff --git a/home/sidebars.json b/home/sidebars.json index 5e701f65569..d6e0c7b7b92 100755 --- a/home/sidebars.json +++ b/home/sidebars.json @@ -92,7 +92,8 @@ "help/dns", "help/ftp", "help/websocket", - "help/mqtt" + "help/mqtt", + "help/modbus" ] }, { diff --git a/material/licenses/LICENSE b/material/licenses/LICENSE index 9f9a3a4bea0..19d9fcaa84b 100644 --- a/material/licenses/LICENSE +++ b/material/licenses/LICENSE @@ -357,6 +357,8 @@ The text of each license is the standard Apache 2.0 license. https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0 https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0 https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0 + https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0 + https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-modbus/0.12.0 Apache-2.0 https://mvnrepository.com/artifact/org.attoparser/attoparser/2.0.7.RELEASE Apache-2.0 https://mvnrepository.com/artifact/org.freemarker/freemarker/2.3.32 Apache-2.0 https://mvnrepository.com/artifact/org.flywaydb/flyway-core/10.11.1 diff --git a/material/licenses/backend/LICENSE b/material/licenses/backend/LICENSE index 958cd9b343f..31c242b64c6 100644 --- a/material/licenses/backend/LICENSE +++ b/material/licenses/backend/LICENSE @@ -357,6 +357,8 @@ The text of each license is the standard Apache 2.0 license. https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0 https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0 https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0 + https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0 + https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-modbus/0.12.0 Apache-2.0 https://mvnrepository.com/artifact/org.attoparser/attoparser/2.0.7.RELEASE Apache-2.0 https://mvnrepository.com/artifact/org.freemarker/freemarker/2.3.32 Apache-2.0 https://mvnrepository.com/artifact/org.flywaydb/flyway-core/10.11.1 diff --git a/material/licenses/collector/LICENSE b/material/licenses/collector/LICENSE index 2620a42bd70..60853a9f8bc 100644 --- a/material/licenses/collector/LICENSE +++ b/material/licenses/collector/LICENSE @@ -288,6 +288,8 @@ The text of each license is the standard Apache 2.0 license. https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0 https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0 https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0 + https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0 + https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-modbus/0.12.0 Apache-2.0 https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator/8.0.1.Final Apache-2.0 https://mvnrepository.com/artifact/org.lz4/lz4-java/1.8.0 Apache-2.0 https://mvnrepository.com/artifact/org.mongodb/mongodb-driver-core/4.6.1 Apache-2.0