I have a fan and a light switch controlled by TP-Link smart switch in my office. My son and I installed them so that we don't have to reach over to turn them on and off :-) Now we just say the words and Google Assistent will obey my command, until it does not. Once in a while, Goolge Assistent just don't want to listen to me and I find myself looking for the Kasa app on my phone to execute the command. So I want to see if I can integrate the TP-Link smart switches into my personal mobile app.
The tplink-cloud-api NPM module allows your to remotely control your TP-Link smartplugs, smart switches, and smartbulbs using the TP-Link cloud web service, from anywhere, without the need to be on the same wifi/lan.
A BehaviorSubject is used to store the list of devices. When the device component becomes visible, the service is contructed with a periodic timer to update
export class DeviceService implements OnDestroy {
devices$ = new BehaviorSubject<any>([]);
private minuteInterval: any;
private tplink: any;
constructor(private http: HttpClient) {
this.updateDevices().then();
this.minuteInterval = setInterval(() => {
this.updateDevices().then();
}, ONE_MINUTE);
}
ngOnDestroy(): void {
if (this.minuteInterval) {
clearInterval(this.minuteInterval);
}
}
async connect() {
if (!this.tplink) {
this.tplink = await login(TPLINK_USER, TPLINK_PASSWORD);
}
}
async updateDevices() {
await this.connect();
if (this.tplink) {
const deviceList = await this.tplink.getDeviceList();
...
this.devices$.next(deviceList;
}
}
I am seeing a lot of $ in examples, which simply indicates that variable$ is an observable.
export class DeviceComponent {
devices$ = this.deviceService.devices$;
constructor(private deviceService: DeviceService) {}
}
Subscription is also simplified with the async pipe in the template which will unsubscribe automatically, eliminated the needs for OnInit and OnDestroy. Now the component is much smaller.
<ion-item *ngFor="let device of (devices$ | async)">
<ion-icon name="{{ device.icon }}" size="large"></ion-icon>
<h5>{{ device.alias }}</h5>
</ion-item>
After watching Complex features made easy with RxJS, I want to try the refresh$ pipe approach. For example, the following code just look a bit odd, calling an async function in the constructor, therefore the Promise.then() was called to trigger the function.
constructor(private http: HttpClient) {
this.updateDevices().then();
this.minuteInterval = setInterval(() => {
this.updateDevices().then();
}, ONE_MINUTE);
}
The code I ended up looks more cryptic but I guess it is actually a common pattern in RxJS. The refresh$ below is a a manual trigger while refreshTimer$ will periodically trigger the refresh. The devices$ will get the devices from the async updateDevices() function.
export class DeviceService implements OnDestroy {
refresh$ = new BehaviorSubject<any>(null);
refreshTimer$ = timer(0, ONE_MINUTE);
devices$ = this.refresh$.pipe(
exhaustMap(() => from(this.updateDevices())),
)
private subs = new SubSink();
private tplink: any;
constructor(private http: HttpClient) {
this.subs.add(this.refreshTimer$.subscribe(this.refresh$));
}
ngOnDestroy(): void {
this.subs.unsubscribe()
}
async connect() {
if (!this.tplink) {
this.tplink = await login(TPLINK_USER, TPLINK_PASSWORD);
}
}
async updateDevices() {
await this.connect();
if (this.tplink) {
const deviceList = await this.tplink.getDeviceList();
...
return deviceList;
}
return [];
}
The component UI has a button to toggle the device on and off, and will refresh the UI by calling a refresh$.next().
async toggleDevice(name: string) {
await this.connect();
if (this.tplink) {
await this.tplink.getHS100(name).toggle();
this.refresh$.next(null);
}
}
}
In the case of the calendar service, the updateEvent$ is used instead of from(Promise), just like the tutorial video.
export class CalendarService implements OnDestroy {
refresh$ = new BehaviorSubject<any>(null);
refreshTimer$ = timer(0, ONE_MINUTE);
updateEvents$ = this.http.get(CALENDAR_URL).pipe(
map(mapEvents),
catchError(handleError)
)
events$ = this.refresh$.pipe(
exhaustMap(() => this.updateEvents$),
)
private subs = new SubSink();
constructor(private http: HttpClient) {
this.subs.add(this.refreshTimer$.subscribe(this.refresh$));
}
ngOnDestroy(): void {
this.subs.unsubscribe();
}
With this pattern, all setInterval() are replaced by the refresh$ and refreshTimer$.