Skip to content

Instantly share code, notes, and snippets.

@olistic
Created September 5, 2016 20:04
Show Gist options
  • Save olistic/c7abaac2926b5311674e25c76b7f61d9 to your computer and use it in GitHub Desktop.
Save olistic/c7abaac2926b5311674e25c76b7f61d9 to your computer and use it in GitHub Desktop.
HomeKit Gate
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;
}
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;
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