import { EventEmitter } from "events" ; import Adapter , { Params } from "@sblendid/adapter-node" ; import Service from "./service" ; export interface Converter < T = Buffer > { uuid : CUUID ; decode ? : ( value : Buffer ) => Promish < T > ; encode ? : ( value : T ) => Promish < Buffer > ; } export interface Properties { read ? : boolean ; write ? : boolean ; notify ? : boolean ; } export interface Options < C extends MaybeConverter > { properties ? : Properties ; converter ? : C ; } export type Value < C > = C extends Converter < infer V > ? V : Buffer ; export type Listener < C > = ( value : Value < C > ) => Promish < void > ; export type MaybeConverter = Converter < any > | undefined ; export default class Characteristic < C extends MaybeConverter = undefined > { public uuid : CUUID ; public service : Service < any > ; public properties : Properties ; private eventEmitter = new EventEmitter ( ) ; private converter ? : C ; constructor ( uuid , service , options = { } ) { this . uuid = uuid ; this . service = service ; this . properties = options . properties || { } ; this . converter = options . converter ; } public async read ( ) { const [ puuid , suuid , uuid ] = this . getUuids ( ) ; const buffer = await this . getAdapter ( ) . read ( puuid , suuid , uuid ) ; return await this . decode ( buffer ) ; } public async write ( value , withoutResponse ? ) { const [ puuid , suuid , uuid ] = this . getUuids ( ) ; const buffer = await this . encode ( value ) ; await this . getAdapter ( ) . write ( puuid , suuid , uuid , buffer , withoutResponse ) ; } public async on ( event , listener ) { this . eventEmitter . on ( event , listener ) ; const isFirstListener = this . eventEmitter . listenerCount ( "notify" ) === 1 ; if ( isFirstListener ) await this . startNotifing ( ) ; } public async off ( event , listener ) { this . eventEmitter . off ( event , listener ) ; const wasLastListener = this . eventEmitter . listenerCount ( "notify" ) === 0 ; if ( wasLastListener ) await this . stopNotifing ( ) ; } private async decode ( buffer ) { const error = "Cannot read using a converter without a decode method" ; if ( ! this . converter ) return buffer as Value < C > ; if ( ! this . converter . decode ) throw new Error ( error ) ; return await this . converter . decode ( buffer ) ; } private async encode ( value ) { const error = "Cannot write using a converter without an encode method" ; if ( ! this . converter ) return value ; if ( ! this . converter . encode ) throw new Error ( error ) ; return await this . converter . encode ( value ) ; } private async startNotifing ( ) { const [ puuid , suuid , uuid ] = this . getUuids ( ) ; const error = `Failed to turn on notifications for ${ uuid }` ; const adapter = this . getAdapter ( ) ; adapter . on ( "read" , this . onNotify . bind ( this ) ) ; const notify = await adapter . notify ( puuid , suuid , uuid , true ) ; if ( notify !== true ) throw new Error ( error ) ; } private async stopNotifing ( ) { const [ puuid , suuid , uuid ] = this . getUuids ( ) ; const error = `Failed to turn off notifications for ${ uuid }` ; const adapter = this . getAdapter ( ) ; adapter . off ( "read" , this . onNotify . bind ( this ) ) ; const notify = await adapter . notify ( puuid , suuid , uuid , false ) ; if ( notify !== false ) throw new Error ( error ) ; } private async onNotify ( ... params ) { const [ puuid , suuid , cuuid , data , isNotification ] = params ; if ( ! isNotification || ! this . isThis ( puuid , suuid , cuuid ) ) return ; this . eventEmitter . emit ( "notify" , await this . decode ( data ) ) ; } private getAdapter ( ) { return this . service . peripheral . adapter ; } private getUuids ( ) { return [ this . service . peripheral . uuid , this . service . uuid , this . uuid ] ; } private isThis ( puuid , suuid , cuuid ) { return this . getUuids ( ) . every ( ( v , i ) => v === [ puuid , suuid , cuuid ] [ i ] ) ; } }