Create an external action for IoTool using Wi-Fi
When adding new actions to the IoTool application, it is encouraged to crate new extension rather than modifying the application itself. This document will help you create your own actions. This document will cover internal actions on your device.
General things
Before we start developing, we need to address that this document will only go briefly over the over the basics on how to create new extension. For more details on how to program such extension, please look at the document located in this link. In general extension only contains 2 types classes (provider and multiple receivers). Additionally it also contains JSON file.
Classes
This particular example uses "LIFX" as a ServiceID. It contains a receiver class and a provider class.
Note: Sections marked with "//TODO" indicate that you may need to change that part of the code.
"IoToolActionProviderLIFXOnOffUDP.java"
In this example, when the function is triggered, it calls a phone number set by the user.
package io.senlab.iotool.actionprovider.lifx.receiver;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import io.senlab.iotool.actionprovider.lifx.DiscoverDevices;
import io.senlab.iotool.actionprovider.lifx.ReciveData;
import io.senlab.iotool.actionprovider.lifx.internal.protocol.PowerState;
import io.senlab.iotool.actionprovider.lifx.internal.protocol.SetLightPowerRequest;
import io.senlab.iotool.actionprovider.lifx.internal.protocol.SetPowerRequest;
import io.senlab.iotool.library.actions.IoToolActionReceiver;
/**
* Created by Frenk99999 on 28.2.2017.
*/
public class IoToolActionProviderLIFXOnOffUDP extends IoToolActionReceiver {
private static final String SELECTOR = "selector";
private static final String POWER = "power";
private static final String DURATION = "duration";
private Context context;
private static boolean connected = false;
private static char[] hex = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
class SendRequest extends AsyncTask<Bundle, Void , Void> {
protected Void doInBackground(Bundle... args) {
try {
String selector = args[0].getString(SELECTOR);
boolean powerb = args[0].getBoolean(POWER);
String duration = args[0].getString(DURATION);
int dur =0;
try {
if (duration.trim().endsWith("ms")) {
dur = Integer.valueOf(duration.substring(0, duration.length() - 2));
} else {
dur = getDuration(duration)*1000;
}
}catch (Exception e){
}
Object[] lights = ReciveData.getALL();
boolean all = selector.equals("all");
String [] select = {"",""};
if(!all){
select = selector.split(":");
select[1].toUpperCase();
}
for(int i=0;i<lights.length;i++){
ReciveData.DiscoveredLight light = (ReciveData.DiscoveredLight) lights[0];
PowerState power = null;
if(all){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
else if(select[0].equals("id")){
String id = light.getMacAddress().getHex().replace(":","");
if(id.equals(select[1])){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
}
else if(select[0].equals("label")){
String id = light.getLabel();
if(id.equals(select[1])){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
}
else if(select[0].equals("group")){
String id = light.getLabel();
if(id.equals(select[1])){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
}
else if(select[0].equals("location")){
String id = light.getLabel();
if(id.equals(select[1])){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
}
else if(select[0].equals("location_id")){
String id = getIdFromBuffer(light.getLocationID());
if(id.equals(select[1])){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
}
else if(select[0].equals("group_id")){
String id = getIdFromBuffer(light.getGroupID());
if(id.equals(select[1])){
if(powerb){
power = PowerState.ON;
}
else{
power = PowerState.OFF;
}
}
}
if(power != null){
boolean sent = false;
while(!sent) {
try {
Log.i("LIGHTS",""+dur);
DatagramSocket socket = new DatagramSocket();
socket.setReuseAddress(true);
SetLightPowerRequest powerRequest = new SetLightPowerRequest(power);
powerRequest.setDuration(dur);
powerRequest.setSource(0);
powerRequest.setResponseRequired(true);
DatagramPacket packet = new DatagramPacket(powerRequest.bytes().array(), powerRequest.bytes().array().length, light.getSocketAddress());
socket.send(packet);
socket.disconnect();
if(!socket.isClosed()){
socket.close();
}
sent = true;
}catch (Exception e) {
}
}
}
}
}catch(Exception m){
Log.e("EXC",m.toString());
}
return null;
}
private String getIdFromBuffer(ByteBuffer buffer){
byte[] array = buffer.array();
char[] chars = new char[array.length*2];
for(int i=0;i<array.length;i++){
byte bit = array[i];
int hexx = bit & 0x000000000f;
chars[i*2+1]=hex[hexx];
hexx = (bit >> 4) & 0x000000000f;
chars[i*2] = hex[hexx];
}
return new String(chars);
}
}
@Override
protected void onActionExecute(Context context, Bundle args, Bundle extras) {
//TODO Code that executes when the action is triggered
try {
boolean con = connected;
if(!connected) {
connected = true;
// start listener for UDP packages
Thread thread = new Thread(new ReciveData());
thread.start();
// start device discovery
Thread thread2 = new Thread(new DiscoverDevices(context));
thread2.start();
}
if(!con){
long w = System.currentTimeMillis();
while(w+1050 > System.currentTimeMillis()){
}
}
new SendRequest().execute(args);
}catch(Exception m){
Log.e("EXC",m.toString());
}
}
@Override
protected String[] getInputParamNames(String... requiredParameters) {
//TODO defining parameters containing the values
return new String[]{SELECTOR,POWER,DURATION};
}
public int getDuration(String sendinterval) {
Integer number = Integer.parseInt(sendinterval.replaceAll("[\\D]", ""));
Double numberMs = Double.valueOf(number);
if (sendinterval.endsWith("m")) number *= 60;
else if (sendinterval.endsWith("h")) number *= 3600;
else if (sendinterval.endsWith("d")) number *= 86400;
return number;
}
}
Part of the code read UDP packages
DatagramSocket socket = new DatagramSocket(BROADCAST_PORT,InetAddress.getByName("0.0.0.0"));
socket.setReuseAddress(true);
while (true) {
byte[] recvBuf = new byte[1024];
DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
socket.receive(packet);
String data = new String(packet.getData());
InetSocketAddress address = new InetSocketAddress(packet.getAddress(),BROADCAST_PORT);
ByteBuffer readbuffer = ByteBuffer.wrap(packet.getData());
readbuffer.rewind();
ByteBuffer packetSize = readbuffer.slice();
packetSize.position(0);
packetSize.limit(2);
int size = Packet.FIELD_SIZE.value(packetSize);
if(packet.getLength() == size){
ByteBuffer packetType = readbuffer.slice();
packetType.position(32);
packetType.limit(34);
int type = Packet.FIELD_PACKET_TYPE.value(packetType);
PacketHandler<?> handler = PacketFactory.createHandler(type);
if (handler == null) {
continue;
}
Packet packetc = handler.handle(readbuffer);
if (packetc == null) {
} else {
handlePacket(packetc, address);
}
}
}
} catch (IOException ex) {
}
"LIFXActionsProvider.java"
Used to feed JSON file to the application.
package io.senlab.iotool.actionprovider.lifx.provider;
import io.senlab.iotool.actionprovider.lifx.R;
import io.senlab.iotool.library.actions.IoToolActionProvider;
/**
* Created by Sandi on 20.4.2016.
*/
public class LIFXActionsProvider extends IoToolActionProvider {
@Override
protected int getActionsRawResourceId() {
return R.raw.actions;
}
}
"raw/actions.json"
Example on how to make JSON. This file will most likely be needed to be changed.
{
"package": "io.senlab.iotool.actionprovider.lifx",
"description": "LIFX actions",
"developer": "SenLab",
"actions": [
{
"classname": "receiver.IoToolActionProviderLIFXOnOffUDP",
"description": "Turn on/off lights via LAN",
"params": [
{
"type": "io.senlab.iotool.actions.input.ActionInputString",
"description": "Selector"
},
{
"type": "io.senlab.iotool.actions.input.ActionInputBoolean",
"description": "Power ON/OFF"
},
{
"type": "io.senlab.iotool.actions.input.ActionInputString",
"description": "Duration"
}
]
}
]
}