Browse Source
* Add SNMP type to transport profiles * Update reference config for transport profiles * Initial implementation to retrieve a value using SNMP GET message * Fix license header * Move config files * Use async handler for SNMP response * Send SNMP agent response to attributes and telemetry * UI: add SNMP option for device profile * UI: use JSON field to set SNMP profile configuration * Handle device profile update event * Use concurrent hash map for SNMP device sessions * UI: Add device transport configuration control * Cancel async request to avoid memory leak and timeout handling * Start SNMP pooling after application started * Move OID per profile mapping to SNMP transport context * Fix build after merge with 3.2.1-SNAPSHOT * Init device sessions on TB start * Fix build error, refactoring * Update session context on device update * Set device info on session context creating * Refresh pooling params on device or profile update * Update license header * Process device and profile transport config update * Process SNMP response asynchronously * Change polling implementationpull/4248/head
committed by
GitHub
37 changed files with 1681 additions and 9 deletions
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.common.data.device; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonAnyGetter; |
|||
import com.fasterxml.jackson.annotation.JsonAnySetter; |
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
public abstract class JsonBasedTransportConfiguration { |
|||
|
|||
@JsonIgnore |
|||
private final Map<String, Object> properties = new HashMap<>(); |
|||
|
|||
@JsonAnyGetter |
|||
public Map<String, Object> properties() { |
|||
return this.properties; |
|||
} |
|||
|
|||
@JsonAnySetter |
|||
public void put(String name, Object value) { |
|||
this.properties.put(name, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.common.data.device.data; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import lombok.Data; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
|
|||
@Data |
|||
public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { |
|||
|
|||
private String address; |
|||
private int port; |
|||
private String community; |
|||
private String protocolVersion; |
|||
|
|||
@Override |
|||
public DeviceTransportType getType() { |
|||
return DeviceTransportType.SNMP; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public boolean isValid() { |
|||
return StringUtils.isNotEmpty(this.address) |
|||
&& this.port > 0 |
|||
&& StringUtils.isNotEmpty(this.community) |
|||
&& StringUtils.isNotEmpty(this.protocolVersion); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.common.data.device.profile; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.kv.DataType; |
|||
|
|||
//TODO: rename class
|
|||
@Data |
|||
public class SnmpDeviceProfileKvMapping { |
|||
private String key; |
|||
private DataType type; |
|||
private String method; |
|||
private String oid; |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.common.data.device.profile; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
|
|||
import java.util.List; |
|||
import java.util.stream.Collectors; |
|||
import java.util.stream.Stream; |
|||
|
|||
@Data |
|||
public class SnmpProfileTransportConfiguration implements DeviceProfileTransportConfiguration { |
|||
|
|||
private int poolPeriodMs; |
|||
private int timeoutMs; |
|||
private int retries; |
|||
private List<SnmpDeviceProfileKvMapping> attributes; |
|||
private List<SnmpDeviceProfileKvMapping> telemetry; |
|||
|
|||
@Override |
|||
public DeviceTransportType getType() { |
|||
return DeviceTransportType.SNMP; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public List<SnmpDeviceProfileKvMapping> getKvMappings() { |
|||
return Stream.concat(attributes.stream(), telemetry.stream()).collect(Collectors.toList()); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 The Thingsboard Authors |
|||
|
|||
Licensed 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. |
|||
|
|||
--> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
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>org.thingsboard.common</groupId> |
|||
<version>3.2.1-SNAPSHOT</version> |
|||
<artifactId>transport</artifactId> |
|||
</parent> |
|||
|
|||
<groupId>org.thingsboard.common.transport</groupId> |
|||
<artifactId>snmp</artifactId> |
|||
<packaging>jar</packaging> |
|||
|
|||
<name>Thingsboard SNMP Transport Common</name> |
|||
<url>https://thingsboard.io</url> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<main.dir>${basedir}/../../..</main.dir> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.thingsboard.common.transport</groupId> |
|||
<artifactId>transport-api</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework</groupId> |
|||
<artifactId>spring-context-support</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework</groupId> |
|||
<artifactId>spring-context</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.slf4j</groupId> |
|||
<artifactId>slf4j-api</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.snmp4j</groupId> |
|||
<artifactId>snmp4j</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.common</groupId> |
|||
<artifactId>dao-api</artifactId> |
|||
</dependency> |
|||
</dependencies> |
|||
</project> |
|||
@ -0,0 +1,142 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.transport.snmp; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.snmp4j.PDU; |
|||
import org.snmp4j.Snmp; |
|||
import org.snmp4j.smi.OID; |
|||
import org.snmp4j.smi.VariableBinding; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileKvMapping; |
|||
import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|||
import org.thingsboard.server.common.transport.TransportContext; |
|||
import org.thingsboard.server.dao.device.DeviceCredentialsService; |
|||
import org.thingsboard.server.transport.snmp.session.DeviceSessionCtx; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service("SnmpTransportContext") |
|||
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") |
|||
@Slf4j |
|||
public class SnmpTransportContext extends TransportContext { |
|||
@Autowired |
|||
DeviceCredentialsService deviceCredentialsService; |
|||
|
|||
@Autowired |
|||
SnmpTransportService snmpTransportService; |
|||
|
|||
@Getter |
|||
private final Map<DeviceProfileId, SnmpProfileTransportConfiguration> profileTransportConfig = new ConcurrentHashMap<>(); |
|||
@Getter |
|||
private final Map<DeviceProfileId, List<PDU>> pdusPerProfile = new ConcurrentHashMap<>(); |
|||
@Getter |
|||
private final Map<DeviceId, DeviceSessionCtx> deviceSessions = new ConcurrentHashMap<>(); |
|||
|
|||
public Optional<SnmpDeviceProfileKvMapping> findAttributesMapping(DeviceProfileId deviceProfileId, OID responseOid) { |
|||
if (profileTransportConfig.containsKey(deviceProfileId)) { |
|||
return findMapping(responseOid, profileTransportConfig.get(deviceProfileId).getAttributes()); |
|||
} |
|||
return Optional.empty(); |
|||
} |
|||
|
|||
public Optional<SnmpDeviceProfileKvMapping> findTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { |
|||
if (profileTransportConfig.containsKey(deviceProfileId)) { |
|||
return findMapping(responseOid, profileTransportConfig.get(deviceProfileId).getTelemetry()); |
|||
} |
|||
return Optional.empty(); |
|||
} |
|||
|
|||
private Optional<SnmpDeviceProfileKvMapping> findMapping(OID responseOid, List<SnmpDeviceProfileKvMapping> mappings) { |
|||
return mappings.stream() |
|||
.filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) |
|||
//TODO: OID shouldn't be duplicated in the config, add backend and UI verification
|
|||
.findFirst(); |
|||
} |
|||
|
|||
public void initPduListPerProfile() { |
|||
profileTransportConfig.forEach(this::updatePduListPerProfile); |
|||
} |
|||
|
|||
public void updatePduListPerProfile(DeviceProfileId id, SnmpProfileTransportConfiguration config) { |
|||
pdusPerProfile.put(id, createPduList(config)); |
|||
} |
|||
|
|||
public void updateDeviceSessionCtx(Device device, DeviceProfile deviceProfile, Snmp snmp) { |
|||
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); |
|||
if (DeviceCredentialsType.ACCESS_TOKEN.equals(credentials.getCredentialsType())) { |
|||
SnmpDeviceTransportConfiguration snmpDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); |
|||
if (snmpDeviceTransportConfiguration.isValid()) { |
|||
DeviceSessionCtx deviceSessionCtx = new DeviceSessionCtx(this, credentials.getCredentialsId(), snmpDeviceTransportConfiguration, snmp, device.getId(), deviceProfile); |
|||
deviceSessionCtx.createSessionInfo(ctx -> getTransportService().registerAsyncSession(deviceSessionCtx.getSessionInfo(), deviceSessionCtx)); |
|||
this.deviceSessions.put(device.getId(), deviceSessionCtx); |
|||
} |
|||
} else { |
|||
log.warn("[{}] Expected credentials type is {} but found {}", device.getId(), DeviceCredentialsType.ACCESS_TOKEN, credentials.getCredentialsType()); |
|||
} |
|||
} |
|||
|
|||
public ExecutorService getSnmpCallbackExecutor() { |
|||
return snmpTransportService.getSnmpCallbackExecutor(); |
|||
} |
|||
|
|||
private List<PDU> createPduList(SnmpProfileTransportConfiguration deviceProfileConfig) { |
|||
Map<String, List<VariableBinding>> varBindingPerMethod = new HashMap<>(); |
|||
|
|||
deviceProfileConfig.getKvMappings().forEach(mapping -> varBindingPerMethod |
|||
.computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) |
|||
.add(new VariableBinding(new OID(mapping.getOid())))); |
|||
|
|||
return varBindingPerMethod.keySet().stream() |
|||
.map(method -> { |
|||
PDU request = new PDU(); |
|||
request.setType(getSnmpMethod(method)); |
|||
request.addAll(varBindingPerMethod.get(method)); |
|||
return request; |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
//TODO: Extract SNMP methods to enum
|
|||
private int getSnmpMethod(String configMethod) { |
|||
switch (configMethod) { |
|||
case "get": |
|||
return PDU.GET; |
|||
case "getNext": |
|||
case "response": |
|||
case "set": |
|||
default: |
|||
return -1; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.transport.snmp; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.snmp4j.Snmp; |
|||
import org.snmp4j.transport.DefaultUdpTransportMapping; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.core.annotation.Order; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.DeviceInfo; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|||
import org.thingsboard.server.dao.device.DeviceProfileService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
import org.thingsboard.server.transport.snmp.session.DeviceSessionCtx; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.io.IOException; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Service("SnmpTransportService") |
|||
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") |
|||
@Slf4j |
|||
public class SnmpTransportService { |
|||
|
|||
private static final int ENTITY_PACK_LIMIT = 1024; |
|||
|
|||
@Autowired |
|||
private SnmpTransportContext snmpTransportContext; |
|||
|
|||
@Autowired |
|||
DeviceProfileService deviceProfileService; |
|||
|
|||
@Autowired |
|||
TenantService tenantService; |
|||
|
|||
@Autowired |
|||
DeviceService deviceService; |
|||
|
|||
@Getter |
|||
private ExecutorService snmpCallbackExecutor; |
|||
private Snmp snmp; |
|||
private ScheduledExecutorService pollingExecutor; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
log.info("Starting SNMP transport..."); |
|||
pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); |
|||
//TODO: Set parallelism value in the config
|
|||
snmpCallbackExecutor = Executors.newWorkStealingPool(20); |
|||
initializeSnmp(); |
|||
log.info("SNMP transport started!"); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdown() { |
|||
log.info("Stopping SNMP transport!"); |
|||
if (pollingExecutor != null) { |
|||
pollingExecutor.shutdownNow(); |
|||
} |
|||
if (snmpCallbackExecutor != null) { |
|||
snmpCallbackExecutor.shutdownNow(); |
|||
} |
|||
if (snmp != null) { |
|||
try { |
|||
snmp.close(); |
|||
} catch (IOException e) { |
|||
log.error(e.getMessage(), e); |
|||
} |
|||
} |
|||
log.info("SNMP transport stopped!"); |
|||
} |
|||
|
|||
@EventListener(ApplicationReadyEvent.class) |
|||
@Order(value = 2) |
|||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { |
|||
log.info("Received application ready event. Starting SNMP polling."); |
|||
initSessionCtxList(); |
|||
startPolling(); |
|||
} |
|||
|
|||
private void initializeSnmp() { |
|||
try { |
|||
this.snmp = new Snmp(new DefaultUdpTransportMapping()); |
|||
this.snmp.listen(); |
|||
} catch (IOException e) { |
|||
//TODO: what should be done if transport wasn't initialized?
|
|||
log.error(e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
private void initSessionCtxList() { |
|||
//TODO: This approach works for monolith, in cluster the same data will be fetched by each node.
|
|||
for (Tenant tenant : new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT)) { |
|||
TenantId tenantId = tenant.getTenantId(); |
|||
for (DeviceProfile deviceProfile : new PageDataIterable<>(pageLink -> deviceProfileService.findDeviceProfiles(tenantId, pageLink), ENTITY_PACK_LIMIT)) { |
|||
if (DeviceTransportType.SNMP.equals(deviceProfile.getTransportType())) { |
|||
snmpTransportContext.getProfileTransportConfig().put(deviceProfile.getId(), |
|||
(SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()); |
|||
initDeviceSessions(deviceProfile); |
|||
} |
|||
} |
|||
} |
|||
snmpTransportContext.initPduListPerProfile(); |
|||
} |
|||
|
|||
private void initDeviceSessions(DeviceProfile deviceProfile) { |
|||
for (DeviceInfo deviceInfo : new PageDataIterable<>(pageLink -> deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId(), pageLink), ENTITY_PACK_LIMIT)) { |
|||
snmpTransportContext.updateDeviceSessionCtx(deviceInfo, deviceProfile, snmp); |
|||
} |
|||
} |
|||
|
|||
private void startPolling() { |
|||
//TODO: Get poll period from configuration;
|
|||
int poolPeriodSeconds = 1; |
|||
pollingExecutor.scheduleAtFixedRate(() -> snmpTransportContext.getDeviceSessions().values().forEach(DeviceSessionCtx::executeSnmpRequest), |
|||
0, poolPeriodSeconds, TimeUnit.SECONDS); |
|||
} |
|||
} |
|||
@ -0,0 +1,206 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.transport.snmp.session; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.snmp4j.CommunityTarget; |
|||
import org.snmp4j.Snmp; |
|||
import org.snmp4j.Target; |
|||
import org.snmp4j.mp.SnmpConstants; |
|||
import org.snmp4j.smi.GenericAddress; |
|||
import org.snmp4j.smi.OctetString; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.transport.SessionMsgListener; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; |
|||
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
|||
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; |
|||
import org.thingsboard.server.transport.snmp.SnmpTransportContext; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.function.Consumer; |
|||
|
|||
@Slf4j |
|||
public class DeviceSessionCtx extends DeviceAwareSessionContext implements SessionMsgListener { |
|||
private final AtomicInteger msgIdSeq = new AtomicInteger(0); |
|||
|
|||
@Getter |
|||
@Setter |
|||
private SnmpDeviceTransportConfiguration deviceTransportConfig; |
|||
@Getter |
|||
@Setter |
|||
private SnmpSessionListener snmpSessionListener; |
|||
@Getter |
|||
@Setter |
|||
private Target target; |
|||
@Getter |
|||
@Setter |
|||
private volatile TransportProtos.SessionInfoProto sessionInfo; |
|||
|
|||
private Snmp snmp; |
|||
private SnmpProfileTransportConfiguration snmpProfileTransportConfiguration; |
|||
private long previousRequestExecutedAt = 0; |
|||
|
|||
public DeviceSessionCtx(SnmpTransportContext transportContext, String token, SnmpDeviceTransportConfiguration deviceTransportConfig, |
|||
Snmp snmp, DeviceId deviceId, DeviceProfile deviceProfile) { |
|||
super(UUID.randomUUID()); |
|||
this.snmpSessionListener = new SnmpSessionListener(transportContext, token); |
|||
super.setDeviceId(deviceId); |
|||
super.setDeviceProfile(deviceProfile); |
|||
//TODO: What should be done if snmp null?
|
|||
if (snmp != null) { |
|||
this.snmp = snmp; |
|||
} |
|||
this.snmpProfileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); |
|||
initTarget(this.snmpProfileTransportConfiguration, deviceTransportConfig); |
|||
} |
|||
|
|||
@Override |
|||
public int nextMsgId() { |
|||
return msgIdSeq.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { |
|||
} |
|||
|
|||
@Override |
|||
public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { |
|||
} |
|||
|
|||
@Override |
|||
public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) { |
|||
} |
|||
|
|||
@Override |
|||
public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { |
|||
} |
|||
|
|||
@Override |
|||
public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { |
|||
} |
|||
|
|||
@Override |
|||
public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { |
|||
super.onDeviceProfileUpdate(sessionInfo, deviceProfile); |
|||
if (DeviceTransportType.SNMP.equals(deviceProfile.getTransportType())) { |
|||
snmpProfileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); |
|||
snmpSessionListener.getSnmpTransportContext().getProfileTransportConfig().put( |
|||
deviceProfile.getId(), |
|||
snmpProfileTransportConfiguration); |
|||
snmpSessionListener.getSnmpTransportContext().updatePduListPerProfile(deviceProfile.getId(), snmpProfileTransportConfiguration); |
|||
} else { |
|||
//TODO: should the context be removed from the map?
|
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) { |
|||
super.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); |
|||
if (super.getDeviceProfile() != null && DeviceTransportType.SNMP.equals(super.getDeviceProfile().getTransportType())) { |
|||
snmpSessionListener.getSnmpTransportContext().updateDeviceSessionCtx(device, deviceProfile, null); |
|||
SnmpProfileTransportConfiguration profileTransportConfig = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); |
|||
SnmpDeviceTransportConfiguration deviceTransportConfig = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); |
|||
initTarget(profileTransportConfig, deviceTransportConfig); |
|||
} else { |
|||
//TODO: should the context be removed from the map?
|
|||
} |
|||
} |
|||
|
|||
public void createSessionInfo(Consumer<TransportProtos.SessionInfoProto> registerSession) { |
|||
getSnmpSessionListener().getSnmpTransportContext().getTransportService().process(DeviceTransportType.SNMP, |
|||
TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(getSnmpSessionListener().getToken()).build(), |
|||
new TransportServiceCallback<ValidateDeviceCredentialsResponse>() { |
|||
@Override |
|||
public void onSuccess(ValidateDeviceCredentialsResponse msg) { |
|||
if (msg.hasDeviceInfo()) { |
|||
sessionInfo = SessionInfoCreator.create(msg, getSnmpSessionListener().getSnmpTransportContext(), UUID.randomUUID()); |
|||
registerSession.accept(sessionInfo); |
|||
setDeviceInfo(msg.getDeviceInfo()); |
|||
} else { |
|||
log.warn("[{}] Failed to process device auth", getDeviceId()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
log.warn("[{}] Failed to process device auth", getDeviceId(), e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void executeSnmpRequest() { |
|||
long timeNow = System.currentTimeMillis(); |
|||
long nextRequestExecutionTime = previousRequestExecutedAt + snmpProfileTransportConfiguration.getPoolPeriodMs(); |
|||
if (nextRequestExecutionTime < timeNow) { |
|||
previousRequestExecutedAt = timeNow; |
|||
|
|||
snmpSessionListener.getSnmpTransportContext().getPdusPerProfile().get(deviceProfile.getId()).forEach(pdu -> { |
|||
try { |
|||
log.debug("[{}] Sending SNMP message...", pdu.getRequestID()); |
|||
snmp.send(pdu, |
|||
target, |
|||
deviceProfile.getId(), |
|||
snmpSessionListener); |
|||
} catch (IOException e) { |
|||
log.error(e.getMessage(), e); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private void initTarget(SnmpProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { |
|||
this.deviceTransportConfig = deviceTransportConfig; |
|||
CommunityTarget communityTarget = new CommunityTarget(); |
|||
communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + this.deviceTransportConfig.getAddress() + "/" + this.deviceTransportConfig.getPort())); |
|||
communityTarget.setVersion(getSnmpVersion(this.deviceTransportConfig.getProtocolVersion())); |
|||
communityTarget.setCommunity(new OctetString(this.deviceTransportConfig.getCommunity())); |
|||
communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); |
|||
communityTarget.setRetries(profileTransportConfig.getRetries()); |
|||
this.target = communityTarget; |
|||
log.info("SNMP target initialized: {}", this.target); |
|||
} |
|||
|
|||
//TODO: replace with enum, wtih preliminary discussion of type version in config (string or integer)
|
|||
private int getSnmpVersion(String configSnmpVersion) { |
|||
switch (configSnmpVersion) { |
|||
case ("v1"): |
|||
return SnmpConstants.version1; |
|||
case ("v2c"): |
|||
return SnmpConstants.version2c; |
|||
case ("v3"): |
|||
return SnmpConstants.version3; |
|||
default: |
|||
return -1; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.transport.snmp.session; |
|||
|
|||
import com.google.gson.JsonElement; |
|||
import com.google.gson.JsonObject; |
|||
import com.google.gson.JsonParser; |
|||
import com.google.gson.JsonSyntaxException; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.snmp4j.PDU; |
|||
import org.snmp4j.Snmp; |
|||
import org.snmp4j.event.ResponseEvent; |
|||
import org.snmp4j.event.ResponseListener; |
|||
import org.snmp4j.smi.VariableBinding; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.kv.DataType; |
|||
import org.thingsboard.server.common.transport.TransportContext; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
|||
import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
|||
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; |
|||
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.transport.snmp.SnmpTransportContext; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.function.Consumer; |
|||
|
|||
@Slf4j |
|||
@AllArgsConstructor |
|||
public class SnmpSessionListener implements ResponseListener { |
|||
|
|||
@Getter |
|||
private final SnmpTransportContext snmpTransportContext; |
|||
|
|||
@Getter |
|||
private final String token; |
|||
|
|||
@Override |
|||
public void onResponse(ResponseEvent event) { |
|||
((Snmp) event.getSource()).cancel(event.getRequest(), this); |
|||
snmpTransportContext.getSnmpCallbackExecutor().submit(() -> processSnmpResponse(event)); |
|||
} |
|||
|
|||
private void processSnmpResponse(ResponseEvent event) { |
|||
PDU response = event.getResponse(); |
|||
if (event.getError() != null) { |
|||
log.warn("Response error: {}", event.getError().getMessage(), event.getError()); |
|||
} |
|||
|
|||
if (response != null) { |
|||
log.debug("[{}] Processing SNMP response: {}", response.getRequestID(), response); |
|||
|
|||
DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); |
|||
TransportService transportService = snmpTransportContext.getTransportService(); |
|||
for (int i = 0; i < response.size(); i++) { |
|||
VariableBinding vb = response.get(i); |
|||
snmpTransportContext.findAttributesMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, |
|||
TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), |
|||
new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { |
|||
try { |
|||
transportService.process(sessionInfo, |
|||
convertToPostAttributes(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), |
|||
TransportServiceCallback.EMPTY); |
|||
reportActivity(sessionInfo); |
|||
} catch (Exception e) { |
|||
log.warn("Failed to process SNMP response: {}", e.getMessage(), e); |
|||
} |
|||
}))); |
|||
snmpTransportContext.findTelemetryMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, |
|||
TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), |
|||
new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { |
|||
try { |
|||
transportService.process(sessionInfo, |
|||
convertToPostTelemetry(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), |
|||
TransportServiceCallback.EMPTY); |
|||
reportActivity(sessionInfo); |
|||
|
|||
} catch (Exception e) { |
|||
log.warn("Failed to process SNMP response: {}", e.getMessage(), e); |
|||
} |
|||
}))); |
|||
} |
|||
} else { |
|||
log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); |
|||
} |
|||
} |
|||
|
|||
private TransportProtos.PostAttributeMsg convertToPostAttributes(String keyName, DataType dataType, String payload) throws AdaptorException { |
|||
try { |
|||
return JsonConverter.convertToAttributesProto(getKvJson(keyName, dataType, payload)); |
|||
} catch (IllegalStateException | JsonSyntaxException ex) { |
|||
//TODO: change the exception type
|
|||
throw new AdaptorException(ex); |
|||
} |
|||
} |
|||
|
|||
private TransportProtos.PostTelemetryMsg convertToPostTelemetry(String keyName, DataType dataType, String payload) throws AdaptorException { |
|||
try { |
|||
return JsonConverter.convertToTelemetryProto(getKvJson(keyName, dataType, payload)); |
|||
} catch (IllegalStateException | JsonSyntaxException ex) { |
|||
//TODO: change the exception type
|
|||
throw new AdaptorException(ex); |
|||
} |
|||
} |
|||
|
|||
private JsonElement getKvJson(String keyName, DataType dataType, String payload) throws AdaptorException { |
|||
JsonObject result = new JsonObject(); |
|||
switch (dataType) { |
|||
case LONG: |
|||
result.addProperty(keyName, Long.parseLong(payload)); |
|||
break; |
|||
case BOOLEAN: |
|||
result.addProperty(keyName, Boolean.parseBoolean(payload)); |
|||
break; |
|||
case DOUBLE: |
|||
result.addProperty(keyName, Double.parseDouble(payload)); |
|||
break; |
|||
case STRING: |
|||
result.addProperty(keyName, payload); |
|||
break; |
|||
default: |
|||
//TODO: change the exception type
|
|||
throw new AdaptorException("Unsupported data type"); |
|||
} |
|||
return new JsonParser().parse(result.toString()); |
|||
} |
|||
|
|||
private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { |
|||
snmpTransportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() |
|||
.setAttributeSubscription(false) |
|||
.setRpcSubscription(false) |
|||
.setLastActivityTime(System.currentTimeMillis()) |
|||
.build(), TransportServiceCallback.EMPTY); |
|||
} |
|||
|
|||
@AllArgsConstructor |
|||
private static class DeviceAuthCallback implements TransportServiceCallback<ValidateDeviceCredentialsResponse> { |
|||
private final TransportContext transportContext; |
|||
private final Consumer<TransportProtos.SessionInfoProto> onSuccess; |
|||
|
|||
@Override |
|||
public void onSuccess(ValidateDeviceCredentialsResponse msg) { |
|||
if (msg.hasDeviceInfo()) { |
|||
onSuccess.accept(SessionInfoCreator.create(msg, transportContext, UUID.randomUUID())); |
|||
} else { |
|||
log.warn("Failed to process device auth"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
log.warn("Failed to process device auth", e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
# |
|||
# Copyright © 2016-2021 The Thingsboard Authors |
|||
# |
|||
# Licensed 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. |
|||
# |
|||
|
|||
FROM thingsboard/openjdk8 |
|||
|
|||
COPY start-tb-snmp-transport.sh ${pkg.name}.deb /tmp/ |
|||
|
|||
RUN chmod a+x /tmp/*.sh \ |
|||
&& mv /tmp/start-tb-snmp-transport.sh /usr/bin |
|||
|
|||
RUN yes | dpkg -i /tmp/${pkg.name}.deb |
|||
RUN rm /tmp/${pkg.name}.deb |
|||
|
|||
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : |
|||
|
|||
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar |
|||
|
|||
USER ${pkg.user} |
|||
|
|||
CMD ["start-tb-snmp-transport.sh"] |
|||
@ -0,0 +1,33 @@ |
|||
#!/bin/bash |
|||
# |
|||
# Copyright © 2016-2021 The Thingsboard Authors |
|||
# |
|||
# Licensed 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. |
|||
# |
|||
|
|||
CONF_FOLDER="/config" |
|||
jarfile=${pkg.installFolder}/bin/${pkg.name}.jar |
|||
configfile=${pkg.name}.conf |
|||
|
|||
source "${CONF_FOLDER}/${configfile}" |
|||
|
|||
export LOADER_PATH=/config,${LOADER_PATH} |
|||
|
|||
echo "Starting '${project.name}' ..." |
|||
|
|||
cd ${pkg.installFolder}/bin |
|||
|
|||
exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.snmp.ThingsboardSnmpTransportApplication \ |
|||
-Dspring.jpa.hibernate.ddl-auto=none \ |
|||
-Dlogging.config=/config/logback.xml \ |
|||
org.springframework.boot.loader.PropertiesLauncher |
|||
@ -0,0 +1,190 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 The Thingsboard Authors |
|||
|
|||
Licensed 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. |
|||
|
|||
--> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
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>org.thingsboard.msa</groupId> |
|||
<artifactId>transport</artifactId> |
|||
<version>3.2.1-SNAPSHOT</version> |
|||
</parent> |
|||
|
|||
<groupId>org.thingsboard.msa.transport</groupId> |
|||
<artifactId>snmp</artifactId> |
|||
<packaging>pom</packaging> |
|||
|
|||
<name>ThingsBoard SNMP Transport Microservice</name> |
|||
<url>https://thingsboard.io</url> |
|||
<description>ThingsBoard SNMP Transport Microservice</description> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<main.dir>${basedir}/../../..</main.dir> |
|||
<pkg.name>tb-snmp-transport</pkg.name> |
|||
<docker.name>tb-snmp-transport</docker.name> |
|||
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder> |
|||
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.thingsboard.transport</groupId> |
|||
<artifactId>snmp</artifactId> |
|||
<version>${project.version}</version> |
|||
<classifier>deb</classifier> |
|||
<type>deb</type> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-dependency-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>copy-tb-snmp-transport-deb</id> |
|||
<phase>package</phase> |
|||
<goals> |
|||
<goal>copy</goal> |
|||
</goals> |
|||
<configuration> |
|||
<artifactItems> |
|||
<artifactItem> |
|||
<groupId>org.thingsboard.transport</groupId> |
|||
<artifactId>snmp</artifactId> |
|||
<classifier>deb</classifier> |
|||
<type>deb</type> |
|||
<destFileName>${pkg.name}.deb</destFileName> |
|||
<outputDirectory>${project.build.directory}</outputDirectory> |
|||
</artifactItem> |
|||
</artifactItems> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-resources-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>copy-docker-config</id> |
|||
<phase>process-resources</phase> |
|||
<goals> |
|||
<goal>copy-resources</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}</outputDirectory> |
|||
<resources> |
|||
<resource> |
|||
<directory>docker</directory> |
|||
<filtering>true</filtering> |
|||
</resource> |
|||
</resources> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>com.spotify</groupId> |
|||
<artifactId>dockerfile-maven-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>build-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>build</goal> |
|||
</goals> |
|||
<configuration> |
|||
<skip>${dockerfile.skip}</skip> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
<verbose>true</verbose> |
|||
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled> |
|||
<contextDirectory>${project.build.directory}</contextDirectory> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>tag-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>tag</goal> |
|||
</goals> |
|||
<configuration> |
|||
<skip>${dockerfile.skip}</skip> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
<tag>${project.version}</tag> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
<profiles> |
|||
<profile> |
|||
<id>push-docker-image</id> |
|||
<activation> |
|||
<property> |
|||
<name>push-docker-image</name> |
|||
</property> |
|||
</activation> |
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>com.spotify</groupId> |
|||
<artifactId>dockerfile-maven-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>push-latest-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>push</goal> |
|||
</goals> |
|||
<configuration> |
|||
<tag>latest</tag> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>push-version-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>push</goal> |
|||
</goals> |
|||
<configuration> |
|||
<tag>${project.version}</tag> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</profile> |
|||
</profiles> |
|||
<repositories> |
|||
<repository> |
|||
<id>jenkins</id> |
|||
<name>Jenkins Repository</name> |
|||
<url>https://repo.jenkins-ci.org/releases</url> |
|||
<snapshots> |
|||
<enabled>false</enabled> |
|||
</snapshots> |
|||
</repository> |
|||
</repositories> |
|||
</project> |
|||
@ -0,0 +1,112 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 The Thingsboard Authors |
|||
|
|||
Licensed 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. |
|||
|
|||
--> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
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>org.thingsboard</groupId> |
|||
<version>3.2.1-SNAPSHOT</version> |
|||
<artifactId>transport</artifactId> |
|||
</parent> |
|||
|
|||
<groupId>org.thingsboard.transport</groupId> |
|||
<artifactId>snmp</artifactId> |
|||
<packaging>jar</packaging> |
|||
|
|||
<name>Thingsboard SNMP Transport Service</name> |
|||
<url>https://thingsboard.io</url> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<main.dir>${basedir}/../..</main.dir> |
|||
<pkg.type>java</pkg.type> |
|||
<pkg.disabled>false</pkg.disabled> |
|||
<pkg.process-resources.phase>process-resources</pkg.process-resources.phase> |
|||
<pkg.package.phase>package</pkg.package.phase> |
|||
<pkg.name>tb-snmp-transport</pkg.name> |
|||
<pkg.copyInstallScripts>false</pkg.copyInstallScripts> |
|||
<pkg.win.dist>${project.build.directory}/windows</pkg.win.dist> |
|||
<pkg.implementationTitle>ThingsBoard SNMP Transport Service</pkg.implementationTitle> |
|||
<pkg.mainClass>org.thingsboard.server.snmp.ThingsboardSnmpTransportApplication</pkg.mainClass> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.thingsboard.common.transport</groupId> |
|||
<artifactId>snmp</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.thingsboard.common</groupId> |
|||
<artifactId>queue</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-web</artifactId> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<finalName>${pkg.name}-${project.version}</finalName> |
|||
<resources> |
|||
<resource> |
|||
<directory>${project.basedir}/src/main/resources</directory> |
|||
</resource> |
|||
</resources> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-resources-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-dependency-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-jar-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-maven-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.thingsboard</groupId> |
|||
<artifactId>gradle-maven-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-assembly-plugin</artifactId> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-install-plugin</artifactId> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
<repositories> |
|||
<repository> |
|||
<id>jenkins</id> |
|||
<name>Jenkins Repository</name> |
|||
<url>https://repo.jenkins-ci.org/releases</url> |
|||
<snapshots> |
|||
<enabled>false</enabled> |
|||
</snapshots> |
|||
</repository> |
|||
</repositories> |
|||
</project> |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.snmp; |
|||
|
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.boot.SpringBootConfiguration; |
|||
import org.springframework.context.annotation.ComponentScan; |
|||
import org.springframework.scheduling.annotation.EnableAsync; |
|||
import org.springframework.scheduling.annotation.EnableScheduling; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
@SpringBootConfiguration |
|||
@EnableAsync |
|||
@EnableScheduling |
|||
@ComponentScan({"org.thingsboard.server.snmp", "org.thingsboard.server.common", "org.thingsboard.server.transport.snmp", "org.thingsboard.server.queue"}) |
|||
public class ThingsboardSnmpTransportApplication { |
|||
|
|||
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; |
|||
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-snmp-transport"; |
|||
|
|||
public static void main(String[] args) { |
|||
SpringApplication.run(ThingsboardSnmpTransportApplication.class, updateArguments(args)); |
|||
} |
|||
|
|||
private static String[] updateArguments(String[] args) { |
|||
if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { |
|||
String[] modifiedArgs = new String[args.length + 1]; |
|||
System.arraycopy(args, 0, modifiedArgs, 0, args.length); |
|||
modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; |
|||
return modifiedArgs; |
|||
} |
|||
return args; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"deviceName": "Thermostat T1", |
|||
"snmpConfig": { |
|||
"address": "192.168.1.2", |
|||
"port": 161, |
|||
"community": "U5J=$HWj6f@7", |
|||
"protocolVersion": "v2c" |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
{ |
|||
"poolPeriodMs": 10000, |
|||
"timeoutMs": 5000, |
|||
"retries": 5, |
|||
"attributes": [ |
|||
{ |
|||
"key": "snmpNodeManagerEmail", |
|||
"type": "STRING", |
|||
"method": "get", |
|||
"oid": ".1.3.6.1.2.1.1.4.0" |
|||
} |
|||
], |
|||
"telemetry": [ |
|||
{ |
|||
"key": "snmpNodeSysUpTime", |
|||
"type": "LONG", |
|||
"method": "get", |
|||
"oid": ".1.3.6.1.2.1.1.3.0" |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 The Thingsboard Authors |
|||
|
|||
Licensed 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. |
|||
|
|||
--> |
|||
<form [formGroup]="snmpDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;"> |
|||
<tb-json-object-edit |
|||
required |
|||
formControlName="configuration"> |
|||
</tb-json-object-edit> |
|||
</form> |
|||
@ -0,0 +1,100 @@ |
|||
///
|
|||
/// Copyright © 2016-2021 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed 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.
|
|||
///
|
|||
|
|||
import {Component, forwardRef, Input, OnInit} from '@angular/core'; |
|||
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; |
|||
import {Store} from '@ngrx/store'; |
|||
import {AppState} from '@app/core/core.state'; |
|||
import {coerceBooleanProperty} from '@angular/cdk/coercion'; |
|||
import { |
|||
DeviceProfileTransportConfiguration, |
|||
DeviceTransportType, |
|||
SnmpDeviceProfileTransportConfiguration |
|||
} from '@shared/models/device.models'; |
|||
import {isDefinedAndNotNull} from "@core/utils"; |
|||
|
|||
export interface OidMappingConfiguration { |
|||
isAttribute: boolean; |
|||
key: string; |
|||
type: string; |
|||
method: string; |
|||
oid: string; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-snmp-device-profile-transport-configuration', |
|||
templateUrl: './snmp-device-profile-transport-configuration.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { |
|||
snmpDeviceProfileTransportConfigurationFormGroup: FormGroup; |
|||
private requiredValue: boolean; |
|||
private configuration = []; |
|||
|
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
|
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
private propagateChange = (v: any) => { |
|||
} |
|||
|
|||
constructor(private store: Store<AppState>, private fb: FormBuilder) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required] |
|||
}); |
|||
this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void { |
|||
if (isDefinedAndNotNull(value)) { |
|||
this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
private updateModel() { |
|||
let configuration: DeviceProfileTransportConfiguration = null; |
|||
if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) { |
|||
configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; |
|||
configuration.type = DeviceTransportType.SNMP; |
|||
} |
|||
this.propagateChange(configuration); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2021 The Thingsboard Authors |
|||
|
|||
Licensed 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. |
|||
|
|||
--> |
|||
<form [formGroup]="snmpDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;"> |
|||
<tb-json-object-edit |
|||
[required]="required" |
|||
label="{{ 'device-profile.transport-type-snmp-hint' | translate }}" |
|||
formControlName="configuration"> |
|||
</tb-json-object-edit> |
|||
</form> |
|||
@ -0,0 +1,100 @@ |
|||
///
|
|||
/// Copyright © 2016-2021 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed 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.
|
|||
///
|
|||
|
|||
import {Component, forwardRef, Input, OnInit} from '@angular/core'; |
|||
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; |
|||
import {Store} from '@ngrx/store'; |
|||
import {AppState} from '@app/core/core.state'; |
|||
import {coerceBooleanProperty} from '@angular/cdk/coercion'; |
|||
import { |
|||
DeviceTransportConfiguration, |
|||
DeviceTransportType, |
|||
SnmpDeviceTransportConfiguration |
|||
} from '@shared/models/device.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-snmp-device-transport-configuration', |
|||
templateUrl: './snmp-device-transport-configuration.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class SnmpDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
snmpDeviceTransportConfigurationFormGroup: FormGroup; |
|||
|
|||
private requiredValue: boolean; |
|||
|
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
|
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
private propagateChange = (v: any) => { |
|||
}; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private fb: FormBuilder) { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.snmpDeviceTransportConfigurationFormGroup = this.fb.group({ |
|||
configuration: [null, Validators.required] |
|||
}); |
|||
this.snmpDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.snmpDeviceTransportConfigurationFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.snmpDeviceTransportConfigurationFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: SnmpDeviceTransportConfiguration | null): void { |
|||
this.snmpDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); |
|||
} |
|||
|
|||
private updateModel() { |
|||
let configuration: DeviceTransportConfiguration = null; |
|||
if (this.snmpDeviceTransportConfigurationFormGroup.valid) { |
|||
configuration = this.snmpDeviceTransportConfigurationFormGroup.getRawValue().configuration; |
|||
configuration.type = DeviceTransportType.SNMP; |
|||
} |
|||
this.propagateChange(configuration); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue