UNPKG

thermostat-pi-dht

Version:

A Node.js framework (standalone app + lib) to control a heating system with a Raspberry Pi using DHT11 or DHT22/AM2302 sensors and GPIO actuators.

297 lines 12.6 kB
"use strict"; // Project: thermostat-pi-dht // File: Thermostat.ts // // Copyright 2021 Henning Kerstan // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Thermostat = void 0; const delay_1 = __importDefault(require("delay")); const pigpio_1 = require("pigpio"); const pigpio_dht_1 = __importDefault(require("pigpio-dht")); /** An implementation of a thermostat on a Raspberry Pi using * - a DHT11 or DHT22/AM2302 sensor and * - a GPIO as actuator. * * Once activated, a thermostat regularly measures the current [[temperature]] (and relative [[humidity]]). It then activates or deactivates the [[actuatorPin | actuator GPIO]] (which in turn should control the heating, e.g. by using a relay) by comparing the current [[temperature]] with the given [[setpoint]]: * - if the [[temperature]] is below the [[setpoint]], it activates the actuator and * - if the [[temperature]] is equal to or above the [[setpoint]], it deactivates the actuator. * * ## Common measuring loop * Note that this implementation uses a *common measuring loop* for all configured thermostats, hence the [[samplingInterval]] as well as the measurement [[timeout]] is configured globally (i.e. as static variables). * * ## Power rail activation/deactivation * The implementation optionally supports a (common) *power rail activation/deactivation*: If a [[sensorPowerPin]] is specified, power to the DHT sensors will be switched on prior to a measurement and switched off after measurements finished (or timed out). The [[sensorWarmUpTime]] determines, how long (in seconds) the software will wait until starting a measurement after power has been activated. */ class Thermostat { /** Constructs a new thermostat based on the supplied configuration. */ constructor(config) { /** The desired temperature (in °C) to be maintained by the thermostat. */ this._setpoint = 18; /** UNIX timestamp (in milliseconds) of the latest measurement. */ this._timestamp = undefined; /** Raw temperature (in °C) of the latest measurement. */ this._rawTemperature = undefined; /** Temperature (in °C) of the latest measurement; possibly with correction summand applied. */ this._temperature = undefined; /** Relative humidity (in %) of the latest measurement. */ this._humidity = undefined; /** Determines whether the heating is currently on. */ this._heatingIsOn = false; this.internalId = Thermostat.nextInternalId++; this.name = config.name; if (config.sensorPin) { this.sensor = (0, pigpio_dht_1.default)(config.sensorPin, config.sensorType ? config.sensorType : Thermostat.defaultSensorType); this.sensorPin = config.sensorPin; this.sensor.on('activate', () => { // nothing to be done }); this.sensor.on('badChecksum', () => { this.onData(undefined, undefined); }); this.sensor.on('result', (data) => { this.onData(data.temperature, data.humidity); }); this.sensor.on('end', () => { // nothing to be done // TODO: maybe reduce measurement counter here? }); } this.setpoint = config.setpoint ? config.setpoint : Thermostat.defaultSetpoint; this.temperatureSummand = config.temperatureSummand ? config.temperatureSummand : 0; if (config.actuatorPin) { this.actuator = new pigpio_1.Gpio(config.actuatorPin, { mode: pigpio_1.Gpio.OUTPUT }); this.actuatorPin = config.actuatorPin; // disable actuator if no sensor is defined if (!config.sensorPin) { this.actuator.digitalWrite(0); } } } /** GPIO pin controlling the power supply for all connected DHT sensors. If undefined, power is assumed to be always on. */ static get sensorPowerPin() { return this._sensorPowerPin; } /** GPIO pin controlling the power supply for all connected DHT sensors. If undefined, power is assumed to be always on. */ static set sensorPowerPin(pin) { this._sensorPowerPin = pin; this._sensorPowerGpio = new pigpio_1.Gpio(pin, { mode: pigpio_1.Gpio.OUTPUT, }); } /** The desired temperature (in °C) to be maintained by the thermostat. */ get setpoint() { return this._setpoint; } /** The desired temperature (in °C) to be maintained by the thermostat. */ set setpoint(temperature) { this._setpoint = temperature; void this.check(); } /** UNIX timestamp (in milliseconds) of the latest measurement. */ get timestamp() { return this._timestamp; } /** Raw temperature (in °C) measured in the latest measurement. */ get rawTemperature() { return this._rawTemperature; } /** Temperature (in °C) measured in the latest measurement; possibly with correction summand applied. */ get temperature() { return this._temperature; } /** Relative humidity (in %) measured in the latest measurement. */ get humidity() { return this._humidity; } /** Determines whether the heating is currently on. */ get heatingIsOn() { return this._heatingIsOn; } /** Determines whether the thermostat is active. */ get isActive() { return Thermostat.activeInstances.has(this.internalId); } /** The main measurement loop.*/ static async main() { // nothing to be done if not active if (!this._isActive) { return; } if (this._sensorPowerGpio) { // power up sensors ... this._sensorPowerGpio.digitalWrite(1); // ... and wait for sensor warmup (at least one second) await (0, delay_1.default)(Math.max(1000, 1000 * this.sensorWarmUpTime)); } // start measurements Thermostat.remainingMeasurements = 0; this.activeInstances.forEach((thermostat) => { if (thermostat.sensor) { Thermostat.remainingMeasurements++; thermostat.sensor.read(); } }); // wait for measurements to finish const started = Date.now(); while (Thermostat.remainingMeasurements > 0) { await (0, delay_1.default)(1000); const duration = (Date.now() - started) / 1000; if (duration > this.timeout) { break; } } if (this._sensorPowerGpio) { // power down sensors this._sensorPowerGpio.digitalWrite(0); } // determine next activation of this function (at least 2s required) const wait = Math.max(2000, this.samplingInterval * 1000); // eslint-disable-next-line @typescript-eslint/no-misused-promises setTimeout(this.main.bind(this), wait); } /** Activates the main measurement loop for all active thermostats. */ static async activate() { // nothing to be done if already active if (this._isActive) { return; } this._isActive = true; // wait await (0, delay_1.default)(1000); // run main loop void this.main(); } // Deactivates all active thermostats at once. static deactivateAll() { this._isActive = false; if (this._sensorPowerGpio) { this._sensorPowerGpio.digitalWrite(0); } this.activeInstances.forEach((thermostat) => { thermostat.deactivate(); }); } /** Handles new temperature/humidity data. */ onData(temperature, humidity) { Thermostat.remainingMeasurements--; this._timestamp = Date.now(); this._rawTemperature = Math.round(temperature * 10) / 10; this._temperature = this._rawTemperature + this.temperatureSummand; this._humidity = Math.round(humidity); void this.check(); } /** Checks if heating needs to be switched on/off */ async check() { if (!this.actuator) { this._heatingIsOn = false; return; } if (this.temperature) { // heating is on iff measured temperatore does not yet exceed setpoint this._heatingIsOn = this.temperature >= this.setpoint ? false : true; // TODO: implement hysteresis to prevent too frequent switching } else { // if measurement failed, switch off heating this._heatingIsOn = false; } // to prevent simultaneous relay switching, add random delay of up to 500ms const ms = Math.random() * 500; await (0, delay_1.default)(ms); this.actuator.digitalWrite(this.heatingIsOn === true ? 1 : 0); } /** Activates the thermostat. */ activate() { // cannot activate an already active thermostat if (Thermostat.activeInstances.has(this.internalId)) { return; } // add to list of active instances Thermostat.activeInstances.set(this.internalId, this); //activate main loop void Thermostat.activate(); } /** Deactivates the thermostat. * @param heatingOn Determines whether after deactivation of the thermostat the heating is off (default) or on (if set to true). */ deactivate(heatingOn = false) { // remove from list of active instances Thermostat.activeInstances.delete(this.internalId); if (Thermostat.activeInstances.size < 1) { Thermostat._isActive = false; } // switch heating on/off if (this.actuator) { this.actuator.digitalWrite(heatingOn ? 1 : 0); } } configurationToJSON() { return { name: this.name, sensorPin: this.sensorPin, sensorType: this.sensorType, actuatorPin: this.actuatorPin, setpoint: this.setpoint, temperatureSummand: this.temperatureSummand, }; } toJSON() { return { name: this.name, setpoint: this.setpoint, timestamp: this.timestamp, rawTemperature: this.rawTemperature, temperature: this.temperature, humidity: this.humidity, heatingIsOn: this.heatingIsOn, }; } toString() { return JSON.stringify(this.toJSON()); } } exports.Thermostat = Thermostat; /** Sampling interval (in seconds) for all thermostats. * * This is the interval ranging from * - the finalization (or timeout) of a measurement run to * - the next start of the measurement run. */ Thermostat.samplingInterval = 120; /** Delay (in seconds) between sensor power on and start of measurements. */ Thermostat.sensorWarmUpTime = 4; /** Timeout after which all measurements will be stopped. */ Thermostat.timeout = 5; /** The default temperature (in °C) to be maintained by a newly created thermostat, if no such value is configured on creation. */ Thermostat.defaultSetpoint = 18; /** The default sensor type for a new thermostat. */ Thermostat.defaultSensorType = 22; Thermostat.remainingMeasurements = 0; Thermostat.activeInstances = new Map(); Thermostat.nextInternalId = 0; // Private members /** GPIO pin controlling the power supply for all connected DHT sensors. If undefined, power is assumed to be always on. */ Thermostat._sensorPowerPin = undefined; /** GPIO controlling the power supply for all connected DHT sensors. If undefined, power is assumed to be always on. */ Thermostat._sensorPowerGpio = undefined; /** Determines whether the thermostat is active. */ Thermostat._isActive = false; //# sourceMappingURL=Thermostat.js.map