]> git.basschouten.com Git - openhab-addons.git/blob
f43ea54636e57c4dcd32148d9f030ea6dd582013
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.snmp.internal;
14
15 import static org.openhab.binding.snmp.internal.SnmpBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.Collections;
21 import java.util.Objects;
22 import java.util.Set;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 import java.util.stream.Collectors;
28
29 import javax.measure.Unit;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.snmp.internal.config.SnmpChannelConfiguration;
34 import org.openhab.binding.snmp.internal.config.SnmpInternalChannelConfiguration;
35 import org.openhab.binding.snmp.internal.config.SnmpTargetConfiguration;
36 import org.openhab.binding.snmp.internal.types.SnmpChannelMode;
37 import org.openhab.binding.snmp.internal.types.SnmpDatatype;
38 import org.openhab.binding.snmp.internal.types.SnmpProtocolVersion;
39 import org.openhab.binding.snmp.internal.types.SnmpSecurityModel;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.library.types.QuantityType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.util.ThingHandlerHelper;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.openhab.core.types.State;
54 import org.openhab.core.types.UnDefType;
55 import org.openhab.core.types.util.UnitUtils;
56 import org.openhab.core.util.HexUtils;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.snmp4j.AbstractTarget;
60 import org.snmp4j.CommandResponder;
61 import org.snmp4j.CommandResponderEvent;
62 import org.snmp4j.CommunityTarget;
63 import org.snmp4j.PDU;
64 import org.snmp4j.PDUv1;
65 import org.snmp4j.ScopedPDU;
66 import org.snmp4j.Snmp;
67 import org.snmp4j.UserTarget;
68 import org.snmp4j.event.ResponseEvent;
69 import org.snmp4j.event.ResponseListener;
70 import org.snmp4j.mp.SnmpConstants;
71 import org.snmp4j.smi.Counter64;
72 import org.snmp4j.smi.Integer32;
73 import org.snmp4j.smi.IpAddress;
74 import org.snmp4j.smi.OID;
75 import org.snmp4j.smi.OctetString;
76 import org.snmp4j.smi.Opaque;
77 import org.snmp4j.smi.UdpAddress;
78 import org.snmp4j.smi.UnsignedInteger32;
79 import org.snmp4j.smi.Variable;
80 import org.snmp4j.smi.VariableBinding;
81
82 /**
83  * The {@link SnmpTargetHandler} is responsible for handling commands, which are
84  * sent to one of the channels or update remote channels
85  *
86  * @author Jan N. Klug - Initial contribution
87  */
88 @NonNullByDefault
89 public class SnmpTargetHandler extends BaseThingHandler implements ResponseListener, CommandResponder {
90     private static final Pattern HEXSTRING_VALIDITY = Pattern.compile("([a-f0-9]{2}[ :-]?)+");
91     private static final Pattern HEXSTRING_EXTRACTOR = Pattern.compile("[^a-f0-9]");
92
93     private final Logger logger = LoggerFactory.getLogger(SnmpTargetHandler.class);
94
95     private @NonNullByDefault({}) SnmpTargetConfiguration config;
96     private final SnmpService snmpService;
97     private @Nullable ScheduledFuture<?> refresh;
98     private int timeoutCounter = 0;
99
100     private @NonNullByDefault({}) AbstractTarget target;
101     private @NonNullByDefault({}) String targetAddressString;
102
103     private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> readChannelSet;
104     private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> writeChannelSet;
105     private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> trapChannelSet;
106
107     public SnmpTargetHandler(Thing thing, SnmpService snmpService) {
108         super(thing);
109         this.snmpService = snmpService;
110     }
111
112     @Override
113     public void handleCommand(ChannelUID channelUID, Command command) {
114         if (target.getAddress() == null && !renewTargetAddress()) {
115             logger.info("failed to renew target address, can't process '{}' to '{}'.", command, channelUID);
116             return;
117         }
118
119         try {
120             if (command instanceof RefreshType) {
121                 SnmpInternalChannelConfiguration channel = readChannelSet.stream()
122                         .filter(c -> channelUID.equals(c.channelUID)).findFirst()
123                         .orElseThrow(() -> new IllegalArgumentException("no readable channel found"));
124                 PDU pdu = getPDU();
125                 pdu.setType(PDU.GET);
126                 pdu.add(new VariableBinding(channel.oid));
127                 snmpService.send(pdu, target, null, this);
128             } else if (command instanceof DecimalType || command instanceof QuantityType
129                     || command instanceof StringType || command instanceof OnOffType) {
130                 SnmpInternalChannelConfiguration channel = writeChannelSet.stream()
131                         .filter(config -> channelUID.equals(config.channelUID)).findFirst()
132                         .orElseThrow(() -> new IllegalArgumentException("no writable channel found"));
133                 Variable variable;
134                 if (command instanceof OnOffType) {
135                     variable = OnOffType.ON.equals(command) ? channel.onValue : channel.offValue;
136                     if (variable == null) {
137                         logger.debug("skipping {} to {}: no value defined", command, channelUID);
138                         return;
139                     }
140                 } else {
141                     Command rawValue = command;
142                     if (command instanceof QuantityType quantityCommand) {
143                         Unit<?> channelUnit = channel.unit;
144                         if (channelUnit == null) {
145                             rawValue = new DecimalType(quantityCommand.toBigDecimal());
146                         } else {
147                             QuantityType<?> convertedValue = quantityCommand.toUnit(channelUnit);
148                             if (convertedValue == null) {
149                                 logger.warn("Cannot convert '{}' to configured unit '{}'", command, channelUnit);
150                                 return;
151                             }
152                             rawValue = new DecimalType(convertedValue.toBigDecimal());
153                         }
154                     }
155                     variable = convertDatatype(rawValue, channel.datatype);
156                 }
157                 PDU pdu = getPDU();
158                 pdu.setType(PDU.SET);
159                 pdu.add(new VariableBinding(channel.oid, variable));
160                 snmpService.send(pdu, target, null, this);
161             }
162         } catch (IllegalArgumentException e) {
163             logger.warn("can't process command {} to {}: {}", command, channelUID, e.getMessage());
164         } catch (IOException e) {
165             logger.warn("Could not send PDU while processing command {} to {}", command, channelUID);
166         }
167     }
168
169     @Override
170     public void initialize() {
171         config = getConfigAs(SnmpTargetConfiguration.class);
172
173         generateChannelConfigs();
174
175         if (thing.getThingTypeUID().equals(THING_TYPE_TARGET3)) {
176             // override default for target3 things
177             config.protocol = SnmpProtocolVersion.v3;
178         }
179
180         try {
181             if (config.protocol.toInteger() == SnmpConstants.version1
182                     || config.protocol.toInteger() == SnmpConstants.version2c) {
183                 CommunityTarget target = new CommunityTarget();
184                 target.setCommunity(new OctetString(config.community));
185                 this.target = target;
186             } else if (config.protocol.toInteger() == SnmpConstants.version3) {
187                 String userName = config.user;
188                 if (userName == null) {
189                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "user not set");
190                     return;
191                 }
192                 String engineIdHexString = config.engineId;
193                 if (engineIdHexString == null) {
194                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "engineId not set");
195                     return;
196                 }
197                 String authPassphrase = config.authPassphrase;
198                 if ((config.securityModel == SnmpSecurityModel.AUTH_PRIV
199                         || config.securityModel == SnmpSecurityModel.AUTH_NO_PRIV)
200                         && (authPassphrase == null || authPassphrase.isEmpty())) {
201                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
202                             "Authentication passphrase not configured");
203                     return;
204                 }
205                 String privPassphrase = config.privPassphrase;
206                 if (config.securityModel == SnmpSecurityModel.AUTH_PRIV
207                         && (privPassphrase == null || privPassphrase.isEmpty())) {
208                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
209                             "Privacy passphrase not configured");
210                     return;
211                 }
212                 byte[] engineId = HexUtils.hexToBytes(engineIdHexString);
213                 snmpService.addUser(userName, config.authProtocol, authPassphrase, config.privProtocol, privPassphrase,
214                         engineId);
215                 UserTarget target = new UserTarget();
216                 target.setAuthoritativeEngineID(engineId);
217                 target.setSecurityName(new OctetString(config.user));
218                 target.setSecurityLevel(config.securityModel.getSecurityLevel());
219                 this.target = target;
220             } else {
221                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SNMP version not supported");
222                 return;
223             }
224
225             snmpService.addCommandResponder(this);
226
227             target.setRetries(config.retries);
228             target.setTimeout(config.timeout);
229             target.setVersion(config.protocol.toInteger());
230             target.setAddress(null);
231
232             timeoutCounter = 0;
233         } catch (IllegalArgumentException e) {
234             // some methods of SNMP4J throw an unchecked IllegalArgumentException if they receive invalid values
235             String message = "Exception during initialization: " + e.getMessage();
236             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
237             return;
238         }
239
240         updateStatus(ThingStatus.UNKNOWN);
241         refresh = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.SECONDS);
242     }
243
244     @Override
245     public void dispose() {
246         final ScheduledFuture<?> r = refresh;
247         if (r != null && !r.isCancelled()) {
248             r.cancel(true);
249         }
250         snmpService.removeCommandResponder(this);
251     }
252
253     @Override
254     public void onResponse(@Nullable ResponseEvent event) {
255         if (event == null) {
256             return;
257         }
258
259         if (event.getSource() instanceof Snmp) {
260             // Always cancel async request when response has been received
261             // otherwise a memory leak is created! Not canceling a request
262             // immediately can be useful when sending a request to a broadcast
263             // address (Comment is taken from the snmp4j API doc).
264             ((Snmp) event.getSource()).cancel(event.getRequest(), this);
265         }
266
267         PDU response = event.getResponse();
268         if (response == null) {
269             Exception e = event.getError();
270             if (e == null) { // no response, no error -> request timed out
271                 timeoutCounter++;
272                 if (timeoutCounter > config.retries) {
273                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "request timed out");
274                     target.setAddress(null);
275                 }
276                 return;
277             }
278             logger.warn("{} requested {} and got error: {}", thing.getUID(), event.getRequest(), e.getMessage());
279             return;
280         }
281         timeoutCounter = 0;
282         if (ThingHandlerHelper.isHandlerInitialized(this)) {
283             updateStatus(ThingStatus.ONLINE);
284         }
285         logger.trace("{} received {}", thing.getUID(), response);
286
287         response.getVariableBindings().forEach(variable -> {
288             if (variable != null) {
289                 updateChannels(variable.getOid(), variable.getVariable(), readChannelSet);
290             }
291         });
292     }
293
294     @Override
295     public void processPdu(@Nullable CommandResponderEvent event) {
296         if (event == null) {
297             return;
298         }
299         logger.trace("{} received trap {}", thing.getUID(), event);
300
301         final PDU pdu = event.getPDU();
302         final String address = ((UdpAddress) event.getPeerAddress()).getInetAddress().getHostAddress();
303         final String community = new String(event.getSecurityName());
304
305         if ((pdu.getType() == PDU.V1TRAP) && config.community.equals(community) && (pdu instanceof PDUv1 pduv1)) {
306             logger.trace("{} received trap is PDUv1.", thing.getUID());
307             OID oidEnterprise = pduv1.getEnterprise();
308             int trapValue = pduv1.getGenericTrap();
309             if (trapValue == PDUv1.ENTERPRISE_SPECIFIC) {
310                 trapValue = pduv1.getSpecificTrap();
311             }
312             updateChannels(oidEnterprise, new UnsignedInteger32(trapValue), trapChannelSet);
313         }
314         if ((pdu.getType() == PDU.TRAP || pdu.getType() == PDU.V1TRAP) && config.community.equals(community)
315                 && targetAddressString.equals(address)) {
316             pdu.getVariableBindings().forEach(variable -> {
317                 if (variable != null) {
318                     updateChannels(variable.getOid(), variable.getVariable(), trapChannelSet);
319                 }
320             });
321         }
322     }
323
324     private @Nullable SnmpInternalChannelConfiguration getChannelConfigFromChannel(Channel channel) {
325         SnmpChannelConfiguration config = channel.getConfiguration().as(SnmpChannelConfiguration.class);
326
327         String oid = config.oid;
328         if (oid == null) {
329             logger.warn("oid must not be null");
330             return null;
331         }
332
333         SnmpDatatype datatype = config.datatype; // maybe null, override later
334         Variable onValue = null;
335         Variable offValue = null;
336         Unit<?> unit = null;
337         State exceptionValue = UnDefType.UNDEF;
338
339         if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
340             if (datatype == null) {
341                 datatype = SnmpDatatype.INT32;
342             } else if (datatype == SnmpDatatype.IPADDRESS || datatype == SnmpDatatype.STRING) {
343                 return null;
344             }
345             String configExceptionValue = config.exceptionValue;
346             if (configExceptionValue != null) {
347                 exceptionValue = DecimalType.valueOf(configExceptionValue);
348             }
349             String configUnit = config.unit;
350             if (configUnit != null) {
351                 unit = UnitUtils.parseUnit(configUnit);
352                 if (unit == null) {
353                     logger.warn("Failed to parse unit from '{}'for channel '{}'", unit, channel.getUID());
354                 }
355             }
356         } else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
357             if (datatype == null) {
358                 datatype = SnmpDatatype.STRING;
359             } else if (datatype != SnmpDatatype.IPADDRESS && datatype != SnmpDatatype.STRING
360                     && datatype != SnmpDatatype.HEXSTRING) {
361                 return null;
362             }
363             String configExceptionValue = config.exceptionValue;
364             if (configExceptionValue != null) {
365                 exceptionValue = StringType.valueOf(configExceptionValue);
366             }
367         } else if (CHANNEL_TYPE_UID_SWITCH.equals(channel.getChannelTypeUID())) {
368             if (datatype == null) {
369                 datatype = SnmpDatatype.UINT32;
370             }
371             try {
372                 final String configOnValue = config.onvalue;
373                 if (configOnValue != null) {
374                     onValue = convertDatatype(new StringType(configOnValue), datatype);
375                 }
376                 final String configOffValue = config.offvalue;
377                 if (configOffValue != null) {
378                     offValue = convertDatatype(new StringType(configOffValue), datatype);
379                 }
380             } catch (IllegalArgumentException e) {
381                 logger.warn("illegal value configuration for channel {}", channel.getUID());
382                 return null;
383             }
384             String configExceptionValue = config.exceptionValue;
385             if (configExceptionValue != null) {
386                 exceptionValue = OnOffType.from(configExceptionValue);
387             }
388         } else {
389             logger.warn("unknown channel type found for channel {}", channel.getUID());
390             return null;
391         }
392         return new SnmpInternalChannelConfiguration(channel.getUID(), new OID(oid), config.mode, datatype, onValue,
393                 offValue, exceptionValue, unit, config.doNotLogException);
394     }
395
396     private void generateChannelConfigs() {
397         Set<SnmpInternalChannelConfiguration> channelConfigs = Collections.unmodifiableSet(thing.getChannels().stream()
398                 .map(this::getChannelConfigFromChannel).filter(Objects::nonNull).collect(Collectors.toSet()));
399         this.readChannelSet = channelConfigs.stream()
400                 .filter(c -> c.mode == SnmpChannelMode.READ || c.mode == SnmpChannelMode.READ_WRITE)
401                 .collect(Collectors.toSet());
402         this.writeChannelSet = channelConfigs.stream()
403                 .filter(c -> c.mode == SnmpChannelMode.WRITE || c.mode == SnmpChannelMode.READ_WRITE)
404                 .collect(Collectors.toSet());
405         this.trapChannelSet = channelConfigs.stream().filter(c -> c.mode == SnmpChannelMode.TRAP)
406                 .collect(Collectors.toSet());
407     }
408
409     private void updateChannels(OID oid, Variable value, Set<SnmpInternalChannelConfiguration> channelConfigs) {
410         Set<SnmpInternalChannelConfiguration> updateChannelConfigs = channelConfigs.stream()
411                 .filter(c -> c.oid.equals(oid)).collect(Collectors.toSet());
412         if (!updateChannelConfigs.isEmpty()) {
413             updateChannelConfigs.forEach(channelConfig -> {
414                 ChannelUID channelUID = channelConfig.channelUID;
415                 final Channel channel = thing.getChannel(channelUID);
416                 State state;
417                 if (channel == null) {
418                     logger.warn("channel uid {} in channel config set but channel not found", channelUID);
419                     return;
420                 }
421                 if (value.isException()) {
422                     if (!channelConfig.doNotLogException) {
423                         logger.info("SNMP Exception: request {} returned '{}'", oid, value);
424                     }
425                     state = channelConfig.exceptionValue;
426                 } else if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
427                     try {
428                         if (channelConfig.datatype == SnmpDatatype.FLOAT) {
429                             if (value instanceof Opaque opaque) {
430                                 byte[] octets = opaque.toByteArray();
431                                 if (octets.length < 3) {
432                                     // two bytes identifier and one byte length should always be present
433                                     throw new UnsupportedOperationException("Not enough octets");
434                                 }
435                                 if (octets.length != (3 + octets[2])) {
436                                     // octet 3 contains the lengths of the value
437                                     throw new UnsupportedOperationException("Not enough octets");
438                                 }
439                                 if (octets[0] == (byte) 0x9f && octets[1] == 0x78 && octets[2] == 0x04) {
440                                     // floating point value
441                                     Unit<?> channelUnit = channelConfig.unit;
442                                     float floatValue = Float.intBitsToFloat(
443                                             octets[3] << 24 | octets[4] << 16 | octets[5] << 8 | octets[6]);
444                                     state = channelUnit == null ? new DecimalType(floatValue)
445                                             : new QuantityType<>(floatValue, channelUnit);
446                                 } else {
447                                     throw new UnsupportedOperationException("Unknown opaque datatype" + value);
448                                 }
449                             } else {
450                                 Unit<?> channelUnit = channelConfig.unit;
451                                 state = channelUnit == null ? new DecimalType(value.toString())
452                                         : new QuantityType<>(value + channelUnit.getSymbol());
453                             }
454                         } else {
455                             Unit<?> channelUnit = channelConfig.unit;
456                             state = channelUnit == null ? new DecimalType(value.toLong())
457                                     : new QuantityType<>(value.toLong(), channelUnit);
458                         }
459                     } catch (UnsupportedOperationException e) {
460                         logger.warn("could not convert {} to number for channel {}", value, channelUID);
461                         return;
462                     }
463                 } else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
464                     if (channelConfig.datatype == SnmpDatatype.HEXSTRING) {
465                         String rawString = ((OctetString) value).toHexString(' ');
466                         state = new StringType(rawString.toLowerCase());
467                     } else {
468                         state = new StringType(value.toString());
469                     }
470                 } else if (CHANNEL_TYPE_UID_SWITCH.equals(channel.getChannelTypeUID())) {
471                     if (value.equals(channelConfig.onValue)) {
472                         state = OnOffType.ON;
473                     } else if (value.equals(channelConfig.offValue)) {
474                         state = OnOffType.OFF;
475                     } else {
476                         logger.debug("channel {} received unmapped value {} ", channelUID, value);
477                         return;
478                     }
479                 } else {
480                     logger.warn("channel {} has unknown ChannelTypeUID", channelUID);
481                     return;
482                 }
483                 updateState(channelUID, state);
484             });
485         } else {
486             logger.debug("received value {} for unknown OID {}, skipping", value, oid);
487         }
488     }
489
490     private Variable convertDatatype(Command command, SnmpDatatype datatype) {
491         switch (datatype) {
492             case INT32 -> {
493                 if (command instanceof DecimalType decimalCommand) {
494                     return new Integer32(decimalCommand.intValue());
495                 } else if (command instanceof StringType stringCommand) {
496                     return new Integer32((new DecimalType(stringCommand.toString())).intValue());
497                 }
498             }
499             case UINT32 -> {
500                 if (command instanceof DecimalType decimalCommand) {
501                     return new UnsignedInteger32(decimalCommand.intValue());
502                 } else if (command instanceof StringType stringCommand) {
503                     return new UnsignedInteger32((new DecimalType(stringCommand.toString())).intValue());
504                 }
505             }
506             case COUNTER64 -> {
507                 if (command instanceof DecimalType decimalCommand) {
508                     return new Counter64(decimalCommand.longValue());
509                 } else if (command instanceof StringType stringCommand) {
510                     return new Counter64((new DecimalType(stringCommand.toString())).longValue());
511                 }
512             }
513             case FLOAT, STRING -> {
514                 if (command instanceof DecimalType decimalCommand) {
515                     return new OctetString(decimalCommand.toString());
516                 } else if (command instanceof StringType stringCommand) {
517                     return new OctetString(stringCommand.toString());
518                 }
519             }
520             case HEXSTRING -> {
521                 if (command instanceof StringType stringCommand) {
522                     String commandString = stringCommand.toString().toLowerCase();
523                     Matcher commandMatcher = HEXSTRING_VALIDITY.matcher(commandString);
524                     if (commandMatcher.matches()) {
525                         commandString = HEXSTRING_EXTRACTOR.matcher(commandString).replaceAll("");
526                         return OctetString.fromHexStringPairs(commandString);
527                     }
528                 }
529             }
530             case IPADDRESS -> {
531                 if (command instanceof StringType stringCommand) {
532                     return new IpAddress(stringCommand.toString());
533                 }
534             }
535             default -> {
536             }
537         }
538         throw new IllegalArgumentException("illegal conversion of " + command + " to " + datatype);
539     }
540
541     private boolean renewTargetAddress() {
542         try {
543             target.setAddress(new UdpAddress(InetAddress.getByName(config.hostname), config.port));
544             targetAddressString = ((UdpAddress) target.getAddress()).getInetAddress().getHostAddress();
545             return true;
546         } catch (UnknownHostException e) {
547             target.setAddress(null);
548             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Cannot resolve target host");
549             return false;
550         }
551     }
552
553     private void refresh() {
554         if (target.getAddress() == null) {
555             if (!renewTargetAddress()) {
556                 logger.info("failed to renew target address, waiting for next refresh cycle");
557                 return;
558             }
559         }
560         PDU pdu = getPDU();
561         pdu.setType(PDU.GET);
562         readChannelSet.stream().map(c -> new VariableBinding(c.oid)).forEach(pdu::add);
563         if (!pdu.getVariableBindings().isEmpty()) {
564             try {
565                 snmpService.send(pdu, target, null, this);
566             } catch (IOException e) {
567                 logger.info("Could not send PDU", e);
568             }
569         }
570     }
571
572     private PDU getPDU() {
573         if (config.protocol == SnmpProtocolVersion.v3 || config.protocol == SnmpProtocolVersion.V3) {
574             return new ScopedPDU();
575         } else {
576             return new PDU();
577         }
578     }
579 }