generated from homebridge/homebridge-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from martin308/martin308/server-sent-event-stream
server sent event stream
- Loading branch information
Showing
4 changed files
with
226 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { EventEmitter } from 'stream'; | ||
import { request, ClientRequest, IncomingMessage } from 'http'; | ||
import { URL } from 'url'; | ||
|
||
declare interface EventSource { | ||
on(event: 'event', listener: (arg0: object) => void): this; | ||
} | ||
|
||
class EventSource extends EventEmitter { | ||
private req: ClientRequest | undefined; | ||
|
||
constructor(private readonly url: URL) { | ||
super(); | ||
} | ||
|
||
close() { | ||
this.removeAllListeners(); | ||
|
||
if(this.req) { | ||
this.req.removeAllListeners(); | ||
this.req.destroy(); | ||
} | ||
} | ||
|
||
connect() { | ||
this.req = request(this.url, this.handleResponse.bind(this)); | ||
this.req.on('error', (err) => this.emit('error', err)); | ||
this.req.on('abort', () => this.emit('error', 'abort')); | ||
this.req.on('close', this.handleClose.bind(this)); | ||
this.req.end(); | ||
} | ||
|
||
private handleClose() { | ||
if(this.req) { | ||
this.req.removeAllListeners(); | ||
} | ||
|
||
setTimeout(this.connect.bind(this), 500); | ||
} | ||
|
||
private handleResponse(res: IncomingMessage) { | ||
res.setEncoding('utf8'); | ||
res.on('data', this.handleData.bind(this)); | ||
} | ||
|
||
private handleData(chunk: string) { | ||
try { | ||
const json = JSON.parse(chunk); | ||
this.emit('event', json); | ||
} catch (error) { | ||
this.emit('error', error); | ||
} | ||
} | ||
} | ||
|
||
export default EventSource; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import fetch, { Response } from 'node-fetch'; | ||
import { URL } from 'url'; | ||
import EventSource from './eventsource'; | ||
|
||
interface Event { | ||
evt: string; | ||
} | ||
|
||
function isAnEvent(obj: object): obj is Event { | ||
return obj && 'evt' in obj; | ||
} | ||
|
||
type JSONShade = { | ||
id: number; | ||
ptName: string; | ||
positions: { | ||
primary: number; | ||
}; | ||
}; | ||
|
||
interface MotionStoppedEvent { | ||
evt: string; | ||
id: number; | ||
currentPositions: Position; | ||
} | ||
|
||
interface MotionStartedEvent { | ||
evt: string; | ||
id: number; | ||
currentPositions: Position; | ||
targetPositions?: Position; | ||
} | ||
|
||
type Position = { | ||
primary: number; | ||
}; | ||
|
||
class Shade implements Shade { | ||
currentPositions: Position; | ||
targetPositions: Position; | ||
|
||
constructor(private readonly hub: Hub, readonly id: number, readonly name: string, position: Position) { | ||
this.currentPositions = position; | ||
this.targetPositions = position; | ||
} | ||
|
||
setTargetPosition(position: Position): void { | ||
this.hub.setShades(position, this.id); | ||
} | ||
} | ||
|
||
class Hub { | ||
private readonly events: EventSource; | ||
private readonly shades: Map<number, Shade> = new Map(); | ||
|
||
constructor(private readonly host: URL) { | ||
const events = new URL('/home/shades/events', host); | ||
this.events = new EventSource(events); | ||
this.events.on('event', this.handleEvent.bind(this)); | ||
this.events.connect(); | ||
} | ||
|
||
close() { | ||
this.events.close(); | ||
} | ||
|
||
async setShades(position: Position, ...ids: number[]): Promise<Response> { | ||
const url = new URL('/home/shades/postitions', this.host); | ||
url.search = `ids=${ids.join(',')}`; | ||
return fetch(url, { | ||
method: 'PUT', | ||
body: JSON.stringify({ positions: { primary: position } }), | ||
headers: {'Content-Type': 'application/json'}, | ||
}); | ||
} | ||
|
||
async getShades(): Promise<Array<Shade>> { | ||
const url = new URL('/home/shades', this.host); | ||
const json = await fetch(url) | ||
.then(response => response.json()) | ||
.then(response => response as Array<JSONShade>); | ||
|
||
const shades = json.map(j => { | ||
const shade = new Shade(this, j.id, j.ptName, j.positions); | ||
|
||
this.shades.set(j.id, shade); | ||
|
||
return shade; | ||
}); | ||
|
||
return shades; | ||
} | ||
|
||
private handleEvent(obj: object) { | ||
if(isAnEvent(obj)) { | ||
switch (obj.evt) { | ||
case 'motion-started': | ||
this.handleMotionStartedEvent(obj as MotionStartedEvent); | ||
break; | ||
case 'motion-stopped': | ||
this.handleMotionStoppedEvent(obj as MotionStoppedEvent); | ||
break; | ||
case 'shade-offline': | ||
case 'shade-online': | ||
case 'battery-alert': | ||
break; | ||
} | ||
} | ||
} | ||
|
||
private handleMotionStartedEvent(event: MotionStartedEvent) { | ||
const shade = this.shades.get(event.id); | ||
|
||
if(shade) { | ||
shade.currentPositions = event.currentPositions; | ||
if (event.targetPositions) { | ||
shade.targetPositions = event.targetPositions; | ||
} | ||
} | ||
} | ||
|
||
private handleMotionStoppedEvent(event: MotionStoppedEvent) { | ||
const shade = this.shades.get(event.id); | ||
|
||
if(shade) { | ||
shade.currentPositions = event.currentPositions; | ||
} | ||
} | ||
} | ||
|
||
export { Shade }; | ||
export default Hub; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters