@@ -507,7 +507,7 @@ const definitions: Definition[] = [
507507 zigbeeModel : [ 'ptvo.switch' ] ,
508508 model : 'ptvo.switch' ,
509509 vendor : 'Custom devices (DiY)' ,
510- description : '[Multi-channel relay switch ](https://ptvo.info/zigbee-switch- configurable-firmware-router-199 /)' ,
510+ description : '[Multi-functional device ](https://ptvo.info/zigbee-configurable-firmware-features /)' ,
511511 fromZigbee : [ fz . on_off , fz . ptvo_multistate_action , legacy . fz . ptvo_switch_buttons , fz . ptvo_switch_uart ,
512512 fz . ptvo_switch_analog_input , fz . brightness , fz . ignore_basic_report , fz . temperature ,
513513 fzLocal . humidity2 , fzLocal . pressure2 , fzLocal . illuminance2 ] ,
@@ -516,9 +516,8 @@ const definitions: Definition[] = [
516516 const expose : Expose [ ] = [ ] ;
517517 const exposeDeviceOptions : KeyValue = { } ;
518518 const deviceConfig = ptvoGetMetaOption ( device , 'device_config' , '' ) ;
519-
520519 if ( deviceConfig === '' ) {
521- if ( ( device != null ) && device . endpoints ) {
520+ if ( ( device != null ) && device . endpoints ) {
522521 for ( const endpoint of device . endpoints ) {
523522 const exposeEpOptions : KeyValue = { } ;
524523 ptvoAddStandardExposes ( endpoint , expose , exposeEpOptions , exposeDeviceOptions ) ;
@@ -533,39 +532,119 @@ const definitions: Definition[] = [
533532 }
534533 }
535534 } else {
536- for ( let i = 0 ; i < deviceConfig . length ; i ++ ) {
537- const epConfig = deviceConfig . charCodeAt ( i ) ;
538- if ( epConfig <= 0x20 ) {
535+ // device configuration description from a device
536+ const deviceConfigArray = deviceConfig . split ( / [ \r \n ] + / ) ;
537+ let prevEp = - 1 ;
538+ for ( let i = 0 ; i < deviceConfigArray . length ; i ++ ) {
539+ const epConfig = deviceConfigArray [ i ] ;
540+ const epId = parseInt ( epConfig . substr ( 0 , 1 ) , 16 ) ;
541+ if ( epId <= 0 ) {
539542 continue ;
540543 }
541- const epId = i + 1 ;
542544 const epName = `l${ epId } ` ;
545+ const epValueAccessRights = epConfig . substr ( 1 , 1 ) ;
546+ const epStateType = ( ( epValueAccessRights === 'W' ) || ( epValueAccessRights === '*' ) ) ?
547+ ea . STATE_SET : ea . STATE ;
548+ let valueConfig = epConfig . substr ( 2 ) ;
549+ valueConfig = valueConfig . split ( ',' ) ;
550+ let valueId = ( valueConfig [ 0 ] ) ? valueConfig [ 0 ] : '' ;
551+ let valueDescription = ( valueConfig [ 1 ] ) ? valueConfig [ 1 ] : '' ;
552+ let valueUnit = ( valueConfig [ 2 ] !== undefined ) ? valueConfig [ 2 ] : '' ;
543553 const exposeEpOptions : KeyValue = { } ;
544- if ( ( epConfig & 0x01 ) != 0 ) {
545- // GPIO input
546- exposeEpOptions [ 'expose_action' ] = true ;
547- }
548- if ( ( epConfig & 0x02 ) != 0 ) {
549- // GPIO output
554+ if ( valueId === '*' ) {
555+ // GPIO output (Generic)
550556 exposeEpOptions [ 'exposed_onoff' ] = true ;
551557 expose . push ( e . switch ( ) . withEndpoint ( epName ) ) ;
552- }
553- if ( ( epConfig & 0x04 ) != 0 ) {
554- // reportable analog value
555- exposeEpOptions [ 'exposed_analog' ] = true ;
556- expose . push ( e . numeric ( epName , ea . STATE ) . withDescription ( 'State or sensor value' ) ) ;
557- } else if ( ( epConfig & 0x08 ) != 0 ) {
558- // readable analog value
558+ } else if ( valueId === '#' ) {
559+ // GPIO state (contact, gas, noise, occupancy, presence, smoke, sos, tamper, vibration, water leak)
560+ exposeEpOptions [ 'exposed_onoff' ] = true ;
561+ let exposeObj = undefined ;
562+ switch ( valueDescription ) {
563+ case 'g' : exposeObj = e . gas ( ) ; break ;
564+ case 'n' : exposeObj = e . noise_detected ( ) ; break ;
565+ case 'o' : exposeObj = e . occupancy ( ) ; break ;
566+ case 'p' : exposeObj = e . presence ( ) ; break ;
567+ case 'm' : exposeObj = e . smoke ( ) ; break ;
568+ case 's' : exposeObj = e . sos ( ) ; break ;
569+ case 't' : exposeObj = e . tamper ( ) ; break ;
570+ case 'v' : exposeObj = e . vibration ( ) ; break ;
571+ case 'w' : exposeObj = e . water_leak ( ) ; break ;
572+ default : // 'c'
573+ exposeObj = e . contact ( ) ;
574+ }
575+ expose . push ( exposeObj . withEndpoint ( epName ) ) ;
576+ } else if ( valueConfig ) {
577+ let valueName = undefined ; // name in Z2M
578+ let valueNumIndex = undefined ;
579+ const idxPos = valueId . search ( / ( \d + ) $ / ) ;
580+ if ( valueId . startsWith ( 'mcpm' ) || valueId . startsWith ( 'ncpm' ) ) {
581+ const num = parseInt ( valueId . substr ( 4 , 1 ) , 16 ) ;
582+ valueName = valueId . substr ( 0 , 4 ) + num ;
583+ } else if ( idxPos >= 0 ) {
584+ valueNumIndex = valueId . substr ( idxPos ) ;
585+ valueId = valueId . substr ( 0 , idxPos ) ;
586+ }
587+
588+ // analog value
589+ // 1: value name (if empty, use the EP name)
590+ // 2: description (if empty or undefined, use the value name)
591+ // 3: units (if undefined, use the key name)
592+ const infoLookup : KeyValue = {
593+ 'C' : 'temperature' ,
594+ '%' : 'humidity' ,
595+ 'm' : 'altitude' ,
596+ 'Pa' : 'pressure' ,
597+ 'ppm' : 'quality' ,
598+ 'psize' : 'particle_size' ,
599+ 'V' : 'voltage' ,
600+ 'A' : 'current' ,
601+ 'Wh' : 'energy' ,
602+ 'W' : 'power' ,
603+ 'Hz' : 'frequency' ,
604+ 'pf' : 'power_factor' ,
605+ 'lx' : 'illuminance_lux' ,
606+ } ;
607+ valueName = ( valueName !== undefined ) ? valueName : infoLookup [ valueId ] ;
608+
609+ if ( ( valueName === undefined ) && valueNumIndex ) {
610+ valueName = 'val' + valueNumIndex ;
611+ }
612+
613+ valueName = ( valueName === undefined ) ? epName : valueName + '_' + epName ;
614+
615+ if ( ( valueDescription === undefined ) || ( valueDescription === '' ) ) {
616+ if ( infoLookup [ valueId ] ) {
617+ valueDescription = infoLookup [ valueId ] ;
618+ valueDescription = valueDescription . replace ( '_' , ' ' ) ;
619+ } else {
620+ valueDescription = 'Sensor value' ;
621+ }
622+ }
623+ valueDescription = valueDescription . substring ( 0 , 1 ) . toUpperCase ( ) +
624+ valueDescription . substring ( 1 ) ;
625+
626+ if ( valueNumIndex ) {
627+ valueDescription = valueDescription + ' ' + valueNumIndex ;
628+ }
629+
630+ if ( ( ( valueUnit === undefined ) || ( valueUnit === '' ) ) && infoLookup [ valueId ] ) {
631+ valueUnit = valueId ;
632+ }
633+
559634 exposeEpOptions [ 'exposed_analog' ] = true ;
560- expose . push ( e . numeric ( epName , ea . STATE_SET )
635+ expose . push ( e . numeric ( valueName , epStateType )
561636 . withValueMin ( - 9999999 ) . withValueMax ( 9999999 ) . withValueStep ( 1 )
562- . withDescription ( 'State or sensor value' ) ) ;
637+ . withDescription ( valueDescription )
638+ . withUnit ( valueUnit ) ) ;
563639 }
564640 const endpoint = device . getEndpoint ( epId ) ;
565641 if ( ! endpoint ) {
566642 continue ;
567643 }
568- ptvoAddStandardExposes ( endpoint , expose , exposeEpOptions , exposeDeviceOptions ) ;
644+ if ( prevEp !== epId ) {
645+ prevEp = epId ;
646+ ptvoAddStandardExposes ( endpoint , expose , exposeEpOptions , exposeDeviceOptions ) ;
647+ }
569648 }
570649 }
571650 if ( exposeDeviceOptions [ 'expose_action' ] ) {
@@ -583,7 +662,7 @@ const definitions: Definition[] = [
583662 const endpointList : any = [ ] ;
584663 const deviceConfig = ptvoGetMetaOption ( device , 'device_config' , '' ) ;
585664 if ( deviceConfig === '' ) {
586- if ( ( device != null ) && device . endpoints ) {
665+ if ( ( device != null ) && device . endpoints ) {
587666 for ( const endpoint of device . endpoints ) {
588667 const epId = endpoint . ID ;
589668 const epName = `l${ epId } ` ;
@@ -614,8 +693,14 @@ const definitions: Definition[] = [
614693 if ( device != null ) {
615694 const controlEp = device . getEndpoint ( 1 ) ;
616695 if ( controlEp != null ) {
617- ptvoSetMetaOption ( device , 'device_config' , ( await controlEp . read ( 'genBasic' , [ 'locationDesc' ] ) ) . locationDesc ) ;
618- device . save ( ) ;
696+ try {
697+ let deviceConfig = await controlEp . read ( 'genBasic' , [ 32768 ] ) ;
698+ if ( deviceConfig ) {
699+ deviceConfig = deviceConfig [ '32768' ] ;
700+ ptvoSetMetaOption ( device , 'device_config' , deviceConfig ) ;
701+ device . save ( ) ;
702+ }
703+ } catch ( err ) { /* do nothing */ }
619704 }
620705 }
621706 } ,
0 commit comments