milesight-powermeter-api 2024.9.13-1 → 2024.9.24-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "milesight-powermeter-api",
3
- "version": "2024.9.13-1",
3
+ "version": "2024.9.24-0",
4
4
  "description": "Milesight Powermeter API is an internal tool accessible on backend. The API allows detail retrieval and command sending to its powermeters.",
5
5
  "author": "Lester Vitor",
6
6
  "license": "UNLICENSED",
@@ -22,7 +22,7 @@
22
22
  "build:kube:ci": "scripts/build-app.sh mewe/lorawan-milesight-powermeter-api-service"
23
23
  },
24
24
  "dependencies": {
25
- "@coinspect/therma-action-rule-decoder": "2024.9.12",
25
+ "@coinspect/therma-action-rule-decoder": "^2025.6.29-0",
26
26
  "@nestjs/common": "^10.0.0",
27
27
  "@nestjs/core": "^10.0.0",
28
28
  "@nestjs/platform-express": "^10.0.0",
@@ -56,22 +56,5 @@
56
56
  "ts-node": "^10.9.1",
57
57
  "tsconfig-paths": "^4.2.0",
58
58
  "typescript": "^5.1.3"
59
- },
60
- "jest": {
61
- "moduleFileExtensions": [
62
- "js",
63
- "json",
64
- "ts"
65
- ],
66
- "rootDir": "src",
67
- "testRegex": ".*\\.spec\\.ts$",
68
- "transform": {
69
- "^.+\\.(t|j)s$": "ts-jest"
70
- },
71
- "collectCoverageFrom": [
72
- "**/*.(t|j)s"
73
- ],
74
- "coverageDirectory": "../coverage",
75
- "testEnvironment": "node"
76
59
  }
77
60
  }
@@ -21,6 +21,7 @@ export interface AppConfig {
21
21
  region: string;
22
22
  };
23
23
  thingType: string;
24
+ threePhaseThingType: string;
24
25
  elasticAPM: {
25
26
  enabled: boolean;
26
27
  serverURL: string;
@@ -94,6 +95,10 @@ const schema: convict.Schema<AppConfig> = {
94
95
  default: 'powermeter-milesight',
95
96
  env: 'MILESIGHT_POWERMETER_THING_TYPE',
96
97
  },
98
+ threePhaseThingType: {
99
+ default: 'three-phase-powermeter-milesight',
100
+ env: 'MILESIGHT_THREE_PHASE_POWERMETER_THING_TYPE',
101
+ },
97
102
  elasticAPM: {
98
103
  enabled: {
99
104
  default: false,
@@ -8,18 +8,26 @@ import { config } from '../config';
8
8
  describe('MilesightController', () => {
9
9
  let milesightController: MilesightController;
10
10
  let milesightService: MilesightService;
11
+ let app: TestingModule;
11
12
 
12
13
  beforeEach(async () => {
13
- const app: TestingModule = await Test.createTestingModule({
14
+ app = await Test.createTestingModule({
14
15
  controllers: [MilesightController],
15
16
  providers: [DownlinkService, MilesightService],
16
17
  }).compile();
17
18
  milesightService = app.get<MilesightService>(MilesightService);
18
19
  milesightController = app.get<MilesightController>(MilesightController);
19
20
  });
21
+
20
22
  afterEach(() => {
21
23
  jest.clearAllMocks();
22
24
  });
25
+
26
+ afterAll(async () => {
27
+ if (app) {
28
+ await app.close();
29
+ }
30
+ });
23
31
  describe('getDeviceList', () => {
24
32
  it('should return device list details', async () => {
25
33
  const queryParams = { maxResult: 1 };
@@ -0,0 +1,139 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { HttpException } from '@nestjs/common';
3
+ import { ThreePhaseMilesightController } from './three-phase-milesight.controller';
4
+ import { ThreePhaseMilesightService } from '../service/three-phase-milesight.service';
5
+ import { DownlinkService } from '../service/downlink.service';
6
+ import { config } from '../config';
7
+
8
+ describe('ThreePhaseMilesightController', () => {
9
+ let threePhaseMilesightController: ThreePhaseMilesightController;
10
+ let threePhaseMilesightService: ThreePhaseMilesightService;
11
+ let app: TestingModule;
12
+
13
+ beforeEach(async () => {
14
+ app = await Test.createTestingModule({
15
+ controllers: [ThreePhaseMilesightController],
16
+ providers: [DownlinkService, ThreePhaseMilesightService],
17
+ }).compile();
18
+ threePhaseMilesightService = app.get<ThreePhaseMilesightService>(ThreePhaseMilesightService);
19
+ threePhaseMilesightController = app.get<ThreePhaseMilesightController>(ThreePhaseMilesightController);
20
+ });
21
+
22
+ afterEach(() => {
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ afterAll(async () => {
27
+ if (app) {
28
+ await app.close();
29
+ }
30
+ });
31
+ describe('getDeviceList', () => {
32
+ it('should return device list details', async () => {
33
+ const queryParams = { maxResult: 1 };
34
+ const expectedResult = {
35
+ $metadata: {
36
+ httpStatusCode: 200,
37
+ requestId: 'b5f4ba6b-de9c-4c17-9879-b6e2e02d14f5',
38
+ extendedRequestId: undefined,
39
+ cfId: undefined,
40
+ attempts: 1,
41
+ totalRetryDelay: 0,
42
+ },
43
+ nextToken: 'sampleToken',
44
+ things: [
45
+ {
46
+ attributes: {},
47
+ thingArn:
48
+ 'arn:aws:iot:us-east-1:224361144749:thing/aaaaaaaaa',
49
+ thingName: 'aaaaaaaaa',
50
+ thingTypeName: config.get('thingType'),
51
+ version: 1,
52
+ },
53
+ ],
54
+ };
55
+ const controllerResult = {
56
+ message:
57
+ 'Retrieved three-phase powermeter device list details',
58
+ result: expectedResult,
59
+ };
60
+ jest.spyOn(threePhaseMilesightService, 'getDeviceList').mockResolvedValue(
61
+ expectedResult,
62
+ );
63
+ const result = await threePhaseMilesightController.getDeviceList(queryParams);
64
+ expect(result).toEqual(controllerResult);
65
+ });
66
+ it('should handle error if device is not retrieved', async () => {
67
+ const queryParams = { maxResult: 1 };
68
+ jest.spyOn(threePhaseMilesightService, 'getDeviceList').mockResolvedValue(
69
+ null,
70
+ );
71
+ let errorResult = null;
72
+ try {
73
+ await threePhaseMilesightController.getDeviceList(queryParams);
74
+ } catch (error) {
75
+ errorResult = error;
76
+ }
77
+ expect(errorResult).toBeInstanceOf(HttpException);
78
+ });
79
+ });
80
+ describe('rebootDevice', () => {
81
+ const data = {
82
+ deviceEui: 'sampleDeviceEui',
83
+ serialNumber: 'sampleDeviceName',
84
+ };
85
+ it('should return result', async () => {
86
+ const controllerResult = {
87
+ message: `Successfully sent reboot command for three-phase device sampleDeviceEui.`,
88
+ result: true,
89
+ };
90
+ jest.spyOn(threePhaseMilesightService, 'rebootDevice').mockResolvedValue(
91
+ true,
92
+ );
93
+ const result = await threePhaseMilesightController.rebootDevice(data);
94
+ expect(result).toEqual(controllerResult);
95
+ });
96
+ it('should handle error properly', async () => {
97
+ jest.spyOn(threePhaseMilesightService, 'rebootDevice').mockResolvedValue(
98
+ null,
99
+ );
100
+ let errorResult = null;
101
+ try {
102
+ await threePhaseMilesightController.rebootDevice(data);
103
+ } catch (error) {
104
+ errorResult = error;
105
+ }
106
+ expect(errorResult).toBeInstanceOf(HttpException);
107
+ });
108
+ });
109
+ describe('setDeviceInterval', () => {
110
+ const data = {
111
+ deviceEui: 'sampleDeviceEui',
112
+ serialNumber: 'sampleDeviceName',
113
+ interval: 1440,
114
+ };
115
+ it('should return result', async () => {
116
+ const controllerResult = {
117
+ message: `Successfully sent set device interval command for three-phase device sampleDeviceEui.`,
118
+ result: true,
119
+ };
120
+ jest.spyOn(threePhaseMilesightService, 'setDeviceInterval').mockResolvedValue(
121
+ true,
122
+ );
123
+ const result = await threePhaseMilesightController.setDeviceInterval(data);
124
+ expect(result).toEqual(controllerResult);
125
+ });
126
+ it('should handle error properly', async () => {
127
+ jest.spyOn(threePhaseMilesightService, 'setDeviceInterval').mockResolvedValue(
128
+ null,
129
+ );
130
+ let errorResult = null;
131
+ try {
132
+ await threePhaseMilesightController.setDeviceInterval(data);
133
+ } catch (error) {
134
+ errorResult = error;
135
+ }
136
+ expect(errorResult).toBeInstanceOf(HttpException);
137
+ });
138
+ });
139
+ });
@@ -0,0 +1,88 @@
1
+ import {
2
+ Body,
3
+ Controller,
4
+ HttpException,
5
+ HttpStatus,
6
+ Get,
7
+ Query,
8
+ Post,
9
+ UsePipes,
10
+ ValidationPipe,
11
+ } from '@nestjs/common';
12
+ import {
13
+ DeviceListParamsDto,
14
+ MilesightBaseDto,
15
+ MilesightSetIntervalDto,
16
+ } from '../dto/milesight.dto';
17
+ import { ThreePhaseMilesightService } from '../service/three-phase-milesight.service';
18
+ import { AWSListThings } from '@coinspect/therma-action-rule-decoder';
19
+
20
+ @Controller('powermeter/three-phase/milesight')
21
+ export class ThreePhaseMilesightController {
22
+ constructor(private readonly threePhaseMilesightService: ThreePhaseMilesightService) {}
23
+ @Get()
24
+ async getDeviceList(
25
+ @Query() queryParams: DeviceListParamsDto,
26
+ ): Promise<{ message: string; result: AWSListThings }> {
27
+ const { maxResult, nextToken } = queryParams;
28
+ const result = await this.threePhaseMilesightService.getDeviceList(
29
+ maxResult,
30
+ nextToken,
31
+ );
32
+ if (!result) {
33
+ throw new HttpException(
34
+ 'Error occured',
35
+ HttpStatus.INTERNAL_SERVER_ERROR,
36
+ );
37
+ }
38
+ return {
39
+ message: 'Retrieved three-phase powermeter device list details',
40
+ result,
41
+ };
42
+ }
43
+
44
+ @Post('reboot')
45
+ @UsePipes(new ValidationPipe())
46
+ async rebootDevice(
47
+ @Body() body: MilesightBaseDto,
48
+ ): Promise<{ message: string; result: boolean }> {
49
+ const { deviceEui, serialNumber } = body;
50
+ const result = await this.threePhaseMilesightService.rebootDevice(
51
+ deviceEui,
52
+ serialNumber ? serialNumber : deviceEui,
53
+ );
54
+ if (!result) {
55
+ throw new HttpException(
56
+ `Error occurred rebooting three-phase device: ${deviceEui}`,
57
+ HttpStatus.INTERNAL_SERVER_ERROR,
58
+ );
59
+ }
60
+ return {
61
+ message: `Successfully sent reboot command for three-phase device ${deviceEui}.`,
62
+ result,
63
+ };
64
+ }
65
+
66
+ @Post('interval')
67
+ @UsePipes(new ValidationPipe())
68
+ async setDeviceInterval(
69
+ @Body() body: MilesightSetIntervalDto,
70
+ ): Promise<{ message: string; result: boolean }> {
71
+ const { deviceEui, serialNumber, interval } = body;
72
+ const result = await this.threePhaseMilesightService.setDeviceInterval(
73
+ deviceEui,
74
+ serialNumber ? serialNumber : deviceEui,
75
+ interval,
76
+ );
77
+ if (!result) {
78
+ throw new HttpException(
79
+ `Error occurred setting interval for three-phase device: ${deviceEui}`,
80
+ HttpStatus.INTERNAL_SERVER_ERROR,
81
+ );
82
+ }
83
+ return {
84
+ message: `Successfully sent set device interval command for three-phase device ${deviceEui}.`,
85
+ result,
86
+ };
87
+ }
88
+ }
@@ -1,11 +1,13 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import { MilesightController } from '../controller/milesight.controller';
3
+ import { ThreePhaseMilesightController } from '../controller/three-phase-milesight.controller';
3
4
  import { MilesightService } from '../service/milesight.service';
5
+ import { ThreePhaseMilesightService } from '../service/three-phase-milesight.service';
4
6
  import { DownlinkModule } from './downlink.module';
5
7
 
6
8
  @Module({
7
9
  imports: [DownlinkModule],
8
- controllers: [MilesightController],
9
- providers: [MilesightService],
10
+ controllers: [MilesightController, ThreePhaseMilesightController],
11
+ providers: [MilesightService, ThreePhaseMilesightService],
10
12
  })
11
- export class MilesightPowermeterModule {}
13
+ export class MilesightPowermeterModule {}
@@ -0,0 +1,228 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { Logger } from '@nestjs/common';
3
+ import { DownlinkService } from './downlink.service';
4
+ import { ThreePhaseMilesightService } from './three-phase-milesight.service';
5
+
6
+ const mockGetListDevices = jest.fn();
7
+ const mockGetDeviceWithTags = jest.fn();
8
+ jest.mock('@coinspect/therma-action-rule-decoder', () => {
9
+ const actualPackage = jest.requireActual(
10
+ '@coinspect/therma-action-rule-decoder',
11
+ );
12
+ return {
13
+ ...actualPackage,
14
+ AWSIotAPI: jest.fn().mockImplementation(() => ({
15
+ getListThingsByType: mockGetListDevices,
16
+ })),
17
+ AWSWirelessIotAPI: jest.fn().mockImplementation(() => ({
18
+ getDeviceWithTags: mockGetDeviceWithTags,
19
+ })),
20
+ };
21
+ });
22
+
23
+ describe('ThreePhaseMilesightService', () => {
24
+ let threePhaseMilesightService: ThreePhaseMilesightService;
25
+ let downlinkService: DownlinkService;
26
+ let module: TestingModule;
27
+ const mockSendDownlink = jest.fn();
28
+ const mockLogger = {
29
+ error: jest.fn(),
30
+ log: jest.fn(),
31
+ warn: jest.fn(),
32
+ debug: jest.fn(),
33
+ verbose: jest.fn(),
34
+ };
35
+
36
+ beforeEach(async () => {
37
+ module = await Test.createTestingModule({
38
+ providers: [
39
+ {
40
+ provide: DownlinkService,
41
+ useValue: {
42
+ sendDownlink: mockSendDownlink,
43
+ },
44
+ },
45
+ {
46
+ provide: Logger,
47
+ useValue: mockLogger,
48
+ },
49
+ ThreePhaseMilesightService,
50
+ ],
51
+ }).compile();
52
+
53
+ threePhaseMilesightService = module.get<ThreePhaseMilesightService>(ThreePhaseMilesightService);
54
+ downlinkService = module.get<DownlinkService>(DownlinkService);
55
+ });
56
+
57
+ afterEach(async () => {
58
+ jest.clearAllMocks();
59
+ mockLogger.error.mockClear();
60
+ });
61
+
62
+ afterAll(async () => {
63
+ if (module) {
64
+ await module.close();
65
+ }
66
+ });
67
+
68
+ describe('getDeviceList', () => {
69
+ it('should return three-phase device list details', async () => {
70
+ const MaxResult = 1;
71
+ const expectedResult = {
72
+ $metadata: {
73
+ httpStatusCode: 200,
74
+ requestId: 'b5f4ba6b-de9c-4c17-9879-b6e2e02d14f5',
75
+ extendedRequestId: undefined,
76
+ cfId: undefined,
77
+ attempts: 1,
78
+ totalRetryDelay: 0,
79
+ },
80
+ nextToken: 'sampleToken',
81
+ things: [
82
+ {
83
+ attributes: {},
84
+ thingArn:
85
+ 'arn:aws:iot:us-east-1:224361144749:thing/three-phase-device',
86
+ thingName: 'three-phase-device',
87
+ thingTypeName: 'three-phase-powermeter-milesight',
88
+ version: 1,
89
+ },
90
+ ],
91
+ };
92
+ mockGetListDevices.mockResolvedValue(expectedResult);
93
+
94
+ const result = await threePhaseMilesightService.getDeviceList(MaxResult);
95
+
96
+ expect(mockGetListDevices).toHaveBeenCalledWith(
97
+ 'three-phase-powermeter-milesight',
98
+ MaxResult,
99
+ undefined
100
+ );
101
+ expect(result).toEqual(expectedResult);
102
+ });
103
+
104
+ it('should return null when getting error on device list', async () => {
105
+ const MaxResult = 1;
106
+ mockGetListDevices.mockRejectedValue(new Error('Test error'));
107
+
108
+ const response = await threePhaseMilesightService.getDeviceList(MaxResult);
109
+ expect(response).toBeNull();
110
+ mockGetListDevices.mockClear();
111
+ });
112
+ });
113
+
114
+ describe('rebootDevice', () => {
115
+ const data = {
116
+ deviceEui: 'sampleThreePhaseDeviceEui',
117
+ serialNumber: 'sampleThreePhaseDeviceName',
118
+ };
119
+
120
+ it('should return true when downlink is successful', async () => {
121
+ const expectedDownlinkCommand = 'ff10ff';
122
+ const sendDownlinkSpy = jest
123
+ .spyOn(downlinkService, 'sendDownlink')
124
+ .mockResolvedValue(true);
125
+
126
+ const result = await threePhaseMilesightService.rebootDevice(
127
+ data.deviceEui,
128
+ data.serialNumber,
129
+ );
130
+
131
+ expect(sendDownlinkSpy).toHaveBeenCalledWith(
132
+ data.deviceEui,
133
+ data.serialNumber,
134
+ expectedDownlinkCommand,
135
+ 'threePhaseMilesightRebootDevice',
136
+ 85,
137
+ true,
138
+ );
139
+ expect(result).toBe(true);
140
+ sendDownlinkSpy.mockClear();
141
+ });
142
+
143
+ it('should return null when downlink fails', async () => {
144
+ const sendDownlinkSpy = jest
145
+ .spyOn(downlinkService, 'sendDownlink')
146
+ .mockRejectedValue(new Error('Downlink error'));
147
+
148
+ const result = await threePhaseMilesightService.rebootDevice(
149
+ data.deviceEui,
150
+ data.serialNumber,
151
+ );
152
+
153
+ expect(result).toBeNull();
154
+ sendDownlinkSpy.mockClear();
155
+ });
156
+ });
157
+
158
+ describe('setDeviceInterval', () => {
159
+ const data = {
160
+ deviceEui: 'sampleThreePhaseDeviceEui',
161
+ serialNumber: 'sampleThreePhaseDeviceName',
162
+ interval: 1440,
163
+ };
164
+
165
+ it('should return true when downlink is successful', async () => {
166
+ const expectedDownlinkCommand = 'ff8e0005a0'; // 1440 minutes in hex
167
+ const sendDownlinkSpy = jest
168
+ .spyOn(downlinkService, 'sendDownlink')
169
+ .mockResolvedValue(true);
170
+
171
+ const result = await threePhaseMilesightService.setDeviceInterval(
172
+ data.deviceEui,
173
+ data.serialNumber,
174
+ data.interval,
175
+ );
176
+
177
+ expect(sendDownlinkSpy).toHaveBeenCalledWith(
178
+ data.deviceEui,
179
+ data.serialNumber,
180
+ expectedDownlinkCommand,
181
+ 'threePhaseMilesightSetDeviceInterval',
182
+ 85,
183
+ true,
184
+ );
185
+ expect(result).toBe(true);
186
+ sendDownlinkSpy.mockClear();
187
+ });
188
+
189
+ it('should return null when downlink fails', async () => {
190
+ const sendDownlinkSpy = jest
191
+ .spyOn(downlinkService, 'sendDownlink')
192
+ .mockRejectedValue(new Error('Downlink error'));
193
+
194
+ const result = await threePhaseMilesightService.setDeviceInterval(
195
+ data.deviceEui,
196
+ data.serialNumber,
197
+ data.interval,
198
+ );
199
+
200
+ expect(result).toBeNull();
201
+ sendDownlinkSpy.mockClear();
202
+ });
203
+
204
+ it('should correctly convert different intervals to hex', async () => {
205
+ const sendDownlinkSpy = jest
206
+ .spyOn(downlinkService, 'sendDownlink')
207
+ .mockResolvedValue(true);
208
+
209
+ // Test interval of 60 minutes (0x003c in hex)
210
+ await threePhaseMilesightService.setDeviceInterval(
211
+ data.deviceEui,
212
+ data.serialNumber,
213
+ 60,
214
+ );
215
+
216
+ expect(sendDownlinkSpy).toHaveBeenCalledWith(
217
+ data.deviceEui,
218
+ data.serialNumber,
219
+ 'ff8e00003c',
220
+ 'threePhaseMilesightSetDeviceInterval',
221
+ 85,
222
+ true,
223
+ );
224
+
225
+ sendDownlinkSpy.mockClear();
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,97 @@
1
+ import {
2
+ AWSIotAPI,
3
+ AWSListThings,
4
+ } from '@coinspect/therma-action-rule-decoder';
5
+ import { Inject, Injectable, Logger } from '@nestjs/common';
6
+ import { DownlinkService } from './downlink.service';
7
+ import { config } from '../config';
8
+
9
+ const fPort = config.get('fport');
10
+
11
+ @Injectable()
12
+ export class ThreePhaseMilesightService {
13
+ private readonly logger: Logger;
14
+ private readonly awsIotAPI: AWSIotAPI;
15
+
16
+ constructor(
17
+ @Inject(DownlinkService)
18
+ private readonly downlinkService: DownlinkService,
19
+ ) {
20
+ this.logger = new Logger(ThreePhaseMilesightService.name);
21
+ this.awsIotAPI = new AWSIotAPI(config.get('aws'));
22
+ }
23
+
24
+ async getDeviceList(
25
+ maxResult: number,
26
+ nextToken?: string,
27
+ ): Promise<AWSListThings | null> {
28
+ try {
29
+ this.logger.log('Retrieving three-phase devices list');
30
+ const result = await this.awsIotAPI.getListThingsByType(
31
+ config.get('threePhaseThingType'),
32
+ maxResult,
33
+ nextToken,
34
+ );
35
+ return result;
36
+ } catch (error) {
37
+ this.logger.error(
38
+ `Error retrieving three-phase devices list details: ${error}`,
39
+ );
40
+ return null;
41
+ }
42
+ }
43
+
44
+ async rebootDevice(
45
+ deviceEui: string,
46
+ serialNumber: string,
47
+ ): Promise<boolean | null> {
48
+ try {
49
+ this.logger.log(`Rebooting three-phase device: ${deviceEui}`);
50
+ const rebootDownlinkCommand = 'ff10ff';
51
+ const response = await this.downlinkService.sendDownlink(
52
+ deviceEui,
53
+ serialNumber,
54
+ rebootDownlinkCommand,
55
+ 'threePhaseMilesightRebootDevice',
56
+ fPort,
57
+ true,
58
+ );
59
+ this.logger.log(`Successfully rebooted three-phase device ${deviceEui}`);
60
+ return response;
61
+ } catch (error) {
62
+ this.logger.error(`Error rebooting three-phase device ${deviceEui}: ${error}`);
63
+ return null;
64
+ }
65
+ }
66
+
67
+ async setDeviceInterval(
68
+ deviceEui: string,
69
+ serialNumber: string,
70
+ interval: number,
71
+ ): Promise<boolean | null> {
72
+ try {
73
+ this.logger.log(
74
+ `Setting ${interval} minutes interval for three-phase device: ${deviceEui}`,
75
+ );
76
+ const hexString = interval.toString(16).padStart(4, '0');
77
+ const downlinkCommand = 'ff8e00' + hexString;
78
+ const response = await this.downlinkService.sendDownlink(
79
+ deviceEui,
80
+ serialNumber,
81
+ downlinkCommand,
82
+ 'threePhaseMilesightSetDeviceInterval',
83
+ fPort,
84
+ true,
85
+ );
86
+ this.logger.log(
87
+ `Successfully set device interval for three-phase device ${deviceEui}`,
88
+ );
89
+ return response;
90
+ } catch (error) {
91
+ this.logger.error(
92
+ `Error setting device interval for three-phase device ${deviceEui}: ${error}`,
93
+ );
94
+ return null;
95
+ }
96
+ }
97
+ }
package/timestamp.sh CHANGED
@@ -1 +1 @@
1
- export DT=20240923034156
1
+ export DT=20250702031942