Merge branch 'master' of github.com:thingsboard/thingsboard

This commit is contained in:
Andrii Shvaika 2022-01-25 12:26:53 +02:00
commit 1e41be45ba
18 changed files with 132 additions and 24 deletions

View File

@ -21,6 +21,7 @@ import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.coap.client.TbCoapClientState;
@ -52,6 +53,11 @@ public abstract class AbstractSyncSessionCallback implements SessionMsgListener
}
@Override
public void onDeviceDeleted(DeviceId deviceId) {
}
@Override
public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest) {
logUnsupportedCommandMessage(toDeviceRequest);

View File

@ -603,6 +603,13 @@ public class DeviceApiController implements TbTransportService {
responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
}
@Override
public void onDeviceDeleted(DeviceId deviceId) {
UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
log.trace("[{}] Received device deleted notification for device with id: {}",sessionId, deviceId);
responseWriter.setResult(new ResponseEntity<>("Device was deleted!", HttpStatus.FORBIDDEN));
}
}
private static MediaType parseMediaType(String contentType) {

View File

@ -71,7 +71,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private LeshanServer server;
@AfterStartUp
@AfterStartUp(order = Integer.MAX_VALUE - 1)
public void init() {
this.server = getLhServer();
/*
@ -83,8 +83,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
*/
LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RESOURCE);
this.server.coap().getServer().add(otaCoapResource);
this.startLhServer();
this.context.setServer(server);
this.startLhServer();
}
private void startLhServer() {

View File

@ -20,6 +20,7 @@ import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.link.LinkParamValue;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
@ -108,7 +109,7 @@ public class LwM2mClient implements Serializable {
@Getter
private Long edrxCycle;
@Getter
private Registration registration;
private transient Registration registration;
@Getter
@Setter
private boolean asleep;
@ -121,9 +122,9 @@ public class LwM2mClient implements Serializable {
private boolean firstEdrxDownlink = true;
@Getter
private Set<ContentFormat> clientSupportContentFormats;
private transient Set<ContentFormat> clientSupportContentFormats;
@Getter
private ContentFormat defaultContentFormat;
private transient ContentFormat defaultContentFormat;
@Getter
private final AtomicInteger retryAttempts;
@ -430,9 +431,9 @@ public class LwM2mClient implements Serializable {
static private Set<ContentFormat> clientSupportContentFormat(Registration registration) {
Set<ContentFormat> contentFormats = new HashSet<>();
contentFormats.add(ContentFormat.DEFAULT);
String code = Arrays.stream(registration.getObjectLinks()).filter(link -> link.getUriReference().equals("/")).findFirst().get().getLinkParams().get("ct").getUnquoted();
if (code != null) {
Set<ContentFormat> codes = Stream.of(code.replaceAll("\"", "").split(" ", -1))
LinkParamValue ct = Arrays.stream(registration.getObjectLinks()).filter(link -> link.getUriReference().equals("/")).findFirst().get().getLinkParams().get("ct");
if (ct != null) {
Set<ContentFormat> codes = Stream.of(ct.getUnquoted().replaceAll("\"", "").split(" ", -1))
.map(String::trim)
.map(Integer::parseInt)
.map(ContentFormat::fromCode)

View File

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -72,6 +73,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
private final LwM2MSessionManager sessionManager;
private final TransportDeviceProfileCache deviceProfileCache;
private final LwM2MModelConfigService modelConfigService;
private final RegistrationStore registrationStore;
@Autowired
@Lazy
@ -118,8 +120,11 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
private void updateFetchedClient(String nodeId, LwM2mClient client) {
boolean updated = false;
if (client.getRegistration() != null) {
lwM2mClientsByRegistrationId.put(client.getRegistration().getId(), client);
Registration registration = registrationStore.getRegistrationByEndpoint(client.getEndpoint());
if (registration != null) {
client.setRegistration(registration);
lwM2mClientsByRegistrationId.put(registration.getId(), client);
}
if (client.getSession() != null) {
client.refreshSessionId(nodeId);

View File

@ -111,7 +111,11 @@ public abstract class RpcDownlinkRequestCallbackProxy<R, T> implements DownlinkR
}
protected void sendRpcReplyOnError(Exception e) {
reply(LwM2MRpcResponseBody.builder().result(ResponseCode.INTERNAL_SERVER_ERROR.getName()).error(e.getMessage()).build());
String error = e.getMessage();
if (error == null) {
error = e.toString();
}
reply(LwM2MRpcResponseBody.builder().result(ResponseCode.INTERNAL_SERVER_ERROR.getName()).error(error).build());
}
}

View File

@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.rpc.RpcStatus;
@ -1066,4 +1067,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt);
}
@Override
public void onDeviceDeleted(DeviceId deviceId) {
context.onAuthFailure(address);
ChannelHandlerContext ctx = deviceSessionCtx.getChannel();
ctx.close();
}
}

View File

@ -19,6 +19,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.mqtt.MqttMessage;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService;
@ -136,6 +137,11 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
// This feature is not supported in the TB IoT Gateway yet.
}
@Override
public void onDeviceDeleted(DeviceId deviceId) {
parent.onDeviceDeleted(this.getSessionInfo().getDeviceName());
}
private boolean isAckExpected(MqttMessage message) {
return message.fixedHeader().qosLevel().value() > 0;
}

View File

@ -178,6 +178,10 @@ public class GatewaySessionHandler {
devices.forEach(this::deregisterSession);
}
public void onDeviceDeleted(String deviceName) {
deregisterSession(deviceName);
}
public String getNodeId() {
return context.getNodeId();
}

View File

@ -45,6 +45,8 @@ public interface SessionMsgListener {
void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse);
void onDeviceDeleted(DeviceId deviceId);
default void onUplinkNotification(UplinkNotificationMsg notificationMsg){};
default void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto toTransportUpdateCredentials){}
@ -54,8 +56,6 @@ public interface SessionMsgListener {
default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device,
Optional<DeviceProfile> deviceProfileOpt) {}
default void onDeviceDeleted(DeviceId deviceId) {}
default void onResourceUpdate(TransportProtos.ResourceUpdateMsg resourceUpdateMsgOpt) {}
default void onResourceDelete(TransportProtos.ResourceDeleteMsg resourceUpdateMsgOpt) {}

View File

@ -91,6 +91,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.provider.TbTransportQueueFactory;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbTransportComponent;
import javax.annotation.PostConstruct;
@ -227,6 +228,10 @@ public class DefaultTransportService implements TransportService {
transportNotificationsConsumer.subscribe(Collections.singleton(tpi));
transportApiRequestTemplate.init();
mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
}
@AfterStartUp
private void start() {
mainConsumerExecutor.execute(() -> {
while (!stopped) {
try {

View File

@ -339,6 +339,21 @@ public class MqttClientTest extends AbstractContainerTest {
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
}
@Test
public void deviceDeletedClosingSession() throws Exception {
restClient.login("tenant@thingsboard.org", "tenant");
String deviceForDeletingTestName = "Device for deleting notification test";
Device device = createDevice(deviceForDeletingTestName);
DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get();
MqttMessageListener listener = new MqttMessageListener();
MqttClient mqttClient = getMqttClient(deviceCredentials, listener);
restClient.deleteDevice(device.getId());
TimeUnit.SECONDS.sleep(3);
Assert.assertFalse(mqttClient.isConnected());
}
private RuleChainId createRootRuleChainForRpcResponse() throws Exception {
RuleChain newRuleChain = new RuleChain();
newRuleChain.setName("testRuleChain");

View File

@ -374,7 +374,15 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
Assert.assertEquals(clientResponse.toString(), serverResponse.getBody());
}
private void checkAttribute(boolean client, String expectedValue) throws Exception{
@Test
public void deviceCreationAfterDeleted() throws Exception {
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.createdDevice.getId());
Optional<Device> deletedDevice = restClient.getDeviceById(this.createdDevice.getId());
Assert.assertTrue(deletedDevice.isEmpty());
this.createdDevice = createDeviceThroughGateway(mqttClient, gatewayDevice);
}
private void checkAttribute(boolean client, String expectedValue) throws Exception {
JsonObject gatewayAttributesRequest = new JsonObject();
int messageId = new Random().nextInt(100);
gatewayAttributesRequest.addProperty("id", messageId);

View File

@ -134,6 +134,7 @@ import {
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import cssjs from '@core/css/css';
import { DOCUMENT } from '@angular/common';
import { IAliasController } from '@core/api/widget-api.models';
// @dynamic
@Component({
@ -179,6 +180,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
@Input()
parentDashboard?: IDashboardComponent = null;
@Input()
parentAliasController?: IAliasController = null;
@ViewChild('dashboardContainer') dashboardContainer: ElementRef<HTMLElement>;
prevDashboard: Dashboard;
@ -419,7 +423,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.readonly = this.embedded || (this.singlePageMode && !this.widgetEditMode && !this.route.snapshot.queryParamMap.get('edit'))
|| this.forceFullscreen || this.isMobileApp || this.authUser.authority === Authority.CUSTOMER_USER;
this.dashboardCtx.aliasController = new AliasController(this.utils,
this.dashboardCtx.aliasController = this.parentAliasController ? this.parentAliasController : new AliasController(this.utils,
this.entityService,
this.translate,
() => this.dashboardCtx.stateController,

View File

@ -21,7 +21,8 @@
[hideToolbar]="true"
[currentState]="currentState"
[dashboard]="dashboard"
[parentDashboard]="parentDashboard">
[parentDashboard]="parentDashboard"
[parentAliasController]="parentAliasController">
</tb-dashboard-page>
<div class="tb-absolute-fill tb-widget-error" *ngIf="!stateExists">
<span>{{ 'dashboard.non-existent-dashboard-state-error' | translate:{stateId} }}</span>

View File

@ -19,7 +19,7 @@ import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models';
import { StateObject } from '@core/api/widget-api.models';
import { IAliasController, StateObject } from '@core/api/widget-api.models';
import { updateEntityParams, WidgetContext } from '@home/models/widget-component.models';
import { deepClone, isDefinedAndNotNull, isNotEmptyStr, objToBase64 } from '@core/utils';
import { IDashboardComponent } from '@home/models/dashboard-component.models';
@ -63,6 +63,8 @@ export class DashboardStateComponent extends PageComponent implements OnInit, On
parentDashboard: IDashboardComponent;
parentAliasController: IAliasController;
stateExists = true;
private stateSubscription: Subscription;
@ -92,6 +94,7 @@ export class DashboardStateComponent extends PageComponent implements OnInit, On
this.parentDashboard = this.ctx.parentDashboard ?
this.ctx.parentDashboard : this.ctx.dashboard;
if (this.syncParentStateParams) {
this.parentAliasController = this.parentDashboard.aliasController;
this.stateSubscription = this.ctx.stateController.dashboardCtrl.dashboardCtx.stateChanged.subscribe(() => {
this.updateCurrentState();
this.cd.markForCheck();

View File

@ -41,6 +41,22 @@ export function createTooltip(target: L.Layer,
return popup;
}
export function disablePopup(target: L.Layer) {
if (target.isPopupOpen()) {
target.closePopup();
}
target.unbindPopup();
target.off('popupopen');
}
export function enablePopup(target: L.Layer, popup: L.Popup,
settings: MarkerSettings | PolylineSettings | PolygonSettings, datasource: Datasource) {
target.bindPopup(popup);
target.on('popupopen', () => {
bindPopupActions(popup, settings, datasource);
});
}
export function bindPopupActions(popup: L.Popup, settings: MarkerSettings | PolylineSettings | PolygonSettings,
datasource: Datasource) {
const actions = popup.getElement().getElementsByClassName('tb-custom-action');

View File

@ -23,7 +23,7 @@ import {
MarkerSettings,
UnitedMapSettings
} from './map-models';
import { bindPopupActions, createTooltip, } from './maps-utils';
import { bindPopupActions, createTooltip, disablePopup, enablePopup, } from './maps-utils';
import { aspectCache, fillPattern, parseWithTranslation, processPattern, safeExecute } from './common-maps-utils';
import tinycolor from 'tinycolor2';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
@ -37,6 +37,7 @@ export class Marker {
tooltip: L.Popup;
data: FormattedData;
dataSources: FormattedData[];
isDragging = false;
constructor(private map: LeafletMap, private location: L.LatLng, public settings: UnitedMapSettings,
data?: FormattedData, dataSources?, onDragendListener?) {
@ -64,17 +65,31 @@ export class Marker {
}
if (this.settings.markerClick) {
this.leafletMarker.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.markerClick) {
if (!this.isDragging) {
this.leafletMarker.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.markerClick) {
if (typeof (this.settings.markerClick[action]) === 'function') {
this.settings.markerClick[action](event.originalEvent, this.data.$datasource);
this.settings.markerClick[action](event.originalEvent, this.data.$datasource);
}
}
});
}
});
}
}
if (settings.draggableMarker && onDragendListener) {
this.leafletMarker.on('pm:dragend', (e) => onDragendListener(e, this.data));
this.leafletMarker.on('pm:dragend', (e) => {
onDragendListener(e, this.data);
this.isDragging = false;
if (settings.showTooltip && settings.showTooltipAction === 'click') {
enablePopup(this.leafletMarker, this.tooltip, settings, this.data.$datasource);
}
});
this.leafletMarker.on('pm:dragstart', (e) => {
this.isDragging = true;
if (settings.showTooltip && settings.showTooltipAction === 'click') {
disablePopup(this.leafletMarker);
}
});
}
}