From 5c7ca1e3c689db35191a8e236da4bf2a8255ccb2 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 23 Feb 2018 23:40:35 -0800 Subject: [PATCH] controller: mechanical relay implementation --- controller/config.ts | 3 ++ controller/controller.ts | 8 ++-- controller/interfaces.ts | 4 +- controller/mocks.ts | 4 +- controller/package-lock.json | 19 +++++++++ controller/package.json | 3 +- controller/relay.ts | 74 ++++++++++++++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 controller/relay.ts diff --git a/controller/config.ts b/controller/config.ts index 454248d..635a3fa 100644 --- a/controller/config.ts +++ b/controller/config.ts @@ -2,6 +2,9 @@ export default { PID: { strikeThreshold: 0.95, // percentage of target temp to reach before switching to PID control }, + Relay: { + pins: [10, 12] + }, thermometer: { defaultTemperature: -1, // uninitialized temperature value sampleIntervalMS: 750, // ms between temperature measurements diff --git a/controller/controller.ts b/controller/controller.ts index 4ba4e40..8ba9041 100644 --- a/controller/controller.ts +++ b/controller/controller.ts @@ -2,14 +2,14 @@ import BrewConfig from '../core/models/BrewConfig'; import BrewStep from '../core/models/BrewStep'; import BrewUpdate from '../core/models/BrewUpdate'; import controllerConfig from './config'; -import { MockRelay } from './mocks'; import { PIDController } from './pid'; import { PiThermometer } from './thermometer'; +import { MechanicalRelay } from './relay'; import { TemperatureUnits, ZynetMessageType} from '../core/constants'; import ZynetConnection from './connection'; import ZynetMessage from '../core/models/ZynetMessage'; -const relay = new MockRelay(); +const relay = new MechanicalRelay(controllerConfig.Relay.pins[0]); const thermometer = new PiThermometer(); const mockUpdate = new BrewUpdate(1, 1, 60, 68, 152, false, 0); @@ -35,10 +35,10 @@ function subscribeThermometer() { if (config) { if (pidThresholdReached) { pidController.updateTemperature(temperature); - relay.switch(pidController.state); + relay.toggle(pidController.state); } else { pidThresholdReached = temperature >= (config.steps[currentStepIndex].temperature * controllerConfig.PID.strikeThreshold); - relay.switch(!pidThresholdReached); + relay.toggle(!pidThresholdReached); } } diff --git a/controller/interfaces.ts b/controller/interfaces.ts index 42ffd4c..b69f240 100644 --- a/controller/interfaces.ts +++ b/controller/interfaces.ts @@ -11,7 +11,9 @@ export interface Connection { export interface Relay { on: boolean; - switch(on: boolean): void; + switchOn(): void; + switchOff(): void; + toggle(on: boolean): void; } export interface Thermometer { diff --git a/controller/mocks.ts b/controller/mocks.ts index 37f8da5..4811c36 100644 --- a/controller/mocks.ts +++ b/controller/mocks.ts @@ -12,7 +12,9 @@ export class MockRelay implements Relay { this.on = false; } - switch(on: boolean): void { this.on = on; } + switchOn(): void { this.on = true; } + switchOff(): void { this.on = false; } + toggle(on: boolean): void { this.on = on; } } export class MockThermometer implements Thermometer { diff --git a/controller/package-lock.json b/controller/package-lock.json index 5424eca..60e3146 100644 --- a/controller/package-lock.json +++ b/controller/package-lock.json @@ -4,15 +4,34 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + }, "ds18b20": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ds18b20/-/ds18b20-0.1.0.tgz", "integrity": "sha1-tXHU2j9/kt1c2K72kut3fjBpavE=" }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" + }, "node-pid-controller": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-pid-controller/-/node-pid-controller-1.0.0.tgz", "integrity": "sha1-lFZx0dHOKUiFhQifBIBwDDykik8=" + }, + "rpio": { + "version": "0.9.20", + "resolved": "https://registry.npmjs.org/rpio/-/rpio-0.9.20.tgz", + "integrity": "sha1-p9XZk6UhABVdHrehKal4E64a7aU=", + "requires": { + "bindings": "1.3.0", + "nan": "2.8.0" + } } } } diff --git a/controller/package.json b/controller/package.json index e39199b..1db9746 100644 --- a/controller/package.json +++ b/controller/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "ds18b20": "^0.1.0", - "node-pid-controller": "^1.0.0" + "node-pid-controller": "^1.0.0", + "rpio": "^0.9.20" } } diff --git a/controller/relay.ts b/controller/relay.ts new file mode 100644 index 0000000..6ffae6c --- /dev/null +++ b/controller/relay.ts @@ -0,0 +1,74 @@ +import * as rpio from 'rpio'; + +import config from './config'; +import { Relay } from './interfaces'; + + +export class MechanicalRelay implements Relay { + public on: boolean; + + constructor(private pin: number) { + this.on = false; + rpio.open(this.pin, rpio.OUTPUT); + } + + switchOn(): void { + this.toggle(true); + } + + switchOff(): void { + this.toggle(false); + } + + toggle(on: boolean): void { + rpio.write(this.pin, !on ? rpio.HIGH : rpio.LOW); + this.on = !on; + } +} + +// in order to spread out the wear on the mechanical relays, keep +// a pointer to the current relay and only turn it on when a relay +// needs is activated +// on the next activation increment the relay pointer +export class RelayArray implements Relay { + private currentRelayIndex: number; + private relays: Relay[]; + + constructor(private reversed = true) { + this.currentRelayIndex = 0; + this.relays = config.Relay.pins.map(pin => new MechanicalRelay(pin)); + } + + public get on(): boolean { + return this.currentRelay.on; + } + + private get currentRelay(): Relay { + return this.relays[this.currentRelayIndex]; + } + + switchOn(): void { + // check that the current relay is not already switched on + if (!this.currentRelay.on) { + // increment the relay pointer + this.currentRelayIndex = (this.currentRelayIndex + 1) % this.relays.length; + // switch on the current relay + this.currentRelay.switchOn(); + } + } + + switchOff(): void { + // turn off any relays that are on + this.relays + .filter(relay => relay.on) + .forEach(relay => relay.switchOff()); + } + + toggle(on: boolean): void { + if (!(on && this.reversed)) { + this.switchOn(); + } else { + this.switchOff(); + } + } +}