Skip to content

Commit

Permalink
Updates for Medusa
Browse files Browse the repository at this point in the history
  • Loading branch information
Zefiro committed Nov 29, 2023
1 parent 6301e2a commit 5edc830
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 9 deletions.
3 changes: 2 additions & 1 deletion medusa.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,9 @@ const web = require('./web')(god, 'web')
const network = require('./network')(god, 'net')
const scenario = require('./scenario')(god, 'scenario')
//const screenkeys = require('./screenkeys')(god, 'keys')
const extender = require('./extender')(god, 'extender')
const extender = god.extender = require('./extender')(god, 'extender')
god.zwave = require('./zwave.js')(god)
god.timerController = require('./timer.js')(god, 'timer', 'medusa-timer')
god.thingController = require('./things')(god, 'things')


Expand Down
16 changes: 9 additions & 7 deletions things.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,12 +456,14 @@ class Button extends Thing {
getValue() { return '' }

onAction(action) {
let mqttString = this.def.mqtt
let index = mqttString.indexOf(' ')
let topic = mqttString.substr(0, index)
let message = mqttString.substr(index + 1)
this.logger.debug('Action for %s (%o): send "%s" "%s"', this.def.id, action, topic, message)
god.mqtt.publish(topic, message)
let mqttList = isArray(this.def.mqtt) ? this.def.mqtt : [ this.def.mqtt ]
for(let mqttString of mqttList) {
let index = mqttString.indexOf(' ')
let topic = mqttString.substr(0, index)
let message = mqttString.substr(index + 1)
this.logger.debug('Action for Button %s (%o): send "%s" "%s"', this.def.id, action, topic, message)
god.mqtt.publish(topic, message)
}
}

/** Buttons can't be poked */
Expand Down Expand Up @@ -825,7 +827,7 @@ module.exports = function(god2, loggerName = 'things') {
/** Gets called from clients (websocket), expects the thing id and action with thing-specific commands */
onAction: function(id, action) {
let thing = god.things[id]
this.logger.debug('action for %s (%s): %o', id, thing.def.name, action)
this.logger.debug('onAction for %s (%s): %o', id, thing.def.name, action)
if (thing) thing.onAction(action)
},

Expand Down
125 changes: 125 additions & 0 deletions timer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* Starts actions at specific absolute or relative times
*/

const winston = require('winston')

// https://stackoverflow.com/a/16608045/131146
var isObject = function(a) {
return (!!a) && (a.constructor === Object);
}
var isArray = function(a) {
return (!!a) && (a.constructor === Array);
}

var god, logger

class Timer {
id = undefined
isActive = true
firesAt = undefined
timeoutId = undefined

constructor(config) {
this.logger = logger
this.id = config.id ?? 'Timer #' + this.autoIncrementId++
this.config = config
if (config.relTime) {
// TODO see https://stackoverflow.com/a/1214753/131146
// regex simple statements of '5m' or '3s' or 5min 3ssec'
let match = config.relTime.match(/\s*\+?((\d+)\s*m(in)?)?\s*((\d+)\s*s(ec)?)?/)
if (match) {
let min = match[2] ?? 0
let sec = match[5] ?? 0
this.firesAt = new Date(Date.now() + ((min * 60 + sec) * 1000))
this.logger.debug('Set timeout for %s to %d min %d sec', this.id, min, sec)
} else {
this.logger.error('reltime unrecognized: %s ', config.relTime)
}
}
if (!this.firesAt) {
this.logger.error('No firing time defined for timer ', id)
return
}
let fireIn = this.firesAt - Date.now()
this.timeoutId = setTimeout(this.fire.bind(this), fireIn)
}

unregister(reason) {
if (this.isActive || this.timeoutId) {
this.isActive = false
clearTimeout(this.timeoutId)
this.timeoutId = undefined
this.logger.debug('Unregistered "%s"', this.id)
} else {
this.logger.debug('Unregister called on "%s", but not active', this.id)
}
}

async fire() {
this.isActive = false
this.timeoutId = undefined
this.logger.info('Timer "%s" fired, performing action', this.id)
let actions = isArray(this.config.action) ? this.config.action : [ this.config ]
for(let action of actions) {
if (action.action == 'mqtt') {
let index = action.mqtt.indexOf(' ')
let topic = action.mqtt.substr(0, index)
let message = action.mqtt.substr(index + 1)
this.logger.debug('Timer "%s": sending mqtt: "%s" "%s"', this.id, topic, message)
god.mqtt.publish(topic, message)
} else if (action.action == 'extender') {
let result = await god.extender.send(action.cmnd);
this.logger.debug('Timer "%s": sending extender: "%s" -> "%s"', this.id, action.cmnd, result)
} else {
this.logger.error('Timer "%s": action unrecognized: %s', this.id, action.action)
}
}
}
}

class RepeatingTimer extends Timer {
// TODO WIP
}

class TimerController {
timers = {}

constructor(mqttTopic) {
this.logger = logger
this.mqttTopic = mqttTopic
god.mqtt.addTrigger('cmnd/' + this.mqttTopic + '/#', 'timer', this.onMqttMessage.bind(this))
}

async onMqttMessage(trigger, topic, message, packet) {
let data = message.toString()
this.logger.debug('Received mqtt %s: %s', topic, data)
try {
data = JSON.parse(data)
} catch(e) {
this.logger.error('MQTT: Failed to parse JSON: ' + data)
return
}
if (topic == 'cmnd/' + this.mqttTopic + '/set') {
if (this.timers[data.id]) this.timers[id].unregister()
this.timers[id] = new Timer(data)
}
}

}

module.exports = function(god2, loggerName = 'timer', _mqttTopic = undefined) {
var self = {

mqttTopic: _mqttTopic ?? loggerName,
timerController: undefined,

init: function() {
god = god2
this.logger = logger = winston.loggers.get(loggerName)
timerController = new TimerController(_mqttTopic)
},
}

self.init()
return self
}
2 changes: 1 addition & 1 deletion zwave.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Connects to ZWave2MQTT on topic zwave/#
/* Connects to ZWave2MQTT on topic 'zwave/#'
*/

const winston = require('winston')
Expand Down

0 comments on commit 5edc830

Please sign in to comment.