Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added device event subscription #2

Merged
merged 8 commits into from
Oct 4, 2018
Prev Previous commit
Next Next commit
Fixed event worker messaging by adding dedicated worker.
  • Loading branch information
Marko Heyns committed Sep 25, 2018
commit ebe5e3b5dfa930c88ef23b81118b1f1b748321d2
4 changes: 2 additions & 2 deletions demo/app/main-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ export class HelloWorldModel extends Observable {
this.set(HelloWorldModel.SUBSCRIBE_BUTTON_KEY, this.subscribed ? "Unsubscribe" : "Subscribe to Events");
if (this.subscribed) {
this.selectedDevice.subscribe(PARTICLE_EVENT_NAME, (data) => {
console.log(`selectedDevice.subscribe eventhandler, eventdata: ${data}`);
console.log(`selectedDevice.subscribe activity, eventdata: ${data}`);
});
} else {
this.selectedDevice.unsubscribe();
}
}

startwizard(): void {
console.log('start wizard tapped');
this.particle.startDeviceSetupWizard((result) => {
Expand Down
2 changes: 1 addition & 1 deletion src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nativescript-particle",
"version": "1.1.1",
"version": "1.1.0",
"description": "Control your Particle.io devices from a NativeScript app!",
"main": "particle",
"typings": "index.d.ts",
Expand Down
195 changes: 195 additions & 0 deletions src/particle-event-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
require("globals"); // necessary to bootstrap tns modules on the new thread
import {
TNSParticleDevice,
TNSParticleDeviceVariable,
getDeviceType,
TNSParticleLoginOptions,
TNSParticleDeviceType
} from "./particle.common";

// declare const io: any;
const ParticleCloudSDK = io.particle.android.sdk.cloud.ParticleCloudSDK;
declare module io {
export module particle {
export module android {
export module sdk {
export module cloud {
export class ParticleCloudSDK {
public static class: java.lang.Class<io.particle.android.sdk.cloud.ParticleCloudSDK>;
public static getCloud(): any;
// public static initWithOauthCredentialsProvider(param0: globalAndroid.content.Context, param1: io.particle.android.sdk.cloud.ApiFactory.OauthBasicAuthCredentialsProvider): void;
public static init(param0: globalAndroid.content.Context): void;
}
export class ParticleEvent {
public static class: java.lang.Class<io.particle.android.sdk.cloud.ParticleEvent>;
public deviceId: string;
public dataPayload: string;
public publishedAt: java.util.Date;
public timeToLive: number;
public constructor(param0: string, param1: string, param2: java.util.Date, param3: number);
}
export class ParticleEventHandler {
public static class: java.lang.Class<io.particle.android.sdk.cloud.ParticleEventHandler>;
/**
* Constructs a new instance of the io.particle.android.sdk.cloud.ParticleEventHandler interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
*/
public constructor(implementation: {
onEventError(param0: java.lang.Exception): void;
onEvent(param0: string, param1: io.particle.android.sdk.cloud.ParticleEvent): void;
});
public constructor();
public onEventError(param0: java.lang.Exception): void;
public onEvent(param0: string, param1: io.particle.android.sdk.cloud.ParticleEvent): void;
}
}
}
}
}
}

let cachedDevices: Array<MyTNSParticleDevice>;

class MyTNSParticleDevice implements TNSParticleDevice {
id: string;
name: string;
status: string;
type: TNSParticleDeviceType;
functions: Array<string>;
variables: Array<TNSParticleDeviceVariable>;
eventIds: string[];

constructor(public particleDevice: any) {
this.id = particleDevice.getID();
this.name = particleDevice.getName();
this.status = particleDevice.getStatus();
this.type = getDeviceType(particleDevice.getProductID());
this.functions = toJsArray(particleDevice.getFunctions());
this.variables = toJsonVariables(particleDevice.getVariables());
this.eventIds = [];
}

getVariable(name: string): Promise<any> {
return new Promise<any>((resolve, reject) => {
try {
const result: any = this.particleDevice.getVariable(name);
const className = result.getClass ? result.getClass().getName() : "default";
switch (className) {
case "java.lang.Integer":
case "java.lang.Long":
case "java.lang.Double":
resolve(Number(String(result)));
break;
default:
resolve(String(result));
}
} catch (e) {
reject(e.nativeException.getBestMessage());
}
});
}

callFunction(name: string, ...args): Promise<number> {
return new Promise<number>((resolve, reject) => {
try {
resolve(this.particleDevice.callFunction(name, java.util.Arrays.asList(args)));
} catch (e) {
reject(e.nativeException.getBestMessage());
}
});
}

subscribe(name: string, eventHandler: any): void {
try {
var handler = new io.particle.android.sdk.cloud.ParticleEventHandler({
onEventError(param0: java.lang.Exception){
(<any>global).postMessage({success: kCMTextDisplayFlag_allSubtitlesForced});
},
onEvent(param0: string, param1: io.particle.android.sdk.cloud.ParticleEvent){
if (param1)
(<any>global).postMessage({success: true, data: {data: param1.dataPayload, name: param0}});
}
});
var id = this.particleDevice.subscribeToEvents(null, handler);
this.eventIds.push(id);
} catch (e) {
console.log(e.nativeException.getBestMessage());
}
}

unsubscribe(): void {
this.eventIds.forEach(element => {
this.particleDevice.unsubscribeFromEvents(element);
});
}
}

const toJsArray = (nativeSet: java.util.Set<any>): Array<any> => {
const result: Array<any> = [];
if (nativeSet) {
const it = nativeSet.iterator();
while (it.hasNext()) {
result.push(it.next());
}
}
return result;
};

const toJsonVariables = (nativeMap: java.util.Map<any, any>): Array<TNSParticleDeviceVariable> => {
const result: Array<TNSParticleDeviceVariable> = [];
if (nativeMap) {
const it = nativeMap.keySet().iterator();
while (it.hasNext()) {
const name = it.next();
const type = nativeMap.get(name).toString();
result.push({name, type});
}
}
return result;
};


const listDevices = (): void => {
console.log(`worker2 listdevices`);

try {
const particleDevices = ParticleCloudSDK.getCloud().getDevices();
cachedDevices = [];
for (let i = 0; i < particleDevices.size(); i++) {
cachedDevices.push(new MyTNSParticleDevice(particleDevices.get(i)));
}
console.log(`worker 2 got devices`);

// (<any>global).postMessage({success: true, devices: cachedDevices});
} catch (e) {
// (<any>global).postMessage({success: false, error: e.nativeException.getBestMessage()});
}
};

const subcribeFunction = (device: TNSParticleDevice, name: string): void => {
device.subscribe(name, null);
};

const unsubcribeFunction = (device: TNSParticleDevice): void => {
device.unsubscribe();
};

const getDevice = (id: string): TNSParticleDevice => {
return cachedDevices.filter(cachedDevice => cachedDevice.id === id)[0];
};

(<any>global).onmessage = msg => {
let request = msg.data;

if (request.action === "listDevices") {
listDevices();
return;
} else if (request.action === "subscribe") {
subcribeFunction(getDevice(request.options.deviceId), request.options.name);
return;
} else if (request.action === "unsubscribe") {
unsubcribeFunction(getDevice(request.options.deviceId));
return;
} else {
(<any>global).postMessage({success: false, error: `Unsupported action sent to worker: '${request.action}'`});
}
};
1 change: 0 additions & 1 deletion src/particle-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ declare module io {
}

let cachedDevices: Array<MyTNSParticleDevice>;
var ghandler : any;

class MyTNSParticleDevice implements TNSParticleDevice {
id: string;
Expand Down
46 changes: 30 additions & 16 deletions src/particle.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as utils from "tns-core-modules/utils/utils";

// keep this baby active while logged in as it holds state (our devices)
let worker;
let eventWorker;
var cbArr: any = undefined;

declare const io: any;
const ParticleCloudSDK = io.particle.android.sdk.cloud.ParticleCloudSDK;
Expand All @@ -11,15 +13,6 @@ export class Particle implements TNSParticleAPI {

constructor() {
ParticleCloudSDK.init(utils.ad.getApplicationContext());
// ParticleCloudSDK.initWithOauthCredentialsProvider(utils.ad.getApplicationContext(),
// new OauthBasicAuthCredentialsProvider() {
// getClientId():string {
// return 'xxxxx';
// }
// getClientSecret():string {
// return 'xxxxx';
// }
// });
}

public login(options: TNSParticleLoginOptions): Promise<void> {
Expand All @@ -45,8 +38,11 @@ export class Particle implements TNSParticleAPI {
if (global["TNS_WEBPACK"]) {
const WorkerScript = require("nativescript-worker-loader!./particle-worker.js");
worker = new WorkerScript();
const EventWorkerScript = require("nativescript-worker-loader!./particle-event-worker.js");
eventWorker = new EventWorkerScript();
} else {
worker = new Worker("./particle-worker.js");
eventWorker = new Worker("./particle-event-worker.js");
}
}

Expand All @@ -57,14 +53,16 @@ export class Particle implements TNSParticleAPI {
public logout(): void {
// no need for a worker here because there are no network calls involved
ParticleCloudSDK.getCloud().logOut();
worker.terminate();
if (worker) worker.terminate();
if (eventWorker) eventWorker.terminate();
}

public listDevices(): Promise<Array<TNSParticleDevice>> {
return new Promise<Array<TNSParticleDevice>>((resolve, reject) => {
worker.postMessage({
action: "listDevices"
});


worker.onmessage = msg => {
if (msg.data.success) {
Expand All @@ -76,6 +74,9 @@ export class Particle implements TNSParticleAPI {
device.subscribe = (name: string, eventHandler: any): void => this.subscribe(device.id, name, eventHandler);
device.unsubscribe = (): void => this.unsubscribe(device.id);
});
eventWorker.postMessage({
action: "listDevices"
});
resolve(devices);
} else {
reject(msg.data.error);
Expand Down Expand Up @@ -114,24 +115,37 @@ export class Particle implements TNSParticleAPI {
}

private unsubscribe(deviceId: string): void {
worker.postMessage({
eventWorker.postMessage({
action: "unsubscribe",
options: {
deviceId}
});
cbArr = undefined;
}

private subscribe(deviceId: string, name: string, eventHandler: any): void {
worker.postMessage({
if (cbArr == undefined) {
cbArr = {};
}
cbArr[name] = eventHandler;
console.dir(cbArr);

eventWorker.postMessage({
action: "subscribe",
options: {
deviceId,
name
}
});

worker.onmessage = (msg) => {
if (msg.data.success) eventHandler(msg.data.data);
eventWorker.onmessage = (msg) => {
if (msg.data.success) {
const d = msg.data.data;
// console.log(`${d.name}: ${d.data}`);
var cb : (any) => any;
cb = cbArr[d.name];
if (cb) cb(d.data);
}
}
}

Expand All @@ -144,10 +158,10 @@ export class Particle implements TNSParticleAPI {
}

public startDeviceSetupWizard(cb:any): void {
console.log('stub for startDeviceSetupWizard');
// stub for startDeviceSetupWizard
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my info: there's no device setup wizard for Android?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a device setup wizard available for Android. The device setup library is a separate native dependency - https://github.com/particle-iot/spark-setup-android, more info here https://docs.particle.io/reference/android/#android-device-setup-library. I don't have much experience with android so wasn't comfortable attempting to add that in. I do require it for the project I'm working on though. Is this something that you will be interested in adding?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, understood, fair enough! I always strive for x-platform feature parity so I might give it a stab! Not this week though, I'm swamped.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. that being said, that's not blocking for merging this PR of course.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sounds good. Do I need to do anything further to get the PR merged?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is perfect, I'll try to merge it asap.

}

public getDeviceSetupCustomizer(): any {
console.log('stub for startDeviceSetupWizard');
// stub for getDeviceSetupCustomizer
}
}
5 changes: 4 additions & 1 deletion src/particle.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export type TNSParticleDeviceType =
| "RaspberryPi"
| "DigistumpOak"
| "RedBearDuo"
| "Bluz";
| "Bluz"
| "GFConical";

export type VariableType = "INT" | "DOUBLE" | "STRING";

Expand All @@ -29,6 +30,8 @@ export function getDeviceType(id: number): TNSParticleDeviceType {
return "RedBearDuo";
case 103:
return "Bluz";
case 7822:
return "GFConical";
default:
console.log(`Unknown device type (id: ${id})`);
return "Unknown";
Expand Down