Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a config setting to disable use of GPIO pins. #152

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions callattendant/app.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ VOICE_MAIL_MENU_FILE = "resources/voice_mail_menu.wav"
# This should not be changed/overridden except during development/testing
#VOICE_MAIL_MESSAGE_FOLDER = "messages"

# Disable use of GPIO pins for displaying indicators. If custom board is not installed, this should be set to True
# to prevent conflicts with other uses of GPIO pins.
GPIO_DISABLED = False

# GPIO_LED_..._PIN: These values are the GPIO pin numbers attached to the LED indicators
# GPIO_LED_..._BRIGHTNESS: These values are a percentage of brightness for the LED indicators when on.
GPIO_LED_RING_PIN = 14
Expand Down
6 changes: 4 additions & 2 deletions callattendant/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ def __init__(self, config):
# Initialize the visual indicators (LEDs)
self.approved_indicator = ApprovedIndicator(
self.config.get("GPIO_LED_APPROVED_PIN"),
self.config.get("GPIO_LED_APPROVED_BRIGHTNESS", 100))
self.config.get("GPIO_LED_APPROVED_BRIGHTNESS", 100),
self.config.get("GPIO_DISABLED", False))
self.blocked_indicator = BlockedIndicator(
self.config.get("GPIO_LED_BLOCKED_PIN"),
self.config.get("GPIO_LED_BLOCKED_BRIGHTNESS", 100))
self.config.get("GPIO_LED_BLOCKED_BRIGHTNESS", 100),
self.config.get("GPIO_DISABLED", False))
# Create (and open) the modem
self.modem = Modem(self.config)
self.config["MODEM_ONLINE"] = self.modem.is_open # signal the webapp not online
Expand Down
3 changes: 3 additions & 0 deletions callattendant/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"VOICE_MAIL_MENU_FILE": "resources/voice_mail_menu.wav",
"VOICE_MAIL_MESSAGE_FOLDER": "messages",

"GPIO_DISABLED": False,
"GPIO_LED_RING_PIN": 14,
"GPIO_LED_RING_BRIGHTNESS": 100,
"GPIO_LED_APPROVED_PIN": 15,
Expand Down Expand Up @@ -176,6 +177,8 @@ def validate(self):
if not isinstance(self["BLOCK_ENABLED"], bool):
print("* BLOCK_ENABLED should be a bool: {}".format(type(self["BLOCK_ENABLED"])))
success = False
if not isinstance(self["GPIO_DISABLED"], bool):
print("* GPIO_DISABLED should be a bool: {}".format(type(self["GPIO_DISABLED"])))

for mode in self["SCREENING_MODE"]:
if mode not in ("whitelist", "blacklist"):
Expand Down
31 changes: 20 additions & 11 deletions callattendant/hardware/indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
# See: https://gpiozero.readthedocs.io/en/stable/
# See: https://gpiozero.readthedocs.io/en/stable/api_output.html#led

from gpiozero import LED, PWMLED, LEDBoard, OutputDeviceError, LEDCollection
from gpiozero import Device, LED, PWMLED, LEDBoard, OutputDeviceError, LEDCollection
from gpiozero.pins.mock import MockFactory, MockPWMPin

GPIO_RING = 14
GPIO_APPROVED = 15
Expand Down Expand Up @@ -223,14 +224,18 @@ class PWMLEDIndicator(object):
A pulse-width modulated LED.
"""

def __init__(self, gpio_pin, brightness=100):
def __init__(self, gpio_pin, brightness=100, gpio_disabled=False):
"""
Constructor of a PWM LED.
:param gpio_pin:
GPIO pin assignment (not the header pin number)
:param brightness:
Brightness percentage. Defaults to 100%.
"""
if gpio_disabled:
# Use 'fake' pins so rest of code can still be called, but with no hardware interaction
# https://gpiozero.readthedocs.io/en/stable/api_pins.html#mock-pins
Device.pin_factory = MockFactory(pin_class=MockPWMPin)
self.led = PWMLED(gpio_pin)
self.brightness = brightness / 100.0 # brightness value is from 0 to 1.0

Expand Down Expand Up @@ -258,8 +263,8 @@ class RingIndicator(PWMLEDIndicator):
"""
The ring indicator, activated when an incoming call is being received.
"""
def __init__(self, gpio_pin=GPIO_RING, brightness=100):
super().__init__(gpio_pin, brightness)
def __init__(self, gpio_pin=GPIO_RING, brightness=100, gpio_disabled=False):
super().__init__(gpio_pin, brightness, gpio_disabled)

def ring(self):
self.blink()
Expand All @@ -270,26 +275,26 @@ class ApprovedIndicator(PWMLEDIndicator):
"""
The approved indicator activated when a call from a permitted number is received.
"""
def __init__(self, gpio_pin=GPIO_APPROVED, brightness=100):
super().__init__(gpio_pin, brightness)
def __init__(self, gpio_pin=GPIO_APPROVED, brightness=100, gpio_disabled=False):
super().__init__(gpio_pin, brightness, gpio_disabled)


class BlockedIndicator(PWMLEDIndicator):
"""
The blocked indicator activated when a call from a blocked number is received.
"""

def __init__(self, gpio_pin=GPIO_BLOCKED, brightness=100):
super().__init__(gpio_pin, brightness)
def __init__(self, gpio_pin=GPIO_BLOCKED, brightness=100, gpio_disabled=False):
super().__init__(gpio_pin, brightness, gpio_disabled)


class MessageIndicator(PWMLEDIndicator):
"""
The message indicator activated when the voice messaging features are used.
"""

def __init__(self, gpio_pin=GPIO_MESSAGE, brightness=100):
super().__init__(gpio_pin, brightness)
def __init__(self, gpio_pin=GPIO_MESSAGE, brightness=100, gpio_disabled=False):
super().__init__(gpio_pin, brightness, gpio_disabled)

def turn_off(self):
print("{MSG LED OFF}")
Expand All @@ -312,7 +317,11 @@ class MessageCountIndicator(object):
"""
The message count indicator displays the number of unplayed messages in the system.
"""
def __init__(self, *pins, **kwargs):
def __init__(self, gpio_disabled, *pins, **kwargs):
if gpio_disabled:
# Use 'fake' pins so rest of code can still be called, but with no hardware interaction
# https://gpiozero.readthedocs.io/en/stable/api_pins.html#mock-pins
Device.pin_factory = MockFactory()
if len(pins) > 0:
self.seven_seg = SevenSegmentDisplay(*pins, **kwargs)
else:
Expand Down
3 changes: 2 additions & 1 deletion callattendant/hardware/modem.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ def __init__(self, config):
# Ring notifications
self.ring_indicator = RingIndicator(
self.config.get("GPIO_LED_RING_PIN"),
self.config.get("GPIO_LED_RING_BRIGHTNESS", 100))
self.config.get("GPIO_LED_RING_BRIGHTNESS", 100),
self.config.get("GPIO_DISABLED", False))
self.ring_event = threading.Event()

# Initialize the serial port attached to the physical modem
Expand Down
6 changes: 4 additions & 2 deletions callattendant/messaging/voicemail.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ def __init__(self, db, config, modem):
# Initialize the message indicators (LEDs)
self.message_indicator = MessageIndicator(
self.config.get("GPIO_LED_MESSAGE_PIN", GPIO_MESSAGE),
self.config.get("GPIO_LED_MESSAGE_BRIGHTNESS", 100))
self.config.get("GPIO_LED_MESSAGE_BRIGHTNESS", 100),
self.config.get("GPIO_DISABLED", False)
)
pins = self.config.get("GPIO_LED_MESSAGE_COUNT_PINS", GPIO_MESSAGE_COUNT_PINS)
kwargs = self.config.get("GPIO_LED_MESSAGE_COUNT_KWARGS", GPIO_MESSAGE_COUNT_KWARGS)
self.message_count_indicator = MessageCountIndicator(*pins, **kwargs)
self.message_count_indicator = MessageCountIndicator(self.config.get("GPIO_DISABLED", False), *pins, **kwargs)

# Create the Message object used to interface with the DB
self.messages = Message(db, config)
Expand Down
Empty file added integration_tests/__init__.py
Empty file.
94 changes: 94 additions & 0 deletions integration_tests/test_indicators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# test_indicators.py
#
# Copyright 2020 Bruce Schubert <[email protected]>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os
import time

import pytest

from config import Config
from callattendant.hardware.indicators import RingIndicator, ApprovedIndicator, BlockedIndicator, \
MessageIndicator, MessageCountIndicator

def test_multiple():

config = Config()

ringer = RingIndicator(config["GPIO_LED_RING_PIN"], brightness=100, gpio_disabled=False)
approved = ApprovedIndicator(config["GPIO_LED_APPROVED_PIN"], brightness=25, gpio_disabled=False)
blocked = BlockedIndicator(config["GPIO_LED_BLOCKED_PIN"], brightness=25, gpio_disabled=False)
message = MessageIndicator(config["GPIO_LED_MESSAGE_PIN"], brightness=100, gpio_disabled=False)

pins_tuple = config["GPIO_LED_MESSAGE_COUNT_PINS"]
kwargs_dict = config["GPIO_LED_MESSAGE_COUNT_KWARGS"]
message_count = MessageCountIndicator(False, *pins_tuple, **kwargs_dict)

# ~ ringer = RingIndicator()
# ~ approved = ApprovedIndicator()
# ~ blocked = BlockedIndicator()
# ~ message = MessageIndicator()
# ~ message_count = MessageCountIndicator()

for i in range(0, 16):
message_count.display_hex(i)
time.sleep(.5)

print("[Visual Tests]")

print("Turning on all LEDs for 5 seconds...")
ringer.turn_on()
approved.turn_on()
blocked.turn_on()
message.turn_on()
time.sleep(5)

print("Blinking on all LEDs for 5 seconds...")
ringer.blink()
time.sleep(.1)
approved.blink()
time.sleep(.1)
blocked.blink()
time.sleep(.1)
message.blink()
time.sleep(5)

print("Turning off all LEDs...")
ringer.turn_off()
approved.turn_off()
blocked.turn_off()
message.turn_off()
time.sleep(2)

print("Test normal status")
ringer.ring()
message.pulse(),
message_count.display(2)
time.sleep(10)

# Release GPIO pins
ringer.close()
approved.close()
blocked.close()
message.close()
File renamed without changes.
22 changes: 5 additions & 17 deletions tests/test_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,18 @@
from callattendant.hardware.indicators import RingIndicator, ApprovedIndicator, BlockedIndicator, \
MessageIndicator, MessageCountIndicator

# Skip the test when running under continous integraion
pytestmark = pytest.mark.skipif(os.getenv("CI") == "true", reason="Hardware not installed")


def test_multiple():

config = Config()

ringer = RingIndicator(config["GPIO_LED_RING_PIN"], brightness=100)
approved = ApprovedIndicator(config["GPIO_LED_APPROVED_PIN"], brightness=25)
blocked = BlockedIndicator(config["GPIO_LED_BLOCKED_PIN"], brightness=25)
message = MessageIndicator(config["GPIO_LED_MESSAGE_PIN"], brightness=100)
ringer = RingIndicator(config["GPIO_LED_RING_PIN"], brightness=100, gpio_disabled=True)
approved = ApprovedIndicator(config["GPIO_LED_APPROVED_PIN"], brightness=25, gpio_disabled=True)
blocked = BlockedIndicator(config["GPIO_LED_BLOCKED_PIN"], brightness=25, gpio_disabled=True)
message = MessageIndicator(config["GPIO_LED_MESSAGE_PIN"], brightness=100, gpio_disabled=True)

pins_tuple = config["GPIO_LED_MESSAGE_COUNT_PINS"]
kwargs_dict = config["GPIO_LED_MESSAGE_COUNT_KWARGS"]
message_count = MessageCountIndicator(*pins_tuple, **kwargs_dict)
message_count = MessageCountIndicator(True, *pins_tuple, **kwargs_dict)

# ~ ringer = RingIndicator()
# ~ approved = ApprovedIndicator()
Expand All @@ -57,7 +53,6 @@ def test_multiple():

for i in range(0, 16):
message_count.display_hex(i)
time.sleep(.5)

print("[Visual Tests]")

Expand All @@ -66,30 +61,23 @@ def test_multiple():
approved.turn_on()
blocked.turn_on()
message.turn_on()
time.sleep(5)

print("Blinking on all LEDs for 5 seconds...")
ringer.blink()
time.sleep(.1)
approved.blink()
time.sleep(.1)
blocked.blink()
time.sleep(.1)
message.blink()
time.sleep(5)

print("Turning off all LEDs...")
ringer.turn_off()
approved.turn_off()
blocked.turn_off()
message.turn_off()
time.sleep(2)

print("Test normal status")
ringer.ring()
message.pulse(),
message_count.display(2)
time.sleep(10)

# Release GPIO pins
ringer.close()
Expand Down
10 changes: 7 additions & 3 deletions tests/test_voicemail.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from tempfile import gettempdir

import pytest
from unittest import mock

from callattendant.config import Config
from callattendant.hardware.modem import Modem
Expand All @@ -54,6 +55,7 @@ def config():
config['DEBUG'] = True
config['TESTING'] = True
config['VOICE_MAIL_MESSAGE_FOLDER'] = gettempdir()
config['GPIO_DISABLED'] = True

return config

Expand All @@ -68,7 +70,11 @@ def logger(db, config):
@pytest.fixture(scope='module')
def modem(db, config):

modem = Modem(config)
def _fake_record_audio(filename, detect_silence=True):
_ = open(filename, 'w')
return True
modem = mock.create_autospec(Modem)
modem.record_audio = mock.Mock(side_effect=_fake_record_audio)
yield modem
modem.stop()

Expand All @@ -81,8 +87,6 @@ def voicemail(db, config, modem):
voicemail.stop()


# Skip the test when running under continous integraion
@pytest.mark.skipif(os.getenv("CI") == "true", reason="Hardware not installed")
def test_multiple(voicemail, logger):

call_no = logger.log_caller(caller)
Expand Down