/**
* Copyright © 2017 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.gateway.extensions.sigfox;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.gateway.extensions.sigfox.conf.SigfoxConfiguration;
import org.thingsboard.gateway.extensions.sigfox.conf.SigfoxDeviceTypeConfiguration;
import org.thingsboard.gateway.extensions.sigfox.conf.mapping.SigfoxDeviceDataConverter;
import org.thingsboard.gateway.service.GatewayService;
import org.thingsboard.gateway.service.MqttDeliveryFuture;
import org.thingsboard.gateway.service.data.DeviceData;
import org.thingsboard.gateway.util.ConfigurationTools;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@Slf4j
@ConditionalOnProperty(prefix = "sigfox", value = "enabled", havingValue = "true")
public class DefaultSigfoxService implements SigfoxService {
private static final int OPERATION_TIMEOUT_IN_SEC = 10;
@Value("${sigfox.configuration}")
private String configurationFile;
@Autowired
private GatewayService gateway;
private Map<String, SigfoxDeviceTypeConfiguration> sigfoxDeviceTypeConfigurations;
@PostConstruct
public void init() throws IOException {
SigfoxConfiguration configuration;
try {
configuration = ConfigurationTools.readConfiguration(configurationFile, SigfoxConfiguration.class);
sigfoxDeviceTypeConfigurations = configuration
.getDeviceTypeConfigurations()
.stream()
.collect(Collectors.toMap(SigfoxDeviceTypeConfiguration::getDeviceTypeId, Function.identity()));
} catch (IOException e) {
log.error("Sigfox service configuration failed!", e);
throw e;
}
}
@Override
public void processRequest(String deviceTypeId, String token, String body) throws Exception {
log.trace("Processing request body [{}] for deviceTypeId [{}] and token [{}]", body, deviceTypeId, token);
SigfoxDeviceTypeConfiguration configuration = sigfoxDeviceTypeConfigurations.get(deviceTypeId);
if (configuration != null) {
if (configuration.getToken().equals(token)) {
processBody(body, configuration);
} else {
log.error("Request token [{}] for device type id [{}] doesn't match configuration token!", token, deviceTypeId);
throw new SecurityException("Request token [" + token + "] for device type id [" + deviceTypeId + "] doesn't match configuration token!");
}
} else {
log.error("No configuration found for device type id [{}]. Please add configuration for this device type id!", deviceTypeId);
throw new IllegalArgumentException("No configuration found for device type id [" + deviceTypeId + "]. Please add configuration for this device type id!");
}
}
private void processBody(String body, SigfoxDeviceTypeConfiguration configuration) throws Exception {
for (SigfoxDeviceDataConverter converter : configuration.getConverters()) {
if (converter.isApplicable(body)) {
DeviceData dd = converter.parseBody(body);
if (dd != null) {
waitWithTimeout(gateway.onDeviceConnect(dd.getName()));
List<MqttDeliveryFuture> futures = new ArrayList<>();
if (!dd.getAttributes().isEmpty()) {
futures.add(gateway.onDeviceAttributesUpdate(dd.getName(), dd.getAttributes()));
}
if (!dd.getTelemetry().isEmpty()) {
futures.add(gateway.onDeviceTelemetry(dd.getName(), dd.getTelemetry()));
}
for (Future future : futures) {
waitWithTimeout(future);
}
Optional<MqttDeliveryFuture> future = gateway.onDeviceDisconnect(dd.getName());
if (future.isPresent()) {
waitWithTimeout(future.get());
}
} else {
log.error("DeviceData is null. Body [{}] was not parsed successfully!", body);
throw new IllegalArgumentException("Device Data is null. Body [" + body + "] was not parsed successfully!");
}
}
}
}
private void waitWithTimeout(Future future) throws Exception {
future.get(OPERATION_TIMEOUT_IN_SEC, TimeUnit.SECONDS);
}
}