From 9d8b7c46388f8f313fa6cb372e3201e559b5484e Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sun, 5 Apr 2020 17:15:13 +0200 Subject: [PATCH] feat(gpio): add option to configure switches --- config/test.yml | 3 ++ docs/integrations/gpio.md | 12 +++++++ src/integrations/gpio/gpio.config.ts | 7 ++++ src/integrations/gpio/gpio.service.spec.ts | 13 +++++++- src/integrations/gpio/gpio.service.ts | 39 ++++++++++++++++++++++ src/integrations/gpio/gpio.switch.spec.ts | 28 ++++++++++++++++ src/integrations/gpio/gpio.switch.ts | 21 ++++++++++++ 7 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/integrations/gpio/gpio.switch.spec.ts create mode 100644 src/integrations/gpio/gpio.switch.ts diff --git a/config/test.yml b/config/test.yml index 1c74a11..00481f3 100644 --- a/config/test.yml +++ b/config/test.yml @@ -9,6 +9,9 @@ gpio: - name: Radar pin: 24 deviceClass: motion + switches: + - name: Test Switch + pin: 17 shell: sensors: - name: Simple Test diff --git a/docs/integrations/gpio.md b/docs/integrations/gpio.md index e4c7046..2b31600 100644 --- a/docs/integrations/gpio.md +++ b/docs/integrations/gpio.md @@ -49,6 +49,7 @@ services: | Name | Type | Default | Description | | --------------- | -------------------------------------- | ------- | -------------------------------------- | | `binarySensors` | [GPIO Binary Sensors](#binary-sensors) | | Array of binary sensor configurations. | +| `switches` | [GPIO Switches](#switches) | | Array of switch configurations. | ### Binary Sensors @@ -58,6 +59,14 @@ services: | `pin` | Number | | Number of the GPIO pin that should be tracked. | | `deviceClass` | String | | Home Assistant [device class](https://www.home-assistant.io/integrations/binary_sensor/#device-class) that the sensor should be shown as. | +### Switches + +| Name | Type | Default | Description | +| ------ | ------ | ------- | ------------------------------------------------------------ | +| `name` | String | | Friendly name for this sensor. | +| `pin` | Number | | Number of the GPIO pin that should be used as output. | +| `icon` | String | | Icon to display for the switch in the Home Assistant frontend. | + ::: details Example Config ```yaml @@ -69,6 +78,9 @@ gpio: - name: Bedroom Motion Sensor pin: 17 deviceClass: motion + switches: + - name: LED + pin: 18 ``` ::: diff --git a/src/integrations/gpio/gpio.config.ts b/src/integrations/gpio/gpio.config.ts index cce71c8..ef8d591 100644 --- a/src/integrations/gpio/gpio.config.ts +++ b/src/integrations/gpio/gpio.config.ts @@ -2,6 +2,7 @@ import { BinarySensorDeviceClass } from '../home-assistant/binary-sensor-config' export class GpioConfig { binarySensors: GpioBinarySensorOptions[] = []; + switches: GpioSwitchOptions[] = []; } class GpioBinarySensorOptions { @@ -9,3 +10,9 @@ class GpioBinarySensorOptions { pin: number; deviceClass?: BinarySensorDeviceClass; } + +class GpioSwitchOptions { + name: string; + pin: number; + icon?: string; +} diff --git a/src/integrations/gpio/gpio.service.spec.ts b/src/integrations/gpio/gpio.service.spec.ts index 2e899d4..dce7cd4 100644 --- a/src/integrations/gpio/gpio.service.spec.ts +++ b/src/integrations/gpio/gpio.service.spec.ts @@ -8,6 +8,7 @@ import { BinarySensorConfig } from '../home-assistant/binary-sensor-config'; import { Gpio } from 'onoff'; import { mocked } from 'ts-jest/utils'; import { ClusterService } from '../../cluster/cluster.service'; +import { GpioSwitch } from './gpio.switch'; jest.mock('onoff'); @@ -43,7 +44,7 @@ describe('GpioService', () => { it('should register entities on bootstrap', () => { service.onApplicationBootstrap(); - expect(entitiesService.add).toHaveBeenCalledTimes(2); + expect(entitiesService.add).toHaveBeenCalledTimes(3); expect(entitiesService.add).toHaveBeenCalledWith( new BinarySensor('gpio-pir-sensor', 'PIR Sensor'), expect.any(Array) @@ -52,6 +53,10 @@ describe('GpioService', () => { new BinarySensor('gpio-radar', 'Radar'), expect.any(Array) ); + expect(entitiesService.add).toHaveBeenCalledWith( + new GpioSwitch('gpio-test-switch', 'Test Switch', expect.any(Gpio)), + expect.any(Array) + ); }); it('should pass on entity customizations', () => { @@ -65,6 +70,12 @@ describe('GpioService', () => { }); }); + it('should export the switches as output pins', () => { + service.onApplicationBootstrap(); + + expect(mockGpio).toHaveBeenCalledWith(17, 'out'); + }); + it('should export the binary sensors as input pins', () => { service.onApplicationBootstrap(); diff --git a/src/integrations/gpio/gpio.service.ts b/src/integrations/gpio/gpio.service.ts index fbe5f23..f1f995a 100644 --- a/src/integrations/gpio/gpio.service.ts +++ b/src/integrations/gpio/gpio.service.ts @@ -15,6 +15,9 @@ import { BinarySensorConfig, BinarySensorDeviceClass } from '../home-assistant/binary-sensor-config'; +import { SwitchConfig } from '../home-assistant/switch-config'; +import { GpioSwitch } from './gpio.switch'; +import { Switch } from '../../entities/switch'; @Injectable() export class GpioService @@ -42,6 +45,14 @@ export class GpioService binarySensor.deviceClass ); }); + + this.config.switches.forEach(switchOptions => { + this.createSwitch( + switchOptions.name, + switchOptions.pin, + switchOptions.icon + ); + }); } /** @@ -95,4 +106,32 @@ export class GpioService return binarySensor; } + + /** + * Creates a new switch that controls a GPIO output. + * + * @param name - Friendly name of the switch + * @param pin - GPIO pin to output to + * @param icon - Icon to use + * @returns Registered switch + */ + protected createSwitch(name: string, pin: number, icon?: string): Switch { + const id = makeId(`gpio ${name}`); + const customizations: Array> = [ + { + for: SwitchConfig, + overrides: { + icon + } + } + ]; + + const gpio = new Gpio(pin, 'out'); + this.gpios.push(gpio); + + return this.entitiesService.add( + new GpioSwitch(id, name, gpio), + customizations + ) as Switch; + } } diff --git a/src/integrations/gpio/gpio.switch.spec.ts b/src/integrations/gpio/gpio.switch.spec.ts new file mode 100644 index 0000000..3ecbd52 --- /dev/null +++ b/src/integrations/gpio/gpio.switch.spec.ts @@ -0,0 +1,28 @@ +import { Gpio } from 'onoff'; +import { GpioSwitch } from './gpio.switch'; + +jest.mock('onoff'); + +describe('GpioSwitch', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should write 1 to the GPIO pin when turned on', async () => { + const pin = new Gpio(17, 'out'); + const gpioSwitch = new GpioSwitch('test', 'Test', pin); + + await gpioSwitch.turnOn(); + expect(pin.write).toHaveBeenCalledWith(1); + expect(gpioSwitch.state).toBeTruthy(); + }); + + it('should write 0 to the GPIO pin when turned off', async () => { + const pin = new Gpio(17, 'out'); + const gpioSwitch = new GpioSwitch('test', 'Test', pin); + + await gpioSwitch.turnOff(); + expect(pin.write).toHaveBeenCalledWith(0); + expect(gpioSwitch.state).toBeFalsy(); + }); +}); diff --git a/src/integrations/gpio/gpio.switch.ts b/src/integrations/gpio/gpio.switch.ts new file mode 100644 index 0000000..9e489b2 --- /dev/null +++ b/src/integrations/gpio/gpio.switch.ts @@ -0,0 +1,21 @@ +import { Switch } from '../../entities/switch'; +import { Gpio } from 'onoff'; + +export class GpioSwitch extends Switch { + private gpio: Gpio; + + constructor(id: string, name: string, gpio: Gpio) { + super(id, name, false); + this.gpio = gpio; + } + + async turnOn(): Promise { + await this.gpio.write(1); + super.turnOn(); + } + + async turnOff(): Promise { + await this.gpio.write(0); + super.turnOff(); + } +}