@@ -71,6 +71,12 @@ const humidityTestCases: TestCase[] = [
7171 // AMS number-first with ht suffix (ams_1_ht_humidity)
7272 { name : 'AMS HT number-first suffix' , entityId : 'sensor.h2c_ams_1_ht_humidity' , expected : '128' } ,
7373
74+ // Compact AMS model naming (H2D: ams2_1_humidity, amsht_1_humidity)
75+ { name : 'Compact AMS model H2D unit 1' , entityId : 'sensor.h2d_ams2_1_humidity' , expected : '1' } ,
76+ { name : 'Compact AMS model H2D unit 2' , entityId : 'sensor.h2d_ams2_2_humidity' , expected : '2' } ,
77+ { name : 'Compact AMS HT H2D unit 1' , entityId : 'sensor.h2d_amsht_1_humidity' , expected : '128' } ,
78+ { name : 'Compact AMS HT H2D unit 2' , entityId : 'sensor.h2d_amsht_2_humidity' , expected : '129' } ,
79+
7480 // Compact HT naming without _ams_ prefix (e.g., sensor.h2c_ht1_humidity)
7581 { name : 'Compact HT 1 English' , entityId : 'sensor.h2c_ht1_humidity' , expected : '128' } ,
7682 { name : 'Compact HT 2 English' , entityId : 'sensor.h2c_ht2_humidity' , expected : '129' } ,
@@ -133,6 +139,11 @@ const trayTestCases: TrayTestCase[] = [
133139 // AMS number-first with ht suffix (ams_1_ht_tray_N)
134140 { name : 'AMS HT number-first suffix Tray 1' , entityId : 'sensor.h2c_ams_1_ht_tray_1' , expected : { amsNumber : '128' , trayNumber : 1 } } ,
135141
142+ // Compact AMS model tray naming (H2D: ams2_1_tray_1, amsht_1_tray_1)
143+ { name : 'Compact AMS model H2D Tray 1' , entityId : 'sensor.h2d_ams2_1_tray_1' , expected : { amsNumber : '1' , trayNumber : 1 } } ,
144+ { name : 'Compact AMS model H2D Tray 4' , entityId : 'sensor.h2d_ams2_1_tray_4' , expected : { amsNumber : '1' , trayNumber : 4 } } ,
145+ { name : 'Compact AMS HT H2D Tray 1' , entityId : 'sensor.h2d_amsht_1_tray_1' , expected : { amsNumber : '128' , trayNumber : 1 } } ,
146+
136147 // Compact HT naming without _ams_ prefix (e.g., sensor.h2c_ht1_tray_1)
137148 { name : 'Compact HT 1 Tray 1' , entityId : 'sensor.h2c_ht1_tray_1' , expected : { amsNumber : '128' , trayNumber : 1 } } ,
138149 { name : 'Compact HT 1 Slot 2 German' , entityId : 'sensor.h2c_ht1_slot_2' , expected : { amsNumber : '128' , trayNumber : 2 } } ,
@@ -176,6 +187,8 @@ const buildAmsPatternTestCases: BuildPatternTestCase[] = [
176187 { name : 'AMS Lite match' , prefix : 'a1_mini' , entityId : 'sensor.a1_mini_ams_lite_humidity' , shouldMatch : true , expectedAmsNumber : 'lite' } ,
177188 { name : 'Compact HT match' , prefix : 'h2c' , entityId : 'sensor.h2c_ht1_humidity' , shouldMatch : true , expectedAmsNumber : '128' } ,
178189 { name : 'Compact HT 2 match' , prefix : 'h2c' , entityId : 'sensor.h2c_ht2_humidity' , shouldMatch : true , expectedAmsNumber : '129' } ,
190+ { name : 'Compact AMS model match' , prefix : 'h2d' , entityId : 'sensor.h2d_ams2_1_humidity' , shouldMatch : true , expectedAmsNumber : '1' } ,
191+ { name : 'Compact AMS HT match' , prefix : 'h2d' , entityId : 'sensor.h2d_amsht_1_humidity' , shouldMatch : true , expectedAmsNumber : '128' } ,
179192 { name : 'Wrong prefix no match' , prefix : 'x1c' , entityId : 'sensor.p1s_ams_1_humidity' , shouldMatch : false } ,
180193] ;
181194
@@ -206,6 +219,9 @@ const buildTrayPatternTestCases: BuildTrayPatternTestCase[] = [
206219 // Compact HT naming (ht1_tray_N without _ams_)
207220 { name : 'Compact HT tray match' , prefix : 'h2c' , amsNumber : '128' , trayNum : 1 , entityId : 'sensor.h2c_ht1_tray_1' , shouldMatch : true } ,
208221 { name : 'Compact HT slot match' , prefix : 'h2c' , amsNumber : '128' , trayNum : 2 , entityId : 'sensor.h2c_ht1_slot_2' , shouldMatch : true } ,
222+ // Compact AMS model naming (ams2_1_tray_N, amsht_1_tray_N)
223+ { name : 'Compact AMS model tray match' , prefix : 'h2d' , amsNumber : '1' , trayNum : 1 , entityId : 'sensor.h2d_ams2_1_tray_1' , shouldMatch : true } ,
224+ { name : 'Compact AMS HT tray match' , prefix : 'h2d' , amsNumber : '128' , trayNum : 1 , entityId : 'sensor.h2d_amsht_1_tray_1' , shouldMatch : true } ,
209225 // AMS HT should NOT match amsNumber=1
210226 { name : 'AMS HT should not match amsNumber=1' , prefix : 'h2c' , amsNumber : '1' , trayNum : 1 , entityId : 'sensor.h2c_ams_ht_1_tray_1' , shouldMatch : false } ,
211227 { name : 'AMS Lite no number match' , prefix : 'schiller' , amsNumber : '1' , trayNum : 1 , entityId : 'sensor.schiller_ams_tray_1' , shouldMatch : true } ,
@@ -351,8 +367,16 @@ for (const tc of buildAmsPatternTestCases) {
351367 // Group 1 = number (number-first), Group 2 = number (type-first pro)
352368 // Group 3 = "lite" or "ht" (standalone), Group 4 = number (type-first ht)
353369 // Group 5 = number (compact ht without _ams_)
370+ // Group 6 = number (compact AMS model: ams\d+_N_)
371+ // Group 7 = number (compact AMS HT: amsht_N_)
354372 let amsNum : string ;
355- if ( match [ 5 ] ) {
373+ if ( match [ 6 ] ) {
374+ // Compact AMS model: use number directly
375+ amsNum = match [ 6 ] ;
376+ } else if ( match [ 7 ] ) {
377+ // Compact AMS HT: offset by 127
378+ amsNum = String ( 127 + parseInt ( match [ 7 ] , 10 ) ) ;
379+ } else if ( match [ 5 ] ) {
356380 // Compact HT: offset by 127
357381 amsNum = String ( 127 + parseInt ( match [ 5 ] , 10 ) ) ;
358382 } else if ( match [ 4 ] ) {
@@ -570,6 +594,8 @@ const externalSpoolTestCases: ExternalSpoolTestCase[] = [
570594 { name : 'H2C English numbered spool 2' , entityId : 'sensor.h2c_externalspool2_external_spool' , shouldMatch : true } ,
571595 { name : 'H2C German numbered spool 2' , entityId : 'sensor.h2c_externalspool2_externe_spule' , shouldMatch : true } ,
572596 { name : 'H2C underscore numbered spool 2' , entityId : 'sensor.h2c_external_spool_2_bobina_esterna' , shouldMatch : true } ,
597+ // H2D external spool with underscore+digit (externalspool_1_)
598+ { name : 'H2D externalspool_1 English' , entityId : 'sensor.h2d_externalspool_1_external_spool' , shouldMatch : true } ,
573599 // Edge cases - should NOT match
574600 { name : 'Invalid - binary sensor' , entityId : 'binary_sensor.x1c_external_spool_actief' , shouldMatch : false } ,
575601 { name : 'Invalid - no external' , entityId : 'sensor.x1c_spool' , shouldMatch : false } ,
@@ -652,6 +678,10 @@ test('getExternalSpoolIndex: English external_spool returns 1', () => {
652678 assertEqual ( getExternalSpoolIndex ( 'sensor.x1c_external_spool' ) , 1 ) ;
653679} ) ;
654680
681+ test ( 'getExternalSpoolIndex: H2D externalspool_1 returns 1' , ( ) => {
682+ assertEqual ( getExternalSpoolIndex ( 'sensor.h2d_externalspool_1_external_spool' ) , 1 ) ;
683+ } ) ;
684+
655685// =============================================================================
656686// H2C Multi-AMS + AMS HT Collision Tests (GitHub Issue #35)
657687// =============================================================================
@@ -735,6 +765,116 @@ test('stogs H2C: Compact HT does not collide with regular AMS', () => {
735765 if ( ams1 === compactHt ) throw new Error ( 'Regular AMS and compact HT should have different numbers' ) ;
736766} ) ;
737767
768+ // =============================================================================
769+ // H2D Compact AMS Entity Naming (GitHub Issues #45, #47)
770+ // =============================================================================
771+
772+ console . log ( '\n=== H2D Compact AMS Entity Naming (GitHub Issues #45, #47) ===\n' ) ;
773+
774+ // nickangers' H2D entity list from issue #45
775+ const h2dEntities = [
776+ 'sensor.h2d_ams2_1_humidity' ,
777+ 'sensor.h2d_ams2_1_tray_1' ,
778+ 'sensor.h2d_ams2_1_tray_2' ,
779+ 'sensor.h2d_ams2_1_tray_3' ,
780+ 'sensor.h2d_ams2_1_tray_4' ,
781+ 'sensor.h2d_amsht_1_humidity' ,
782+ 'sensor.h2d_amsht_1_tray_1' ,
783+ 'sensor.h2d_amsht_1_tray_2' ,
784+ 'sensor.h2d_amsht_1_tray_3' ,
785+ 'sensor.h2d_amsht_1_tray_4' ,
786+ 'sensor.h2d_externalspool_1_external_spool' ,
787+ ] ;
788+
789+ test ( 'H2D: Compact AMS 2 humidity detected as AMS 1' , ( ) => {
790+ const result = matchAmsHumidityEntity ( 'sensor.h2d_ams2_1_humidity' ) ;
791+ assertEqual ( result , '1' ) ;
792+ } ) ;
793+
794+ test ( 'H2D: Compact AMS HT humidity detected as AMS 128' , ( ) => {
795+ const result = matchAmsHumidityEntity ( 'sensor.h2d_amsht_1_humidity' ) ;
796+ assertEqual ( result , '128' ) ;
797+ } ) ;
798+
799+ test ( 'H2D: All 4 compact AMS 2 trays detected' , ( ) => {
800+ for ( let i = 1 ; i <= 4 ; i ++ ) {
801+ const trayResult = matchTrayEntity ( `sensor.h2d_ams2_1_tray_${ i } ` ) ;
802+ if ( ! trayResult ) throw new Error ( `Tray ${ i } not detected` ) ;
803+ if ( trayResult . amsNumber !== '1' ) throw new Error ( `Expected AMS 1, got ${ trayResult . amsNumber } ` ) ;
804+ if ( trayResult . trayNumber !== i ) throw new Error ( `Expected tray ${ i } , got ${ trayResult . trayNumber } ` ) ;
805+ }
806+ } ) ;
807+
808+ test ( 'H2D: All 4 compact AMS HT trays detected' , ( ) => {
809+ for ( let i = 1 ; i <= 4 ; i ++ ) {
810+ const trayResult = matchTrayEntity ( `sensor.h2d_amsht_1_tray_${ i } ` ) ;
811+ if ( ! trayResult ) throw new Error ( `Tray ${ i } not detected` ) ;
812+ if ( trayResult . amsNumber !== '128' ) throw new Error ( `Expected AMS 128, got ${ trayResult . amsNumber } ` ) ;
813+ if ( trayResult . trayNumber !== i ) throw new Error ( `Expected tray ${ i } , got ${ trayResult . trayNumber } ` ) ;
814+ }
815+ } ) ;
816+
817+ test ( 'H2D: External spool with underscore+digit detected' , ( ) => {
818+ const result = matchExternalSpoolEntity ( 'sensor.h2d_externalspool_1_external_spool' ) ;
819+ assertEqual ( result , true ) ;
820+ } ) ;
821+
822+ test ( 'H2D: External spool index extracted correctly' , ( ) => {
823+ assertEqual ( getExternalSpoolIndex ( 'sensor.h2d_externalspool_1_external_spool' ) , 1 ) ;
824+ } ) ;
825+
826+ test ( 'H2D: buildAmsPattern matches compact AMS 2' , ( ) => {
827+ const pattern = buildAmsPattern ( 'h2d' ) ;
828+ const match = 'sensor.h2d_ams2_1_humidity' . match ( pattern ) ;
829+ if ( ! match ) throw new Error ( 'buildAmsPattern should match compact AMS model' ) ;
830+ } ) ;
831+
832+ test ( 'H2D: buildAmsPattern matches compact AMS HT' , ( ) => {
833+ const pattern = buildAmsPattern ( 'h2d' ) ;
834+ const match = 'sensor.h2d_amsht_1_humidity' . match ( pattern ) ;
835+ if ( ! match ) throw new Error ( 'buildAmsPattern should match compact AMS HT' ) ;
836+ } ) ;
837+
838+ test ( 'H2D: buildTrayPattern matches compact AMS 2 tray' , ( ) => {
839+ const pattern = buildTrayPattern ( 'h2d' , '1' , 1 ) ;
840+ const match = 'sensor.h2d_ams2_1_tray_1' . match ( pattern ) ;
841+ if ( ! match ) throw new Error ( 'buildTrayPattern should match compact AMS model tray' ) ;
842+ } ) ;
843+
844+ test ( 'H2D: buildTrayPattern matches compact AMS HT tray' , ( ) => {
845+ const pattern = buildTrayPattern ( 'h2d' , '128' , 1 ) ;
846+ const match = 'sensor.h2d_amsht_1_tray_1' . match ( pattern ) ;
847+ if ( ! match ) throw new Error ( 'buildTrayPattern should match compact AMS HT tray' ) ;
848+ } ) ;
849+
850+ test ( 'H2D: buildExternalSpoolPattern matches H2D format' , ( ) => {
851+ const pattern = buildExternalSpoolPattern ( 'h2d' ) ;
852+ const match = 'sensor.h2d_externalspool_1_external_spool' . match ( pattern ) ;
853+ if ( ! match ) throw new Error ( 'buildExternalSpoolPattern should match H2D external spool' ) ;
854+ } ) ;
855+
856+ test ( 'H2D: Compact AMS and AMS HT do not collide' , ( ) => {
857+ const ams = matchAmsHumidityEntity ( 'sensor.h2d_ams2_1_humidity' ) ;
858+ const ht = matchAmsHumidityEntity ( 'sensor.h2d_amsht_1_humidity' ) ;
859+ assertEqual ( ams , '1' ) ;
860+ assertEqual ( ht , '128' ) ;
861+ if ( ams === ht ) throw new Error ( 'Compact AMS and AMS HT should have different numbers' ) ;
862+ } ) ;
863+
864+ test ( 'H2D: All entities from issue #45 are discovered' , ( ) => {
865+ let humidityCount = 0 ;
866+ let trayCount = 0 ;
867+ let externalCount = 0 ;
868+ for ( const entity of h2dEntities ) {
869+ if ( matchAmsHumidityEntity ( entity ) ) humidityCount ++ ;
870+ if ( matchTrayEntity ( entity ) ) trayCount ++ ;
871+ if ( matchExternalSpoolEntity ( entity ) ) externalCount ++ ;
872+ }
873+ assertEqual ( humidityCount , 2 ) ; // ams2 + amsht
874+ assertEqual ( trayCount , 8 ) ; // 4 ams2 trays + 4 amsht trays
875+ assertEqual ( externalCount , 1 ) ;
876+ } ) ;
877+
738878// =============================================================================
739879// Summary
740880// =============================================================================
0 commit comments