Created
September 5, 2016 20:04
-
-
Save olistic/c7abaac2926b5311674e25c76b7f61d9 to your computer and use it in GitHub Desktop.
HomeKit Gate
This file contains hidden or 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
int RELAY = D0; | |
int RELAY_OPERATION_TIME = 220; | |
int GATE_OPENING_TIME = 16750; | |
int GATE_LOCK_TIME = 120000; | |
int OPEN = 0; | |
int CLOSED = 1; | |
int OPENING = 2; | |
int CLOSING = 3; | |
int STOPPED_OPENING = 4; | |
int STOPPED_CLOSING = 5; | |
int state; | |
bool locked; | |
int remainingOpeningTime; | |
unsigned long operatedAt; | |
unsigned long lockedAt; | |
void setup() { | |
// Initialize the relay pin | |
pinMode(RELAY, OUTPUT); | |
digitalWrite(RELAY, LOW); | |
// Initialize the gate | |
state = CLOSED; | |
locked = false; | |
remainingOpeningTime = GATE_OPENING_TIME; | |
// Register Cloud variables | |
Particle.variable("state", state); | |
// Register Cloud functions | |
Particle.function("operate", operate); | |
Particle.function("open", open); | |
Particle.function("close", close); | |
} | |
void loop() { | |
unsigned long now = millis(); | |
if ((state == OPENING && now - operatedAt > remainingOpeningTime) || (state == CLOSING && now - operatedAt > GATE_OPENING_TIME - remainingOpeningTime)) { | |
if (state == OPENING) { | |
remainingOpeningTime = 0; | |
state = OPEN; | |
} else if (state == CLOSING) { | |
remainingOpeningTime = GATE_OPENING_TIME; | |
state = CLOSED; | |
} | |
lockedAt = millis(); | |
locked = true; | |
} else if (locked && now - lockedAt > GATE_LOCK_TIME) { | |
locked = false; | |
} | |
} | |
// Callback executed when the gate is operated | |
void operationCallback() { | |
unsigned long now = millis(); | |
int operationTime = now - operatedAt; // Time elapsed since previous operation | |
operatedAt = now; | |
if (locked) { | |
locked = false; | |
} else if (state == OPEN) { | |
state = CLOSING; | |
} else if (state == CLOSED) { | |
state = OPENING; | |
} else if (state == OPENING) { | |
remainingOpeningTime -= operationTime; | |
state = STOPPED_OPENING; | |
} else if (state == CLOSING) { | |
remainingOpeningTime += operationTime; | |
state = STOPPED_CLOSING; | |
} else if (state == STOPPED_OPENING) { | |
state = CLOSING; | |
} else if (state == STOPPED_CLOSING) { | |
state = OPENING; | |
} | |
} | |
// Operate the gate an specified amount of times | |
void operateGate(int times) { | |
for (int i = 0; i < times; i++) { | |
if (i != 0) { | |
delay(RELAY_OPERATION_TIME); | |
} | |
digitalWrite(RELAY, HIGH); | |
delay(RELAY_OPERATION_TIME); | |
digitalWrite(RELAY, LOW); | |
operationCallback(); | |
} | |
} | |
/* | |
* Cloud functions | |
*/ | |
int open(String arg) { | |
if (state == CLOSED || state == STOPPED_CLOSING) { | |
if (locked) { | |
operateGate(2); | |
} else { | |
operateGate(1); | |
} | |
} else if (state == CLOSING) { | |
operateGate(2); | |
} else if (state == STOPPED_OPENING) { | |
operateGate(3); | |
} | |
return 1; | |
} | |
int close(String arg) { | |
if (state == OPEN || state == STOPPED_OPENING) { | |
if (locked) { | |
operateGate(2); | |
} else { | |
operateGate(1); | |
} | |
} else if (state == OPENING) { | |
operateGate(2); | |
} else if (state == STOPPED_CLOSING) { | |
operateGate(3); | |
} | |
return 1; | |
} | |
int operate(String arg) { | |
if (arg.length() > 0) { | |
int times = arg.toInt(); | |
operateGate(times); | |
return 1; | |
} | |
return -1; | |
} |
This file contains hidden or 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
const Particle = require('particle-api-js'); | |
const PARTICLE_ACCESS_TOKEN = process.env.PARTICLE_ACCESS_TOKEN; | |
const DEVICE_ID = '310441150a47343356565663'; | |
const particle = new Particle(); | |
class Gate { | |
getState() { | |
return particle.getVariable({ | |
deviceId: DEVICE_ID, | |
name: 'state', | |
auth: PARTICLE_ACCESS_TOKEN, | |
}).then(data => data.body.result); | |
} | |
open() { | |
return particle.callFunction({ | |
deviceId: DEVICE_ID, | |
name: 'open', | |
auth: PARTICLE_ACCESS_TOKEN, | |
}); | |
} | |
close() { | |
return particle.callFunction({ | |
deviceId: DEVICE_ID, | |
name: 'close', | |
auth: PARTICLE_ACCESS_TOKEN, | |
}); | |
} | |
} | |
Gate.STATES = { | |
OPEN: 0, | |
CLOSED: 1, | |
OPENING: 2, | |
CLOSING: 3, | |
STOPPED_OPENING: 4, | |
STOPPED_CLOSING: 5, | |
}; | |
exports = module.exports = Gate; |
This file contains hidden or 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
const Accessory = require('../').Accessory; | |
const Service = require('../').Service; | |
const Characteristic = require('../').Characteristic; | |
const uuid = require('../').uuid; | |
const Gate = require('./Gate'); | |
// Create a new instance of a Particle controlled gate | |
const gate = new Gate(); | |
// Generate a consistent UUID that will remain the same even when restarting the server | |
const gateUUID = uuid.generate('hap-nodejs:accessories:gate'); | |
// This is the Accessory that will be returned to HAP-NodeJS | |
const gateAccessory = exports.accessory = new Accessory('Gate', gateUUID); | |
// Add properties for publishing | |
gateAccessory.username = 'B2:AC:6E:98:DE:0A'; | |
gateAccessory.pincode = '031-45-154'; | |
// Set some basic properties | |
gateAccessory | |
.getService(Service.AccessoryInformation) | |
.setCharacteristic(Characteristic.Manufacturer, 'Imecotron') | |
.setCharacteristic(Characteristic.Model, 'Electric Gate') | |
.setCharacteristic(Characteristic.SerialNumber, 'A1S2NASF88EW'); | |
// Listen for "identify" events for this Accessory | |
gateAccessory.on('identify', (paired, callback) => callback()); | |
// Add the Garage Door Opener service and listen for "set" events from iOS | |
gateAccessory | |
.addService(Service.GarageDoorOpener, 'Gate') | |
.setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.CLOSED) | |
.getCharacteristic(Characteristic.TargetDoorState) | |
.on('set', (value, callback) => { | |
if (value === Characteristic.TargetDoorState.OPEN) { | |
gate | |
.open() | |
.then(() => { | |
gateAccessory | |
.getService(Service.GarageDoorOpener) | |
.setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.OPEN); | |
callback(); | |
}) | |
.catch(callback); | |
} else if (value === Characteristic.TargetDoorState.CLOSED) { | |
gate | |
.close() | |
.then(() => { | |
gateAccessory | |
.getService(Service.GarageDoorOpener) | |
.setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.CLOSED); | |
callback(); | |
}) | |
.catch(callback); | |
} | |
}); | |
function getCurrentDoorState(state) { | |
switch (state) { | |
case Gate.STATES.OPEN: | |
return Characteristic.CurrentDoorState.OPEN; | |
case Gate.STATES.CLOSED: | |
return Characteristic.CurrentDoorState.CLOSED; | |
case Gate.STATES.OPENING: | |
return Characteristic.CurrentDoorState.OPENING; | |
case Gate.STATES.CLOSING: | |
return Characteristic.CurrentDoorState.CLOSING; | |
case Gate.STATES.STOPPED_OPENING: | |
case Gate.STATES.STOPPED_CLOSING: | |
return Characteristic.CurrentDoorState.STOPPED; | |
default: | |
throw new Error(`Unknown state: ${state}`); | |
} | |
} | |
// Intercept requests for the current state | |
gateAccessory | |
.getService(Service.GarageDoorOpener) | |
.getCharacteristic(Characteristic.CurrentDoorState) | |
.on('get', (callback) => { | |
gate | |
.getState() | |
.then(state => callback(null, getCurrentDoorState(state))) | |
.catch(callback); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment