Created
May 8, 2020 00:22
-
-
Save schester44/70fbcae7695972a27d3156f3391496e4 to your computer and use it in GitHub Desktop.
RaspberryPi + NodeJS Garage Door Opener
This file contains 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
/** | |
* This file should be a repo but oh well.. | |
* This code will check a magnetic sensor value and send a text message via twilio if the circuit remains open for an extended amount of time | |
* Used Koa for an http server so I could read the current state via clients. | |
**/ | |
const Koa = require("koa") | |
const Router = require("koa-router") | |
const rpio = require("rpio") | |
const cors = require("@koa/cors") | |
const Twilio = require("twilio") | |
const { getHours, isWeekend, isBefore, subSeconds, differenceInSeconds } = require("date-fns") | |
const bodyParser = require("koa-bodyparser") | |
const app = new Koa() | |
const router = new Router() | |
const ALLOWED_LIMIT = 60 | |
const twilioConfig = { | |
ACCOUNT_SID: "", | |
AUTH_TOKEN: "", | |
NUMBER: "" | |
} | |
const twilio = new Twilio(twilioConfig.ACCOUNT_SID, twilioConfig.AUTH_TOKEN) | |
const getDoorStatus = () => (rpio.read(12) ? "open" : "closed") | |
const OPENER_PIN = 7 | |
// sensor | |
rpio.open(12, rpio.INPUT, rpio.PULL_UP) | |
// opener | |
rpio.open(OPENER_PIN, rpio.OUTPUT, rpio.LOW); | |
let settings = { | |
textEnabled: true, | |
overrideEnabled: false | |
} | |
let status = { | |
status: getDoorStatus(), | |
lastCheck: new Date(), | |
lastAlertSent: undefined, | |
lastOpen: undefined | |
} | |
const shouldSendText = () => { | |
// dont send if we manually disabled it | |
if (!settings.textEnabled) return false | |
const now = new Date() | |
const currentHour = getHours(now) | |
// send if weekday between midnight and 6pm | |
let isValidTime = currentHour < 18 | |
let isWeek = !isWeekend(now) | |
return (isValidTime && isWeek) || settings.overrideEnabled | |
} | |
const getNewStatus = current => { | |
let newStatus = { ...current, status: getDoorStatus(), lastCheck: new Date() } | |
// if the door was closed but now its open then the last time it was opened was just now. | |
if (current.status === "closed" && newStatus.status == "open") { | |
newStatus.lastOpen = status.lastCheck | |
} | |
return newStatus | |
} | |
const doSendAlertCheck = async current => { | |
// the current time minus the allowed limit is our deadline. If the lastOpen time is before that time then we know its been too long. | |
const deadline = subSeconds(new Date(), ALLOWED_LIMIT) | |
const doorNeedsShut = isBefore(current.lastOpen, deadline) | |
if (!doorNeedsShut) { | |
return current.lastAlertSent | |
} | |
// Don't send a message if we've already sent one since the door has been opened. | |
// The only time lastAlertSent should exist is if the door is still open. | |
if (current.lastAlertSent) { | |
return current.lastAlertSent | |
} | |
if (!shouldSendText()) return | |
console.log(`Sending text message. The door has been open longer than ${ALLOWED_LIMIT} seconds.`) | |
await twilio.messages.create({ | |
body: "The garage door is open.", | |
to: twilioConfig.NUMBER, | |
from: twilioConfig.NUMBER | |
}) | |
return new Date() | |
} | |
setInterval(async () => { | |
let newStatus = getNewStatus(status) | |
const statusHasChanged = status.status !== newStatus.status | |
const isOpen = newStatus.status === "open" | |
if (!isOpen) { | |
// Door is closed, erase the last time we sent an alert | |
newStatus.lastAlertSent = undefined | |
} | |
if (isOpen) { | |
newStatus.lastAlertSent = await doSendAlertCheck(newStatus) | |
} | |
// TODO: Send to logger | |
if (statusHasChanged) { | |
const now = new Date() | |
console.log(`Garage door is now ${newStatus.status}`, now) | |
if (!isOpen) { | |
console.log(`Door was open for ${differenceInSeconds(now, status.lastOpen)} seconds.`) | |
} | |
} | |
status = newStatus | |
}, 500) | |
router.post('/opener', async ctx => { | |
rpio.write(OPENER_PIN, ctx.request.body.position === 'open' ? rpio.LOW : rpio.HIGH); | |
ctx.body = { status: 'success' } | |
}) | |
router.get("/status", (ctx, next) => { | |
ctx.body = { status, settings, limit: ALLOWED_LIMIT } | |
}) | |
router.post("/settings", (ctx, next) => { | |
settings = { | |
...settings, | |
...ctx.request.body | |
} | |
console.log('Settings updated...', settings, new Date()) | |
}) | |
app.use(bodyParser()) | |
app.use(cors()) | |
app.use(router.routes()).use(router.allowedMethods()) | |
app.listen(3000) | |
console.log("listening on 3000") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment