Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions src/lib/asn1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
BACNetDevObjRef,
BACNetObjectID,
BACNetPropertyID,
BACNetTimestamp,
Decode,
Tag,
ObjectId,
Expand Down Expand Up @@ -614,68 +615,71 @@ export const bacappEncodeApplicationData = (
encodeApplicationNull(buffer)
break
case ApplicationTag.BOOLEAN:
encodeApplicationBoolean(buffer, value.value)
encodeApplicationBoolean(buffer, value.value as boolean)
break
case ApplicationTag.UNSIGNED_INTEGER:
encodeApplicationUnsigned(buffer, value.value)
encodeApplicationUnsigned(buffer, value.value as number)
break
case ApplicationTag.SIGNED_INTEGER:
encodeApplicationSigned(buffer, value.value)
encodeApplicationSigned(buffer, value.value as number)
break
case ApplicationTag.REAL:
encodeApplicationReal(buffer, value.value)
encodeApplicationReal(buffer, value.value as number)
break
case ApplicationTag.DOUBLE:
encodeApplicationDouble(buffer, value.value)
encodeApplicationDouble(buffer, value.value as number)
break
case ApplicationTag.OCTET_STRING:
encodeApplicationOctetString(
buffer,
value.value,
value.value as number[],
0,
value.value.length,
(value.value as number[]).length,
)
break
case ApplicationTag.CHARACTER_STRING:
encodeApplicationCharacterString(
buffer,
value.value,
value.value as string,
value.encoding,
)
break
case ApplicationTag.BIT_STRING:
encodeApplicationBitstring(buffer, value.value)
encodeApplicationBitstring(buffer, value.value as BACNetBitString)
break
case ApplicationTag.ENUMERATED:
encodeApplicationEnumerated(buffer, value.value)
encodeApplicationEnumerated(buffer, value.value as number)
break
case ApplicationTag.DATE:
encodeApplicationDate(buffer, value.value)
encodeApplicationDate(buffer, value.value as Date)
break
case ApplicationTag.TIME:
encodeApplicationTime(buffer, value.value)
encodeApplicationTime(buffer, value.value as Date)
break
case ApplicationTag.TIMESTAMP:
bacappEncodeTimestamp(buffer, value.value)
bacappEncodeTimestamp(buffer, value.value as BACNetTimestamp)
break
case ApplicationTag.DATETIME:
bacappEncodeDatetime(buffer, value.value)
bacappEncodeDatetime(buffer, value.value as Date)
break
case ApplicationTag.OBJECTIDENTIFIER:
encodeApplicationObjectId(
buffer,
value.value.type,
value.value.instance,
(value.value as BACNetObjectID).type,
(value.value as BACNetObjectID).instance,
)
break
case ApplicationTag.COV_SUBSCRIPTION:
encodeCovSubscription(buffer, value.value)
encodeCovSubscription(buffer, value.value as BACNetCovSubscription)
break
case ApplicationTag.READ_ACCESS_RESULT:
encodeReadAccessResult(buffer, value.value)
encodeReadAccessResult(buffer, value.value as BACNetReadAccess)
break
case ApplicationTag.READ_ACCESS_SPECIFICATION:
encodeReadAccessSpecification(buffer, value.value)
encodeReadAccessSpecification(
buffer,
value.value as BACNetReadAccessSpecification,
)
break
case undefined:
throw new Error(
Expand Down
40 changes: 21 additions & 19 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ export interface PropertyReference {
}

/**
* TODO: when the times comes, drop the default value for the `Tag`
* paramter to enforce strong typing throughout the entire library.
* TODO: when the time comes, drop the default value for the `Tag`
* parameter to enforce strong typing throughout the entire library.
* This would be a breaking change requiring major version bump.
*/
export interface TypedValue<
Tag extends ApplicationTag = ApplicationTag,
Expand Down Expand Up @@ -125,6 +126,7 @@ export interface BACNetDevObjRef {
/**
* TODO: when the time comes, drop the default value for the `Tag` generic
* parameter to enforce type safety everywhere throughout the library.
* This would be a breaking change requiring major version bump.
*/
export interface BACNetAppData<
Tag extends ApplicationTag = ApplicationTag,
Expand Down Expand Up @@ -152,7 +154,7 @@ export interface ApplicationTagValueTypeMap {
[ApplicationTag.SIGNED_INTEGER]: number
[ApplicationTag.REAL]: number
[ApplicationTag.DOUBLE]: number
[ApplicationTag.OCTET_STRING]: any
[ApplicationTag.OCTET_STRING]: number[]
[ApplicationTag.CHARACTER_STRING]: string
[ApplicationTag.BIT_STRING]:
| StatusFlagsBitString
Expand All @@ -170,27 +172,27 @@ export interface ApplicationTagValueTypeMap {
[ApplicationTag.DATE]: Date
[ApplicationTag.TIME]: Date
[ApplicationTag.OBJECTIDENTIFIER]: BACNetObjectID
[ApplicationTag.EMPTYLIST]: any
[ApplicationTag.WEEKNDAY]: any
[ApplicationTag.DATERANGE]: any
[ApplicationTag.DATETIME]: any
[ApplicationTag.EMPTYLIST]: unknown[]
[ApplicationTag.WEEKNDAY]: CalendarWeekDay
[ApplicationTag.DATERANGE]: CalendarDateRange
[ApplicationTag.DATETIME]: Date
[ApplicationTag.TIMESTAMP]: BACNetTimestamp
[ApplicationTag.ERROR]: any
[ApplicationTag.ERROR]: BACnetError
[ApplicationTag.DEVICE_OBJECT_PROPERTY_REFERENCE]: DeviceObjPropertyRef
[ApplicationTag.DEVICE_OBJECT_REFERENCE]: BACNetDevObjRef
[ApplicationTag.OBJECT_PROPERTY_REFERENCE]: any
[ApplicationTag.DESTINATION]: any
[ApplicationTag.OBJECT_PROPERTY_REFERENCE]: DeviceObjPropertyRef
[ApplicationTag.DESTINATION]: BACNetAddress
[ApplicationTag.RECIPIENT]: BACNetRecipient
[ApplicationTag.COV_SUBSCRIPTION]: BACNetCovSubscription
[ApplicationTag.CALENDAR_ENTRY]: any
[ApplicationTag.WEEKLY_SCHEDULE]: any
[ApplicationTag.SPECIAL_EVENT]: any
[ApplicationTag.READ_ACCESS_SPECIFICATION]: any
[ApplicationTag.READ_ACCESS_RESULT]: any
[ApplicationTag.LIGHTING_COMMAND]: any
[ApplicationTag.CONTEXT_SPECIFIC_DECODED]: any
[ApplicationTag.CONTEXT_SPECIFIC_ENCODED]: any
[ApplicationTag.LOG_RECORD]: any
[ApplicationTag.CALENDAR_ENTRY]: Calendar
[ApplicationTag.WEEKLY_SCHEDULE]: unknown
[ApplicationTag.SPECIAL_EVENT]: unknown
[ApplicationTag.READ_ACCESS_SPECIFICATION]: ReadAccessSpec
[ApplicationTag.READ_ACCESS_RESULT]: ReadAccessDecode
[ApplicationTag.LIGHTING_COMMAND]: unknown
[ApplicationTag.CONTEXT_SPECIFIC_DECODED]: unknown
[ApplicationTag.CONTEXT_SPECIFIC_ENCODED]: number[]
[ApplicationTag.LOG_RECORD]: unknown
}

export interface BACNetPropertyState {
Expand Down
109 changes: 109 additions & 0 deletions test/unit/application-data-types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import test from 'node:test'
import assert from 'node:assert'

import {
ApplicationTag,
BACNetAppData,
CalendarWeekDay,
CalendarDateRange,
} from '../../src'

test.describe('ApplicationData types', () => {
test('should correctly type OCTET_STRING as number[]', () => {
const octetStringData: BACNetAppData<ApplicationTag.OCTET_STRING> = {
type: ApplicationTag.OCTET_STRING,
value: [0x01, 0x02, 0x03, 0x04],
}

assert.strictEqual(octetStringData.type, ApplicationTag.OCTET_STRING)
assert.ok(Array.isArray(octetStringData.value))
assert.strictEqual(octetStringData.value[0], 0x01)
})

test('should correctly type BOOLEAN as boolean', () => {
const booleanData: BACNetAppData<ApplicationTag.BOOLEAN> = {
type: ApplicationTag.BOOLEAN,
value: true,
}

assert.strictEqual(booleanData.type, ApplicationTag.BOOLEAN)
assert.strictEqual(typeof booleanData.value, 'boolean')
assert.strictEqual(booleanData.value, true)
})

test('should correctly type EMPTYLIST as unknown[]', () => {
const emptyListData: BACNetAppData<ApplicationTag.EMPTYLIST> = {
type: ApplicationTag.EMPTYLIST,
value: [],
}

assert.strictEqual(emptyListData.type, ApplicationTag.EMPTYLIST)
assert.ok(Array.isArray(emptyListData.value))
assert.strictEqual(emptyListData.value.length, 0)
})

test('should correctly type WEEKNDAY as CalendarWeekDay', () => {
const weekDayData: BACNetAppData<ApplicationTag.WEEKNDAY> = {
type: ApplicationTag.WEEKNDAY,
value: {
len: 4,
month: 1,
week: 1,
wday: 1,
} as CalendarWeekDay,
}

assert.strictEqual(weekDayData.type, ApplicationTag.WEEKNDAY)
assert.strictEqual(typeof weekDayData.value, 'object')
assert.strictEqual(weekDayData.value.month, 1)
})

test('should correctly type DATERANGE as CalendarDateRange', () => {
const dateRangeData: BACNetAppData<ApplicationTag.DATERANGE> = {
type: ApplicationTag.DATERANGE,
value: {
len: 16,
startDate: {
len: 8,
value: new Date('2024-01-01'),
},
endDate: {
len: 8,
value: new Date('2024-12-31'),
},
} as CalendarDateRange,
}

assert.strictEqual(dateRangeData.type, ApplicationTag.DATERANGE)
assert.strictEqual(typeof dateRangeData.value, 'object')
assert.strictEqual(dateRangeData.value.len, 16)
})

test('should correctly type DATETIME as Date', () => {
const dateTimeData: BACNetAppData<ApplicationTag.DATETIME> = {
type: ApplicationTag.DATETIME,
value: new Date('2024-01-01T12:00:00Z'),
}

assert.strictEqual(dateTimeData.type, ApplicationTag.DATETIME)
assert.ok(dateTimeData.value instanceof Date)
assert.strictEqual(dateTimeData.value.getFullYear(), 2024)
})

test('should allow complex structures with unknown for untyped entries', () => {
const complexData: BACNetAppData<ApplicationTag.WEEKLY_SCHEDULE> = {
type: ApplicationTag.WEEKLY_SCHEDULE,
value: {
// This can be any complex structure for now
scheduleId: 123,
entries: [
{ day: 'Monday', schedule: [] },
{ day: 'Tuesday', schedule: [] },
],
} as unknown,
}

assert.strictEqual(complexData.type, ApplicationTag.WEEKLY_SCHEDULE)
assert.strictEqual(typeof complexData.value, 'object')
})
})