Browse Source

lwm2m: bootstrap new: add reboot device and reboot device bootstrap - 3

pull/14084/head
nickAS21 8 months ago
parent
commit
d02582b13b
  1. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  2. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java
  3. 70
      ui-ngx/src/app/core/http/device.service.ts
  4. 114
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -66,7 +66,9 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_TRIGGER_PARAMS_ID;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_OBJECT_VERSION_DEFAULT; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LWM2M_OBJECT_VERSION_DEFAULT;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.REGISTRATION_TRIGGER_PARAMS_ID;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.equalsResourceTypeGetSimpleName;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@ -342,6 +344,10 @@ public class LwM2mClient {
public String isValidObjectVersion(String path) { public String isValidObjectVersion(String path) {
LwM2mPath pathIds = getLwM2mPathFromString(path); LwM2mPath pathIds = getLwM2mPathFromString(path);
if (pathIds.isResource() && (pathIds.toString().equals(REGISTRATION_TRIGGER_PARAMS_ID ) ||
pathIds.toString().equals(BOOTSTRAP_TRIGGER_PARAMS_ID))) {
return "";
}
LwM2m.Version verSupportedObject = this.getSupportedObjectVersion(pathIds.getObjectId()); LwM2m.Version verSupportedObject = this.getSupportedObjectVersion(pathIds.getObjectId());
if (verSupportedObject == null) { if (verSupportedObject == null) {
return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId()); return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId());

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java

@ -81,6 +81,8 @@ public class LwM2MTransportUtil {
public static final String LOG_LWM2M_INFO = "info"; public static final String LOG_LWM2M_INFO = "info";
public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_ERROR = "error";
public static final String LOG_LWM2M_WARN = "warn"; public static final String LOG_LWM2M_WARN = "warn";
public static final String REGISTRATION_TRIGGER_PARAMS_ID = "/1/0/8";
public static final String BOOTSTRAP_TRIGGER_PARAMS_ID = "/1/0/9";;
public static LwM2mOtaConvert convertOtaUpdateValueToString(String pathIdVer, Object value, ResourceModel.Type currentType) { public static LwM2mOtaConvert convertOtaUpdateValueToString(String pathIdVer, Object value, ResourceModel.Type currentType) {
String path = fromVersionedIdToObjectId(pathIdVer); String path = fromVersionedIdToObjectId(pathIdVer);

70
ui-ngx/src/app/core/http/device.service.ts

@ -16,7 +16,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable, ReplaySubject } from 'rxjs'; import {catchError, Observable, of, ReplaySubject, throwError, timeout} from 'rxjs';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
@ -35,6 +35,7 @@ import { AuthService } from '@core/auth/auth.service';
import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models'; import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models';
import { PersistentRpc, RpcStatus } from '@shared/models/rpc.models'; import { PersistentRpc, RpcStatus } from '@shared/models/rpc.models';
import { ResourcesService } from '@core/services/resources.service'; import { ResourcesService } from '@core/services/resources.service';
import {map} from "rxjs/operators";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -219,4 +220,71 @@ export class DeviceService {
public downloadGatewayDockerComposeFile(deviceId: string): Observable<any> { public downloadGatewayDockerComposeFile(deviceId: string): Observable<any> {
return this.resourcesService.downloadResource(`/api/device-connectivity/gateway-launch/${deviceId}/docker-compose/download`); return this.resourcesService.downloadResource(`/api/device-connectivity/gateway-launch/${deviceId}/docker-compose/download`);
} }
public rebootDevice(deviceId: string = '', isBootstrapServer: boolean): void {
const urlApi = `/api/plugins/rpc/twoway/${deviceId}`;
// DiscoveryAll}
this.http.post(urlApi, { method: "DiscoverAll" })
.pipe(
timeout(10000), // 10 sec
catchError(err => {
console.error('DiscoverAll timeout or error', err);
return throwError(() => err);
})
)
.subscribe({
next: (response: any) => {
console.log('success: Discovery');
console.log(response);
// result = 'CONTENT'
if (response.result && response.result.toUpperCase() === 'CONTENT') {
const resourceId = isBootstrapServer ? 9 : 8;
const rebutName = isBootstrapServer ? "Bootstrap-Request Trigger" :
"Registration Update Trigger";
const resourcePath = `/1/0/${resourceId}`;
// first rebootTrigger
this.rebootTrigger(resourcePath, urlApi).subscribe(responseReboot => {
if (responseReboot.result === 'CHANGED') {
console.info(`info: ${rebutName} success.`);
} else {
console.error(`error: ${rebutName} failed: ${responseReboot.toString()}`);
}
});
}
else {
console.error(`error3: Bad registration device with id = ${deviceId} ❗ RPC result is not CONTENT`);
}
},
error: (e) => {
console.error(`error4: Bad registration device with id = ${deviceId} ${e.toString()}`);
return throwError(() => new Error('Could not get JWT token from store.'));
// return throwError(() => e);
},
complete: () => {
console.log('Discovery stream complete'); }
});
}
private rebootTrigger(resourcePath: string, urlApi: string): Observable<{ result: string;}> {
console.log(`Sending reboot command to ${resourcePath}`);
return this.http.post<any>(urlApi, {
method: 'Execute',
params: { id: resourcePath }
}).pipe(
timeout(10000),
map(res => {
console.log(res);
if (res?.result?.toUpperCase() === 'CHANGED') {
return { result: 'CHANGED' };
} else {
return {result: 'ERROR'}
};
}),
catchError(err => {
console.error(`Execute error5 for ${resourcePath}:`, err);
return of({ result: 'ERROR' });
})
);
}
} }

114
ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts

@ -32,12 +32,11 @@ import {
Lwm2mSecurityType, Lwm2mSecurityType,
Lwm2mSecurityTypeTranslationMap Lwm2mSecurityTypeTranslationMap
} from '@shared/models/lwm2m-security-config.models'; } from '@shared/models/lwm2m-security-config.models';
import {Subject, throwError, timeout, catchError, of} from 'rxjs'; import {Subject} from 'rxjs';
import {map, takeUntil} from 'rxjs/operators'; import {takeUntil} from 'rxjs/operators';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { HttpClient } from '@angular/common/http';
import {DeviceId} from "@shared/models/id/device-id"; import {DeviceId} from "@shared/models/id/device-id";
import {Observable} from "rxjs/internal/Observable"; import {DeviceService} from "@core/http/device.service";
@Component({ @Component({
selector: 'tb-device-credentials-lwm2m', selector: 'tb-device-credentials-lwm2m',
@ -72,7 +71,7 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va
deviceId: DeviceId; deviceId: DeviceId;
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
private http: HttpClient) { private deviceService: DeviceService) {
this.lwm2mConfigFormGroup = this.initLwm2mConfigForm(); this.lwm2mConfigFormGroup = this.initLwm2mConfigForm();
} }
@ -115,110 +114,13 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va
* - DiscoveryAll * - DiscoveryAll
* requestBody = "{\"method\":\"DiscoverAll\"}"; * requestBody = "{\"method\":\"DiscoverAll\"}";
* - "Registration Update Trigger", * - "Registration Update Trigger",
* requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1_1.2/0/8\"}} * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1/0/8\"}}
* - "Bootstrap-Request Trigger" * - "Bootstrap-Request Trigger"
* requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1_1.2/0/9\"}} * requestBody = "{\"method\": \"Execute\", \"params\": {\"id\": \"/1/0/9\"}}
*/ */
public rebootDevice(isBootstrapServer: boolean): void {
const urlApi = `/api/plugins/rpc/twoway/${this.deviceId.id}`;
// DiscoveryAll}
this.http.post(urlApi, { method: "DiscoverAll" })
.pipe(
timeout(10000), // 10 sec
catchError(err => {
console.error('DiscoverAll timeout or error', err);
return throwError(() => err);
})
)
.subscribe({
next: (response: any) => {
console.log('success: Discovery');
console.log(response);
// result = 'CONTENT'
if (response.result && response.result.toUpperCase() === 'CONTENT') {
const verId = this.getVerId(response.value);
console.log("ObjectId=1 ver:", verId);
const resourceId = isBootstrapServer ? 9 : 8;
const resourcePath = `/1_${verId}/0/${resourceId}`;
// first rebootTrigger
this.rebootTrigger(resourcePath, urlApi).subscribe(first => {
if (first.result === 'CHANGED') {
console.log('Reboot success first');
}
else if (first.result === 'BAD_REQUEST' && first.newVersionId && first.newVersionId !== verId) {
// Retry with new version
const correctedPath = `/1_${first.newVersionId}/0/${resourceId}`;
console.log(`Retrying with version ${first.newVersionId}`);
this.rebootTrigger(correctedPath, urlApi).subscribe(second => {
if (second.result === 'CHANGED') {
console.log('Success reboot after retry');
} else {
console.error(`error1: Reboot second failed: ${second.toString()}`);
}
});
} else {
console.error(`error2: Reboot first failed: ${first.toString()}`);
}
});
}
else {
console.error(`error3: Bad registration device with id = ${this.deviceId.id} ❗ RPC result is not CONTENT`);
}
},
error: (e) => {
console.error(`error4: Bad registration device with id = ${this.deviceId.id} ${e.toString()}`);
return throwError(() => e);
},
complete: () => {
console.log('Discovery stream complete'); }
});
}
private getVerId(value: string): string {
const verDef = '1.1';
try {
const arr = JSON.parse(value);
if (!Array.isArray(arr)) return verDef;
const obj1 = arr.find((s: string) => s.startsWith('</1'));
const match = obj1?.match(/ver="?([\d.]+)"?/);
return match ? match[1] : verDef;
} catch {
return verDef;
}
}
private rebootTrigger(resourcePath: string, urlApi: string): Observable<{ result: string; newVersionId?: string | null }> {
console.log(`Sending reboot command to ${resourcePath}`);
return this.http.post<any>(urlApi, {
method: 'Execute',
params: { id: resourcePath }
}).pipe(
timeout(10000),
map(res => {
console.log(`Reboot for ${resourcePath}`);
console.log(res);
if (res?.result?.toUpperCase() === 'CHANGED') {
return { result: 'CHANGED' };
}
if (res?.result?.toUpperCase() === 'BAD_REQUEST' && res?.error) {
const match = (res.error as string).match(/version[:=]\s*([\d.]+)/i);
const newVersionId = match ? match[1] : null;
console.warn(`BAD_REQUEST: suggested version ${newVersionId ?? 'unknown'}`);
return { result: 'BAD_REQUEST', newVersionId };
}
return { result: 'ERROR' }; public rebootDevice(isBootstrapServer: boolean): void {
}), this.deviceService.rebootDevice(this.deviceId.id, isBootstrapServer);
catchError(err => {
console.error(`Execute error5 for ${resourcePath}:`, err);
return of({ result: 'ERROR' });
})
);
} }
private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void { private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void {

Loading…
Cancel
Save