Skip to content
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ dmypy.json
# Pyre type checker
.pyre/

/deps/
/attic/
/pjproject/
.idea
/ha-sip/src/config_local.py
/incoming.yaml
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ Example content of `/config/sip-1-incoming.yaml`:
allowed_numbers: # list of numbers which will be answered. If removed all numbers will be accepted
- "5551234456"
- "5559876543"
- "555{*}" # matches every number starting with 555
- "555{?}" # matches every number starting with 555 which is 4 digits long
# blocked_numbers: # alternatively you can specify the numbers not to be answered. You can't have both.
# - "5551234456"
# - "5559876543"
Expand Down Expand Up @@ -202,14 +204,22 @@ used for incoming and outgoing calls.
menu:
id: main # If "id" is present, a message will be sent via webhook (entered_menu), see below (optional)
message: Please enter your access code # the message to be played via TTS (optional, defaults to empty)
playlist: # (optional)
- type: tts
message: Hello World!
- type: audio_file
audio_file: /config/audio/welcome.mp3'
language: en # TTS language (optional, defaults to the global language from add-on config)
choices_are_pin: true # If the choices should be handled like PINs (optional, defaults to false)
timeout: 10 # time in seconds before "timeout" choice is triggered (optional, defaults to 300)
repeat_wait: 2 # time in seconds to wait before repeating a message/playlist (optional, defaults to 0 seconds)
post_action: noop # this action will be triggered after the message was played. Can be
# "noop" (do nothing),
# "return" (makes only sense in a sub-menu),
# "return <level>" (makes only sense in a sub-menu, returns <level> levels, defaults to 1),
# "hangup" (hang-up the call) and
# "repeat_message" (repeat the message until the time-out is reached)
# "repeat_message" (repeat the message or last playlist item until the time-out is reached)
# "repeat_playlist" (repeat the whole playlist until the time-out is reached)
# "jump <menu-id>" (jumps to menu with id <menu-id>)
# (optional, defaults to noop)
action: # action to run when menu was entered (before playing the message) (optional)
# For details visit https://developers.home-assistant.io/docs/api/rest/, POST on /api/services/<domain>/<service>
Expand All @@ -227,7 +237,7 @@ menu:
post_action: hangup
'7777':
audio_file: '/config/audio/welcome.mp3' # audio file to be played (.wav or .mp3).
post_action: hangup
post_action: jump owner # jump to menu id 'owner'
'default': # this will be triggered if the input does not match any specified choice
id: wrong_code
message: Wrong code, please try again
Expand Down
20 changes: 13 additions & 7 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ if [ -z "$DOCKER_HUB_PASSWORD" ]
exit 1
fi

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)

case "$1" in
build-next)
echo "Building on next repo (aarch64 only)..."
Expand Down Expand Up @@ -58,20 +60,24 @@ case "$1" in
docker pull homeassistant/amd64-builder:dev
;;
test)
echo "Running unit tests..."
python3 -m unittest discover -s "$SCRIPT_DIR"/ha-sip/src
echo "Running type-check..."
pyright ha-sip
;;
run-local)
"$SCRIPT_DIR"/ha-sip/src/main.py local
;;
create-venv)
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
rm -rf $SCRIPT_DIR/venv $SCRIPT_DIR/deps
python3 -m venv $SCRIPT_DIR/venv
source $SCRIPT_DIR/venv/bin/activate
rm -rf "$SCRIPT_DIR"/venv "$SCRIPT_DIR"/deps
python3 -m venv "$SCRIPT_DIR"/venv
source "$SCRIPT_DIR"/venv/bin/activate
pip3 install pydub requests PyYAML typing_extensions
mkdir $SCRIPT_DIR/deps
cd $SCRIPT_DIR/deps || exit
mkdir "$SCRIPT_DIR"/deps
cd "$SCRIPT_DIR"/deps || exit
git clone --depth 1 --branch 2.13 https://github.com/pjsip/pjproject.git
cd pjproject || exit
./configure --enable-shared --disable-libwebrtc --prefix $SCRIPT_DIR/venv
./configure --enable-shared --disable-libwebrtc --prefix "$SCRIPT_DIR"/venv
make
make dep
make install
Expand Down
21 changes: 21 additions & 0 deletions ha-sip/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 2.8
- Added option to play a list of messages and/or audio files
- Added option `repeat_playlist` to `post_action`: repeat whole playlist. `repeat_message` will repeat only the last item in case of a playlist.
- Added option `repeat_wait` for an extra delay between repeated messages (in seconds).
```yaml
menu:
playlist:
- type: tts
message: "Hello World!"
- type: audio_file
audio_file: "/config/audio/welcome.mp3"
post_action: "repeat_playlist"
repeat_wait: 2
```

## 2.7
- More flexible `return` post action: specify how many levels to go back from the sub-menu
- Added `jump` post action to jump to any menu with an id
- Add wildcard support for incoming call `allowed_numbers` and `blocked_numbers` filter
- Bugfix: time-out not reset when returning to parent menu

## 2.6
- Call additional web-hooks for incoming and outgoing calls
#### Deprecation notice: `webhook_to_call_after_call_was_established` will be removed in the next release and is replaced by the more granular `webhook_to_call`.
Expand Down
2 changes: 1 addition & 1 deletion ha-sip/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ha-sip",
"version": "2.6",
"version": "2.8",
"slug": "ha-sip",
"url": "https://github.com/arnonym/ha-plugins",
"description": "Home-Assistant SIP Gateway",
Expand Down
24 changes: 22 additions & 2 deletions ha-sip/src/account.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations
import re

from typing import Optional

Expand Down Expand Up @@ -98,11 +99,30 @@ def get_sip_return_code(
log(self.config.index, 'Error: cannot specify both of allowed and blocked numbers. Call won\'t be accepted!')
return call.CallHandling.LISTEN
if mode == call.CallHandling.ACCEPT and allowed_numbers:
return call.CallHandling.ACCEPT if parsed_caller in allowed_numbers else call.CallHandling.LISTEN
return call.CallHandling.ACCEPT if Account.is_number_in_list(parsed_caller, allowed_numbers) else call.CallHandling.LISTEN
if mode == call.CallHandling.ACCEPT and blocked_numbers:
return call.CallHandling.ACCEPT if parsed_caller not in blocked_numbers else call.CallHandling.LISTEN
return call.CallHandling.ACCEPT if not Account.is_number_in_list(parsed_caller, blocked_numbers) else call.CallHandling.LISTEN
return mode

@staticmethod
def is_number_in_list(number: Optional[str], number_list: list[str]) -> bool:
def map_to_regex(st: str) -> str:
if st == '{*}':
return '.*'
if st == '{?}':
return '.'
return re.escape(st)
if not number:
return False
for n in number_list:
# split by {*} and {?} keeping delimiters
n_split = re.split(r'(\{\*}|\{\?})', n)
n_regex = '^' + ''.join(map(map_to_regex, n_split)) + '$'
match = re.match(n_regex, number)
if match:
return True
return False


def create_account(end_point: pj.Endpoint, config: MyAccountConfig, callback: call.CallCallback, ha_config: ha.HaConfig, is_default: bool) -> Account:
account = Account(end_point, config, callback, ha_config, is_default)
Expand Down
4 changes: 2 additions & 2 deletions ha-sip/src/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pydub


def convert_audio_to_wav(audio_file_name: str) -> Optional[str]:
def convert_audio_to_wav(audio_file_name: str, parameters: Optional[list] = None) -> Optional[str]:
def get_audio_segment(file_name: str) -> Optional[pydub.AudioSegment]:
_, file_extension = os.path.splitext(file_name)
if file_extension == '.mp3':
Expand All @@ -25,7 +25,7 @@ def get_audio_segment(file_name: str) -> Optional[pydub.AudioSegment]:
if not audio_segment:
print('Error: could not figure out file format (.mp3, .ogg, .wav is supported):', audio_file_name)
return None
audio_segment.export(wave_file_handler.name, format='wav')
audio_segment.export(wave_file_handler.name, format='wav', parameters=parameters if parameters else None)
return wave_file_handler.name


Expand Down
Loading