mclimate-payload-helper 1.2.2 → 1.2.4
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/dist/decoders/commandsReadingHelper.d.ts.map +1 -1
- package/dist/decoders/commandsReadingHelper.js +182 -41
- package/dist/decoders/commandsReadingHelper.js.map +1 -1
- package/dist/decoders/payloadParsers/index.d.ts +1 -0
- package/dist/decoders/payloadParsers/index.d.ts.map +1 -1
- package/dist/decoders/payloadParsers/index.js +1 -0
- package/dist/decoders/payloadParsers/index.js.map +1 -1
- package/dist/decoders/payloadParsers/melissaLorawanPayloadParser.d.ts.map +1 -1
- package/dist/decoders/payloadParsers/melissaLorawanPayloadParser.js +12 -12
- package/dist/decoders/payloadParsers/melissaLorawanPayloadParser.js.map +1 -1
- package/dist/decoders/payloadParsers/multisensorPayloadParser.d.ts +2 -0
- package/dist/decoders/payloadParsers/multisensorPayloadParser.d.ts.map +1 -0
- package/dist/decoders/payloadParsers/multisensorPayloadParser.js +96 -0
- package/dist/decoders/payloadParsers/multisensorPayloadParser.js.map +1 -0
- package/dist/decoders/payloadParsers/types/allDevices.d.ts +2 -1
- package/dist/decoders/payloadParsers/types/allDevices.d.ts.map +1 -1
- package/dist/decoders/payloadParsers/types/allDevices.js +1 -0
- package/dist/decoders/payloadParsers/types/allDevices.js.map +1 -1
- package/dist/encoders/Co2DisplayCommonCommands.d.ts +0 -4
- package/dist/encoders/Co2DisplayCommonCommands.d.ts.map +1 -1
- package/dist/encoders/Co2DisplayCommonCommands.js +0 -38
- package/dist/encoders/Co2DisplayCommonCommands.js.map +1 -1
- package/dist/encoders/CommandBuilder.d.ts.map +1 -1
- package/dist/encoders/CommandBuilder.js +2 -0
- package/dist/encoders/CommandBuilder.js.map +1 -1
- package/dist/encoders/FanCoilThermostat.d.ts +61 -0
- package/dist/encoders/FanCoilThermostat.d.ts.map +1 -0
- package/dist/encoders/FanCoilThermostat.js +719 -0
- package/dist/encoders/FanCoilThermostat.js.map +1 -0
- package/dist/encoders/MelissaCommands.d.ts +6 -0
- package/dist/encoders/MelissaCommands.d.ts.map +1 -0
- package/dist/encoders/MelissaCommands.js +35 -0
- package/dist/encoders/MelissaCommands.js.map +1 -0
- package/dist/encoders/MultiSensorCommands.d.ts +25 -0
- package/dist/encoders/MultiSensorCommands.d.ts.map +1 -0
- package/dist/encoders/MultiSensorCommands.js +256 -0
- package/dist/encoders/MultiSensorCommands.js.map +1 -0
- package/dist/encoders/SetAqiLed.d.ts +6 -0
- package/dist/encoders/SetAqiLed.d.ts.map +1 -0
- package/dist/encoders/SetAqiLed.js +46 -0
- package/dist/encoders/SetAqiLed.js.map +1 -0
- package/dist/encoders/index.d.ts +2 -0
- package/dist/encoders/index.d.ts.map +1 -1
- package/dist/encoders/index.js +2 -0
- package/dist/encoders/index.js.map +1 -1
- package/dist/encoders/types/enumValidation.d.ts +6 -0
- package/dist/encoders/types/enumValidation.d.ts.map +1 -0
- package/dist/encoders/types/enumValidation.js +50 -0
- package/dist/encoders/types/enumValidation.js.map +1 -0
- package/dist/encoders/types/schemaValidatedEnums.d.ts +40 -0
- package/dist/encoders/types/schemaValidatedEnums.d.ts.map +1 -0
- package/dist/encoders/types/schemaValidatedEnums.js +181 -0
- package/dist/encoders/types/schemaValidatedEnums.js.map +1 -0
- package/dist/encoders/types/schemas.d.ts +352 -0
- package/dist/encoders/types/schemas.d.ts.map +1 -1
- package/dist/encoders/types/schemas.js +29 -3
- package/dist/encoders/types/schemas.js.map +1 -1
- package/dist/test/payloadDecoders.test.js +28 -10
- package/dist/test/payloadDecoders.test.js.map +1 -1
- package/dist/test/payloadEncoders.test.js +6 -0
- package/dist/test/payloadEncoders.test.js.map +1 -1
- package/dist/types/deviceTypes.d.ts +19 -0
- package/dist/types/deviceTypes.d.ts.map +1 -0
- package/dist/types/deviceTypes.js +23 -0
- package/dist/types/deviceTypes.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +9 -0
- package/dist/types/types.js.map +1 -0
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +8 -0
- package/dist/validation.js.map +1 -0
- package/package.json +1 -1
- package/src/decoders/commandsReadingHelper.ts +327 -186
- package/src/decoders/payloadParsers/index.ts +1 -1
- package/src/decoders/payloadParsers/melissaLorawanPayloadParser.ts +19 -16
- package/src/decoders/payloadParsers/multisensorPayloadParser.ts +97 -0
- package/src/decoders/payloadParsers/types/allDevices.ts +1 -0
- package/src/encoders/CommandBuilder.ts +4 -0
- package/src/encoders/MelissaCommands.ts +33 -0
- package/src/encoders/MultiSensorCommands.ts +253 -0
- package/src/encoders/index.ts +2 -0
- package/src/encoders/types/schemas.ts +74 -4
- package/src/test/payloadDecoders.test.ts +30 -11
- package/src/test/payloadEncoders.test.ts +8 -0
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
import { commandsReadingHelper } from '@/decoders/commandsReadingHelper'
|
|
2
|
-
import { DeviceType
|
|
2
|
+
import { DeviceType } from '@/decoders/payloadParsers/types'
|
|
3
3
|
import { byteArrayParser } from '@/helpers'
|
|
4
4
|
import { CustomError } from '@/utils'
|
|
5
5
|
|
|
6
|
+
interface MelissaKeepAliveData {
|
|
7
|
+
sensorTemperature?: number
|
|
8
|
+
relativeHumidity?: number
|
|
9
|
+
isIrCodeRecordingRequested?: boolean
|
|
10
|
+
}
|
|
6
11
|
export const melissaLorawanPayloadParser = (hexData: string) => {
|
|
7
12
|
let deviceData = {}
|
|
8
13
|
|
|
9
14
|
try {
|
|
10
15
|
const handleKeepAliveData = (bytes: number[]) => {
|
|
11
|
-
let keepaliveData:
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
keepaliveData.internalTemperature = isNegative ? -temperature : temperature;
|
|
17
|
-
|
|
18
|
-
// Energy data
|
|
19
|
-
keepaliveData.energy = ((bytes[2] << 24) | (bytes[3] << 16) | (bytes[4] << 8) | bytes[5]) / 1000
|
|
16
|
+
let keepaliveData: MelissaKeepAliveData = {}
|
|
17
|
+
// Internal temperature sensor data from bytes 1-2 (16 bits)
|
|
18
|
+
// According to the screenshot: t[°C] = (T[15:0] - 400) / 10
|
|
19
|
+
const rawTemperature = (bytes[1] << 8) | bytes[2];
|
|
20
|
+
keepaliveData.sensorTemperature = Number(((rawTemperature - 400) / 10).toFixed(2));
|
|
20
21
|
|
|
21
|
-
//
|
|
22
|
-
|
|
22
|
+
// Relative Humidity from byte 3
|
|
23
|
+
// According to the screenshot: RH[%] = (XX * 100) / 256
|
|
24
|
+
keepaliveData.relativeHumidity = Number(((bytes[3] * 100) / 256).toFixed(2));
|
|
23
25
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
+
// Device status information (byte 4)
|
|
27
|
+
// Bit 7 indicates if IR code recording is requested (1) or not (0)
|
|
28
|
+
keepaliveData.isIrCodeRecordingRequested = ((bytes[4] & 0x80) !== 0);
|
|
26
29
|
|
|
27
30
|
Object.assign(deviceData, { ...deviceData }, { ...keepaliveData })
|
|
28
31
|
}
|
|
@@ -35,7 +38,7 @@ export const melissaLorawanPayloadParser = (hexData: string) => {
|
|
|
35
38
|
handleKeepAliveData(byteArray)
|
|
36
39
|
} else {
|
|
37
40
|
// parse command answers
|
|
38
|
-
let data = commandsReadingHelper(hexData,
|
|
41
|
+
let data = commandsReadingHelper(hexData, 10, DeviceType.MelissaLorawan)
|
|
39
42
|
if (!data) return
|
|
40
43
|
const shouldKeepAlive = data.hasOwnProperty('decodeKeepalive') ? true : false
|
|
41
44
|
if ('decodeKeepalive' in data) {
|
|
@@ -46,7 +49,7 @@ export const melissaLorawanPayloadParser = (hexData: string) => {
|
|
|
46
49
|
|
|
47
50
|
// get only keepalive from device response
|
|
48
51
|
if (shouldKeepAlive) {
|
|
49
|
-
let keepaliveData = hexData.slice(-
|
|
52
|
+
let keepaliveData = hexData.slice(-10)
|
|
50
53
|
let dataToPass = byteArrayParser(keepaliveData)
|
|
51
54
|
if (!dataToPass) return
|
|
52
55
|
handleKeepAliveData(dataToPass)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { commandsReadingHelper } from '@/decoders'
|
|
2
|
+
import { CustomError } from '@/utils'
|
|
3
|
+
import { DeviceType } from '@/decoders/payloadParsers/types'
|
|
4
|
+
import { byteArrayParser } from '@/helpers'
|
|
5
|
+
|
|
6
|
+
// Multisensor payload parser (uplink). Structure mirrors other parsers in this folder.
|
|
7
|
+
export const multisensorPayloadParser = (hexData: string) => {
|
|
8
|
+
let deviceData: any = {}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
if (!hexData) return
|
|
12
|
+
|
|
13
|
+
const bytes = byteArrayParser(hexData)
|
|
14
|
+
if (!bytes || !bytes.length) return
|
|
15
|
+
|
|
16
|
+
const toHex = (arr: number[]) => arr.map((b) => ('0' + b.toString(16)).slice(-2)).join('')
|
|
17
|
+
|
|
18
|
+
const handleKeepAliveData = (barr: number[]) => {
|
|
19
|
+
// Decode according to provided example indexing
|
|
20
|
+
const b = (i: number, d = 0) => (typeof barr[i] === 'number' ? barr[i] : d)
|
|
21
|
+
|
|
22
|
+
// Byte 1..14 mapping per example
|
|
23
|
+
const deltaTMinC = b(1) / 10
|
|
24
|
+
const deltaTMaxC = b(2) / 10
|
|
25
|
+
const avgTempC = Number((b(3) / 10).toFixed(1))
|
|
26
|
+
const relativeHumidity = b(4)
|
|
27
|
+
// Pressure: matches example (A8 -> 956.94): scale = 390
|
|
28
|
+
const pressureMbar = Number(((b(5) * 390) / 255 + 700).toFixed(2))
|
|
29
|
+
const staticAQI = Number(((b(6) * 500) / 255).toFixed(2))
|
|
30
|
+
// eCO2: matches example (06 -> 508.23): scale = 4600
|
|
31
|
+
const eCO2ppm = Number((((b(7) * 4600) / 255) + 400).toFixed(2))
|
|
32
|
+
const tvocRaw = ((b(8) & 0xff) << 8) | (b(9) & 0xff)
|
|
33
|
+
const tvocPpbApprox = tvocRaw
|
|
34
|
+
const lightLux = Math.round((b(10) * 1000) / 255)
|
|
35
|
+
const noiseDb = b(11)
|
|
36
|
+
const pirTriggerCount = b(12)
|
|
37
|
+
const deviceVoltageMv = Math.round((b(13) * 2200) / 255 + 1600)
|
|
38
|
+
const deviceVoltage = Number((deviceVoltageMv / 1000).toFixed(2))
|
|
39
|
+
const powerSourceRaw = b(14)
|
|
40
|
+
const powerSource = powerSourceRaw === 1 ? 'USB' : 'Battery'
|
|
41
|
+
|
|
42
|
+
const keepaliveData = {
|
|
43
|
+
type: 'keep_alive',
|
|
44
|
+
deltaTMinC,
|
|
45
|
+
deltaTMaxC,
|
|
46
|
+
avgTempC,
|
|
47
|
+
relativeHumidity,
|
|
48
|
+
pressureMbar,
|
|
49
|
+
staticAQI,
|
|
50
|
+
eCO2ppm,
|
|
51
|
+
tvocRaw,
|
|
52
|
+
tvocPpbApprox,
|
|
53
|
+
lightLux,
|
|
54
|
+
noiseDb,
|
|
55
|
+
pirTriggerCount,
|
|
56
|
+
deviceVoltageMv,
|
|
57
|
+
deviceVoltage,
|
|
58
|
+
powerSourceRaw,
|
|
59
|
+
powerSource,
|
|
60
|
+
raw: toHex(barr),
|
|
61
|
+
}
|
|
62
|
+
Object.assign(deviceData, { ...deviceData }, { ...keepaliveData })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (hexData.slice(0, 2).toLowerCase() === '82') {
|
|
66
|
+
// keep-alive packet
|
|
67
|
+
handleKeepAliveData(bytes)
|
|
68
|
+
} else {
|
|
69
|
+
// parse command answers via helper
|
|
70
|
+
const data = commandsReadingHelper(hexData, 30, DeviceType.MultiSensor)
|
|
71
|
+
if (!data) return
|
|
72
|
+
|
|
73
|
+
const shouldKeepAlive = data.hasOwnProperty('decodeKeepalive') ? true : false
|
|
74
|
+
if ('decodeKeepalive' in data) {
|
|
75
|
+
delete (data as any).decodeKeepalive
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Object.assign(deviceData, { ...deviceData }, { ...data })
|
|
79
|
+
|
|
80
|
+
// If device response includes keepalive, decode last 15 bytes (30 hex chars)
|
|
81
|
+
if (shouldKeepAlive) {
|
|
82
|
+
const keepaliveHex = hexData.slice(-30)
|
|
83
|
+
const keepaliveBytes = byteArrayParser(keepaliveHex)
|
|
84
|
+
if (keepaliveBytes) handleKeepAliveData(keepaliveBytes)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return deviceData
|
|
89
|
+
} catch (e) {
|
|
90
|
+
throw new CustomError({
|
|
91
|
+
message: `Unhandled data`,
|
|
92
|
+
hexData: hexData,
|
|
93
|
+
deviceType: 'multisensor',
|
|
94
|
+
originalError: e as Error,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
Relay16DryCommands,
|
|
20
20
|
HTPirLiteCommands,
|
|
21
21
|
Co2PirLiteCommands,
|
|
22
|
+
MelissaCommands,
|
|
23
|
+
MultiSensorCommands,
|
|
22
24
|
} from '@/encoders'
|
|
23
25
|
|
|
24
26
|
import { CustomError, toCamelCase } from '@/utils'
|
|
@@ -48,6 +50,8 @@ export class CommandBuilder {
|
|
|
48
50
|
relay_16_dry: Relay16DryCommands,
|
|
49
51
|
ht_pir_lite: HTPirLiteCommands,
|
|
50
52
|
co2_pir_lite: Co2PirLiteCommands,
|
|
53
|
+
melissa_lorawan: MelissaCommands,
|
|
54
|
+
multisensor: MultiSensorCommands,
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GeneralCommands, BaseCommand } from '@/encoders'
|
|
2
|
+
import { applyMixins, decToHex, CustomError } from '@/utils'
|
|
3
|
+
import { DeviceCommandSchemas, MelissaCommandTypes } from './types'
|
|
4
|
+
import { ZodError, } from 'zod'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class MelissaCommands extends GeneralCommands {
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
static setIrCodeRecording(params: MelissaCommandTypes.SetIrCodeRecordingParams) {
|
|
11
|
+
try {
|
|
12
|
+
DeviceCommandSchemas.MelissaCommandSchemas.setIrCodeRecording.parse(params)
|
|
13
|
+
const { time } = params
|
|
14
|
+
return new BaseCommand('SetIrCodeRecording', 0x09, decToHex(time))
|
|
15
|
+
} catch (e) {
|
|
16
|
+
if (e instanceof ZodError) {
|
|
17
|
+
throw new CustomError({
|
|
18
|
+
message: 'Zod validation error during SetIrCodeRecording execution',
|
|
19
|
+
command: 'SetIrCodeRecording',
|
|
20
|
+
originalError: e,
|
|
21
|
+
})
|
|
22
|
+
} else {
|
|
23
|
+
throw new CustomError({
|
|
24
|
+
message: 'Error during SetIrCodeRecording execution',
|
|
25
|
+
command: 'SetIrCodeRecording',
|
|
26
|
+
originalError: e as Error,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
applyMixins(MelissaCommands, [
|
|
33
|
+
])
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { BaseCommand, GeneralCommands } from '@/encoders'
|
|
2
|
+
import { ZodError } from 'zod'
|
|
3
|
+
import { CustomError, decToHex, dec2hex } from '@/utils'
|
|
4
|
+
import { MultiSensorCommandTypes, DeviceCommandSchemas } from '@/encoders/types'
|
|
5
|
+
|
|
6
|
+
export class MultiSensorCommands extends GeneralCommands {
|
|
7
|
+
static setLightState(params: MultiSensorCommandTypes.SetLightStateParams) {
|
|
8
|
+
try {
|
|
9
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setLightState.parse(params)
|
|
10
|
+
const { enabled } = params
|
|
11
|
+
const enabledValue = enabled ? 1 : 0
|
|
12
|
+
return new BaseCommand('SetLightState', 0x1e, decToHex(enabledValue))
|
|
13
|
+
} catch (e) {
|
|
14
|
+
if (e instanceof ZodError) {
|
|
15
|
+
throw new CustomError({
|
|
16
|
+
message: 'Zod validation error during SetLightState execution',
|
|
17
|
+
command: 'SetLightState',
|
|
18
|
+
originalError: e,
|
|
19
|
+
})
|
|
20
|
+
} else {
|
|
21
|
+
throw new CustomError({
|
|
22
|
+
message: 'Error during SetLightState execution',
|
|
23
|
+
command: 'SetLightState',
|
|
24
|
+
originalError: e as Error,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static getLightState() {
|
|
31
|
+
return new BaseCommand('GetLightState', 0x1f)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static setHallState(params: MultiSensorCommandTypes.SetHallStateParams) {
|
|
35
|
+
try {
|
|
36
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setHallState.parse(params)
|
|
37
|
+
const { enabled } = params
|
|
38
|
+
const enabledValue = enabled ? 1 : 0
|
|
39
|
+
return new BaseCommand('SetHallState', 0x20, decToHex(enabledValue))
|
|
40
|
+
} catch (e) {
|
|
41
|
+
if (e instanceof ZodError) {
|
|
42
|
+
throw new CustomError({
|
|
43
|
+
message: 'Zod validation error during SetHallState execution',
|
|
44
|
+
command: 'SetHallState',
|
|
45
|
+
originalError: e,
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
throw new CustomError({
|
|
49
|
+
message: 'Error during SetHallState execution',
|
|
50
|
+
command: 'SetHallState',
|
|
51
|
+
originalError: e as Error,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static getHallState() {
|
|
58
|
+
return new BaseCommand('GetHallState', 0x21)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static setHallBlindPeriod(params: MultiSensorCommandTypes.SetHallBlindPeriodParams) {
|
|
62
|
+
try {
|
|
63
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setHallBlindPeriod.parse(params)
|
|
64
|
+
const { period } = params
|
|
65
|
+
return new BaseCommand('SetHallBlindPeriod', 0x22, dec2hex(period))
|
|
66
|
+
} catch (e) {
|
|
67
|
+
if (e instanceof ZodError) {
|
|
68
|
+
throw new CustomError({
|
|
69
|
+
message: 'Zod validation error during SetHallBlindPeriod execution',
|
|
70
|
+
command: 'SetHallBlindPeriod',
|
|
71
|
+
originalError: e,
|
|
72
|
+
})
|
|
73
|
+
} else {
|
|
74
|
+
throw new CustomError({
|
|
75
|
+
message: 'Error during SetHallBlindPeriod execution',
|
|
76
|
+
command: 'SetHallBlindPeriod',
|
|
77
|
+
originalError: e as Error,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static getHallBlindPeriod() {
|
|
84
|
+
return new BaseCommand('GetHallBlindPeriod', 0x23)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static setMicrophoneState(params: MultiSensorCommandTypes.SetMicrophoneStateParams) {
|
|
88
|
+
try {
|
|
89
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setMicrophoneState.parse(params)
|
|
90
|
+
const { enabled } = params
|
|
91
|
+
const enabledValue = enabled ? 1 : 0
|
|
92
|
+
return new BaseCommand('SetMicrophoneState', 0x24, decToHex(enabledValue))
|
|
93
|
+
} catch (e) {
|
|
94
|
+
if (e instanceof ZodError) {
|
|
95
|
+
throw new CustomError({
|
|
96
|
+
message: 'Zod validation error during SetMicrophoneState execution',
|
|
97
|
+
command: 'SetMicrophoneState',
|
|
98
|
+
originalError: e,
|
|
99
|
+
})
|
|
100
|
+
} else {
|
|
101
|
+
throw new CustomError({
|
|
102
|
+
message: 'Error during SetMicrophoneState execution',
|
|
103
|
+
command: 'SetMicrophoneState',
|
|
104
|
+
originalError: e as Error,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static getMicrophoneState() {
|
|
111
|
+
return new BaseCommand('GetMicrophoneState', 0x25)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static setMicrophoneSamplingPeriod(params: MultiSensorCommandTypes.SetMicrophoneSamplingPeriodParams) {
|
|
115
|
+
try {
|
|
116
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setMicrophoneSamplingPeriod.parse(params)
|
|
117
|
+
const { period } = params
|
|
118
|
+
return new BaseCommand('SetMicrophoneSamplingPeriod', 0x26, dec2hex(period))
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (e instanceof ZodError) {
|
|
121
|
+
throw new CustomError({
|
|
122
|
+
message: 'Zod validation error during SetMicrophoneSamplingPeriod execution',
|
|
123
|
+
command: 'SetMicrophoneSamplingPeriod',
|
|
124
|
+
originalError: e,
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
throw new CustomError({
|
|
128
|
+
message: 'Error during SetMicrophoneSamplingPeriod execution',
|
|
129
|
+
command: 'SetMicrophoneSamplingPeriod',
|
|
130
|
+
originalError: e as Error,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static getMicrophoneSamplingPeriod() {
|
|
137
|
+
return new BaseCommand('GetMicrophoneSamplingPeriod', 0x27)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static setGasState(params: MultiSensorCommandTypes.SetGasStateParams) {
|
|
141
|
+
try {
|
|
142
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setGasState.parse(params)
|
|
143
|
+
const { enabled } = params
|
|
144
|
+
const enabledValue = enabled ? 1 : 0
|
|
145
|
+
return new BaseCommand('SetGasState', 0x28, decToHex(enabledValue))
|
|
146
|
+
} catch (e) {
|
|
147
|
+
if (e instanceof ZodError) {
|
|
148
|
+
throw new CustomError({
|
|
149
|
+
message: 'Zod validation error during SetGasState execution',
|
|
150
|
+
command: 'SetGasState',
|
|
151
|
+
originalError: e,
|
|
152
|
+
})
|
|
153
|
+
} else {
|
|
154
|
+
throw new CustomError({
|
|
155
|
+
message: 'Error during SetGasState execution',
|
|
156
|
+
command: 'SetGasState',
|
|
157
|
+
originalError: e as Error,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static getGasState() {
|
|
164
|
+
return new BaseCommand('GetGasState', 0x29)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static setGasMeasurementPeriod(params: MultiSensorCommandTypes.SetGasMeasurementPeriodParams) {
|
|
168
|
+
try {
|
|
169
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setGasMeasurementPeriod.parse(params)
|
|
170
|
+
const { minutes } = params
|
|
171
|
+
return new BaseCommand('SetGasMeasurementPeriod', 0x2a, decToHex(minutes))
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e instanceof ZodError) {
|
|
174
|
+
throw new CustomError({
|
|
175
|
+
message: 'Zod validation error during SetGasMeasurementPeriod execution',
|
|
176
|
+
command: 'SetGasMeasurementPeriod',
|
|
177
|
+
originalError: e,
|
|
178
|
+
})
|
|
179
|
+
} else {
|
|
180
|
+
throw new CustomError({
|
|
181
|
+
message: 'Error during SetGasMeasurementPeriod execution',
|
|
182
|
+
command: 'SetGasMeasurementPeriod',
|
|
183
|
+
originalError: e as Error,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static getGasMeasurementPeriod() {
|
|
190
|
+
return new BaseCommand('GetGasMeasurementPeriod', 0x2b)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static setPirState(params: MultiSensorCommandTypes.SetPirStateParams) {
|
|
194
|
+
try {
|
|
195
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setPirState.parse(params)
|
|
196
|
+
const { enabled } = params
|
|
197
|
+
const enabledValue = enabled ? 1 : 0
|
|
198
|
+
return new BaseCommand('SetPirState', 0x2c, decToHex(enabledValue))
|
|
199
|
+
} catch (e) {
|
|
200
|
+
if (e instanceof ZodError) {
|
|
201
|
+
throw new CustomError({
|
|
202
|
+
message: 'Zod validation error during SetPirState execution',
|
|
203
|
+
command: 'SetPirState',
|
|
204
|
+
originalError: e,
|
|
205
|
+
})
|
|
206
|
+
} else {
|
|
207
|
+
throw new CustomError({
|
|
208
|
+
message: 'Error during SetPirState execution',
|
|
209
|
+
command: 'SetPirState',
|
|
210
|
+
originalError: e as Error,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static getPirState() {
|
|
217
|
+
return new BaseCommand('GetPirState', 0x2d)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static setPirActiveReportingPeriod(params: MultiSensorCommandTypes.SetPirActiveReportingPeriodParams) {
|
|
221
|
+
try {
|
|
222
|
+
DeviceCommandSchemas.MultiSensorCommandSchemas.setPirActiveReportingPeriod.parse(params)
|
|
223
|
+
const { time } = params
|
|
224
|
+
return new BaseCommand('SetPirActiveReportingPeriod', 0x2e, dec2hex(time))
|
|
225
|
+
} catch (e) {
|
|
226
|
+
if (e instanceof ZodError) {
|
|
227
|
+
throw new CustomError({
|
|
228
|
+
message: 'Zod validation error during SetPirActiveReportingPeriod execution',
|
|
229
|
+
command: 'SetPirActiveReportingPeriod',
|
|
230
|
+
originalError: e,
|
|
231
|
+
})
|
|
232
|
+
} else {
|
|
233
|
+
throw new CustomError({
|
|
234
|
+
message: 'Error during SetPirActiveReportingPeriod execution',
|
|
235
|
+
command: 'SetPirActiveReportingPeriod',
|
|
236
|
+
originalError: e as Error,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static getPirActiveReportingPeriod() {
|
|
243
|
+
return new BaseCommand('GetPirActiveReportingPeriod', 0x2f)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static getRegion() {
|
|
247
|
+
return new BaseCommand('GetRegion', 0xa4)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
static restart() {
|
|
251
|
+
return new BaseCommand('Restart', 0xa5)
|
|
252
|
+
}
|
|
253
|
+
}
|
package/src/encoders/index.ts
CHANGED
|
@@ -493,7 +493,7 @@ const VickiCommandSchemas = {
|
|
|
493
493
|
targetTemperature: z.union([
|
|
494
494
|
z.literal(0), // To disable the feature
|
|
495
495
|
z.number().min(5).max(30).multipleOf(0.1), // Temperature range 5.0-30.0°C with 0.1°C increments
|
|
496
|
-
])
|
|
496
|
+
]),
|
|
497
497
|
}),
|
|
498
498
|
setInternalAlgoTemporaryState: z.object({
|
|
499
499
|
enabled: z.boolean(), // true = enable algorithm (00), false = disable algorithm temporarily (01)
|
|
@@ -504,11 +504,11 @@ const VickiCommandSchemas = {
|
|
|
504
504
|
scaleLevel2: z.number().min(5).max(30).multipleOf(0.1),
|
|
505
505
|
scaleLevel3: z.number().min(5).max(30).multipleOf(0.1),
|
|
506
506
|
scaleLevel4: z.number().min(5).max(30).multipleOf(0.1),
|
|
507
|
-
scaleLevel5: z.number().min(5).max(30).multipleOf(0.1)
|
|
507
|
+
scaleLevel5: z.number().min(5).max(30).multipleOf(0.1),
|
|
508
508
|
}),
|
|
509
509
|
setLedIndicationDuration: z.object({
|
|
510
|
-
duration: z.number().min(0.5).max(20).multipleOf(0.5) // Duration in range [0.5:20] with 0.5s resolution
|
|
511
|
-
})
|
|
510
|
+
duration: z.number().min(0.5).max(20).multipleOf(0.5), // Duration in range [0.5:20] with 0.5s resolution
|
|
511
|
+
}),
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
export namespace VickiCommandTypes {
|
|
@@ -1377,6 +1377,74 @@ export namespace Co2PirLiteCommandTypes {
|
|
|
1377
1377
|
>
|
|
1378
1378
|
export type RestartDeviceParams = z.infer<typeof Co2PirLiteCommandSchemas.restartDevice>
|
|
1379
1379
|
}
|
|
1380
|
+
|
|
1381
|
+
/* --------------------------------------- Melissa --------------------------------------- */
|
|
1382
|
+
const MelissaCommandSchemas = {
|
|
1383
|
+
...GeneralCommandSchemas,
|
|
1384
|
+
setIrCodeRecording: z.object({
|
|
1385
|
+
time: z.number().min(0).max(255),
|
|
1386
|
+
}),
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
export namespace MelissaCommandTypes {
|
|
1390
|
+
export type SetIrCodeRecordingParams = z.infer<typeof MelissaCommandSchemas.setIrCodeRecording>
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/* --------------------------------------- MULTISENSOR COMMANDS --------------------------------------- */
|
|
1394
|
+
const MultiSensorCommandSchemas = {
|
|
1395
|
+
...GeneralCommandSchemas,
|
|
1396
|
+
setLightState: z.object({
|
|
1397
|
+
enabled: z.boolean(),
|
|
1398
|
+
}),
|
|
1399
|
+
getLightState: z.object({}),
|
|
1400
|
+
setHallState: z.object({
|
|
1401
|
+
enabled: z.boolean(),
|
|
1402
|
+
}),
|
|
1403
|
+
getHallState: z.object({}),
|
|
1404
|
+
setHallBlindPeriod: z.object({
|
|
1405
|
+
period: z.number().min(0).max(65535),
|
|
1406
|
+
}),
|
|
1407
|
+
getHallBlindPeriod: z.object({}),
|
|
1408
|
+
setMicrophoneState: z.object({
|
|
1409
|
+
enabled: z.boolean(),
|
|
1410
|
+
}),
|
|
1411
|
+
getMicrophoneState: z.object({}),
|
|
1412
|
+
setMicrophoneSamplingPeriod: z.object({
|
|
1413
|
+
period: z.number().min(0).max(65535),
|
|
1414
|
+
}),
|
|
1415
|
+
getMicrophoneSamplingPeriod: z.object({}),
|
|
1416
|
+
setGasState: z.object({
|
|
1417
|
+
enabled: z.boolean(),
|
|
1418
|
+
}),
|
|
1419
|
+
getGasState: z.object({}),
|
|
1420
|
+
setGasMeasurementPeriod: z.object({
|
|
1421
|
+
minutes: z.number().min(0).max(255),
|
|
1422
|
+
}),
|
|
1423
|
+
getGasMeasurementPeriod: z.object({}),
|
|
1424
|
+
setPirState: z.object({
|
|
1425
|
+
enabled: z.boolean(),
|
|
1426
|
+
}),
|
|
1427
|
+
getPirState: z.object({}),
|
|
1428
|
+
setPirActiveReportingPeriod: z.object({
|
|
1429
|
+
time: z.number().min(0).max(65535),
|
|
1430
|
+
}),
|
|
1431
|
+
getPirActiveReportingPeriod: z.object({}),
|
|
1432
|
+
getRegion: z.object({}),
|
|
1433
|
+
restart: z.object({}),
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
export namespace MultiSensorCommandTypes {
|
|
1437
|
+
export type SetLightStateParams = z.infer<typeof MultiSensorCommandSchemas.setLightState>
|
|
1438
|
+
export type SetHallStateParams = z.infer<typeof MultiSensorCommandSchemas.setHallState>
|
|
1439
|
+
export type SetHallBlindPeriodParams = z.infer<typeof MultiSensorCommandSchemas.setHallBlindPeriod>
|
|
1440
|
+
export type SetMicrophoneStateParams = z.infer<typeof MultiSensorCommandSchemas.setMicrophoneState>
|
|
1441
|
+
export type SetMicrophoneSamplingPeriodParams = z.infer<typeof MultiSensorCommandSchemas.setMicrophoneSamplingPeriod>
|
|
1442
|
+
export type SetGasStateParams = z.infer<typeof MultiSensorCommandSchemas.setGasState>
|
|
1443
|
+
export type SetGasMeasurementPeriodParams = z.infer<typeof MultiSensorCommandSchemas.setGasMeasurementPeriod>
|
|
1444
|
+
export type SetPirStateParams = z.infer<typeof MultiSensorCommandSchemas.setPirState>
|
|
1445
|
+
export type SetPirActiveReportingPeriodParams = z.infer<typeof MultiSensorCommandSchemas.setPirActiveReportingPeriod>
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1380
1448
|
/* --------------------------------------- EXPORT ALL SCHEMA GROUPS --------------------------------------- */
|
|
1381
1449
|
export const DeviceCommandSchemas = {
|
|
1382
1450
|
GeneralCommandSchemas,
|
|
@@ -1402,4 +1470,6 @@ export const DeviceCommandSchemas = {
|
|
|
1402
1470
|
Relay16DryCommandSchemas,
|
|
1403
1471
|
HTPirLiteCommandSchemas,
|
|
1404
1472
|
Co2PirLiteCommandSchemas,
|
|
1473
|
+
MelissaCommandSchemas,
|
|
1474
|
+
MultiSensorCommandSchemas,
|
|
1405
1475
|
}
|
|
@@ -824,23 +824,42 @@ describe('ADS payload decoder', () => {
|
|
|
824
824
|
})
|
|
825
825
|
describe('Melissa payload decoder', () => {
|
|
826
826
|
test('simple keepalive', () => {
|
|
827
|
-
expect(uplinkPayloadParser('
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
})
|
|
827
|
+
expect(uplinkPayloadParser('01027E4600', DeviceType.MelissaLorawan)).toStrictEqual({
|
|
828
|
+
sensorTemperature: 23.8,
|
|
829
|
+
relativeHumidity: 27.34,
|
|
830
|
+
isIrCodeRecordingRequested: false
|
|
831
|
+
})
|
|
833
832
|
|
|
834
833
|
})
|
|
835
834
|
test('keepalive with response of preloaded code', () => {
|
|
836
|
-
expect(uplinkPayloadParser('
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
acVoltage: 164,
|
|
835
|
+
expect(uplinkPayloadParser('06004201027E4600', DeviceType.MelissaLorawan)).toStrictEqual({
|
|
836
|
+
sensorTemperature: 23.8,
|
|
837
|
+
relativeHumidity: 27.34,
|
|
838
|
+
isIrCodeRecordingRequested: false,
|
|
841
839
|
preloadedCode: { codeAddress: 66, codeAddressRaw: '0042' }
|
|
842
840
|
})
|
|
843
841
|
})
|
|
842
|
+
test('keepalive with response of recorded code', () => {
|
|
843
|
+
expect(uplinkPayloadParser('0a0020002001027E4600', DeviceType.MelissaLorawan)).toStrictEqual({
|
|
844
|
+
sensorTemperature: 23.8,
|
|
845
|
+
relativeHumidity: 27.34,
|
|
846
|
+
isIrCodeRecordingRequested: false,
|
|
847
|
+
recordedIrInfo: { recordedIrCodeSize: 32, bytesSent: '0020' }
|
|
848
|
+
})
|
|
849
|
+
})
|
|
850
|
+
test('keepalive with response of transfered code', () => {
|
|
851
|
+
expect(uplinkPayloadParser('0b2200000063320612062e062e000606000508120004040008080005180008081100050c01027E4600', DeviceType.MelissaLorawan)).toStrictEqual({
|
|
852
|
+
sensorTemperature: 23.8,
|
|
853
|
+
relativeHumidity: 27.34,
|
|
854
|
+
isIrCodeRecordingRequested: false,
|
|
855
|
+
irCodeData: {
|
|
856
|
+
bytesCount: 32,
|
|
857
|
+
address: 0,
|
|
858
|
+
data: "0063320612062e062e000606000508120004040008080005180008081100050c"
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
})
|
|
862
|
+
|
|
844
863
|
})
|
|
845
864
|
describe('CO2PirLite payload decoder', () => {
|
|
846
865
|
test('simple keepalive', () => {
|
|
@@ -816,3 +816,11 @@ describe('HT PIR Lite Commands payload encoder', () => {
|
|
|
816
816
|
expect(commandBuilder.build('RestartDevice')).toStrictEqual(new BaseCommand('RestartDevice', 0xa5))
|
|
817
817
|
})
|
|
818
818
|
})
|
|
819
|
+
describe('Melissa Commands payload encoder', () => {
|
|
820
|
+
const commandBuilder = new CommandBuilder('melissa_lorawan')
|
|
821
|
+
test('Set IR Code Recording ', () => {
|
|
822
|
+
expect(commandBuilder.build('SetIrCodeRecording', { time: 15 })).toStrictEqual(
|
|
823
|
+
new BaseCommand('SetIrCodeRecording', 0x09, '0F'),
|
|
824
|
+
)
|
|
825
|
+
})
|
|
826
|
+
})
|