Skip to content

Commit

Permalink
Use caller's name from Permitted or Blocked tables for MQTT report only.
Browse files Browse the repository at this point in the history
Add config option to control MQTT publish retain setting (EVENT or STATE).
Update service example - improves logging
Bump version to 2.0.4
  • Loading branch information
thess committed May 12, 2024
1 parent 20ccc3d commit 7b59486
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 35 deletions.
26 changes: 17 additions & 9 deletions MQTT-Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
Base-topic default is **callattendant**. Indicator names/subtopics are one of:<br>
&nbsp;&nbsp;&nbsp;&nbsp;`RING, Approved, Blocked, Message, MessageCount`

`LastCallerID` is a separate subtopic and does not have a hardware/GPIO equivalent indicator.
`CallerID` is a separate subtopic and does not have a hardware/GPIO equivalent indicator.

Examples:
```
callattendant/Approved {"TimeStamp": 1714510621, "State": "BLINK", "Period": 700, "Count": 2}
callattendant/LastCallerID {"TimeStamp": 1714512345, "Number": 6175551234, "Name": Unknown, "Action": "Screened", "Reason": "")
callattendant/CallerID {"TimeStamp": 1714512345, "Number": 6175551234, "Name": Unknown, "Action": "Screened", "Reason": "")
```
JSON formatted messages:

Expand All @@ -23,18 +23,26 @@ Callattendant MQTT message templates:
- Count := Number of cycles (Period * Count)
- State := ON, OFF, CLOSED, BLINK, PULSE or count of messages

**LastCallerID**: {"TimeStamp": 0, "Number": "", "Name": "", "Action": "", "Reason": ""}
**CallerID**: {"TimeStamp": 0, "Number": "", "Name": "", "Action": "", "Reason": ""}

- Number may be raw/internal or formatted text.
- Caller Name will be taken from the CallerID supplied by your provider unless the Number is found in either the Permitted or Blocked callers lists. In that case, the Name will be from the DB entry found.

Config file additions:
```
#MQTT_TIME_FORMAT: The format of the timestamp in the MQTT message. Valid values are: ISO, UNIX
# ISO = "YYYY-MM-DD HH:MM:SS"
# UNIX = Seconds since epoch (Usually: 1970-01-01 00:00:00)
# MQTT_TIME_FORMAT: The format of the timestamp in the MQTT message. Valid values are: ISO, UNIX
# ISO = "YYYY-MM-DD HH:MM:SS"
# UNIX = Seconds since epoch (Usually: 1970-01-01 00:00:00)
MQTT_TIME_FORMAT = "UNIX"
#MQTT_CALLERID_FORMAT: The format of the callerid in the MQTT message. Valid values are: RAW, DISPLAY
# DISPLAY uses the PHONE_DISPLAY_FORMAT to format the number
# RAW uses the number from the provider
# MQTT_CALLERID_FORMAT: The format of the callerid in the MQTT message. Valid values are: RAW, DISPLAY
# DISPLAY uses the PHONE_DISPLAY_FORMAT to format the number
# RAW uses the number from the provider
MQTT_CALLERID_FORMAT = "RAW"
# MQTT_NOTIFICATION_TYPE: The type of notification to send. Valid values are: EVENT, STATE
# This setting controls the RETAIN attribute of MQTT messages.
# EVENT: Indicator topics are not retained
# STATE: Indicator topics are retained
MQTT_INDICATOR_TYPE = "STATE"
```
7 changes: 6 additions & 1 deletion bin/callattendant.service.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
[Unit]
Description=Call Attendant
After=multi-user.target
# May be necessary if using GPIO on Raspberry Pi
#Requires=pigpiod.service

[Service]
Type=simple
ExecStart=callattendant --config /home/pi/.callattendant/app.cfg
StandardOutput=journal
StandardError=inherit
ExecStart=/home/pi/venv/bin/python -u /home/pi/venv/bin/callattendant --config /home/pi/.callattendant/app.cfg
WorkingDirectory=/home/pi/.callattendant
User=pi
Restart=on-abort

[Install]
Expand Down
17 changes: 12 additions & 5 deletions callattendant/app.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,22 @@ MQTT_TOPIC_PREFIX = "callattendant"
# Port is optional, default is 1883
#MQTT_PORT = 1883
# Username is optional, default is None
#MQTT_USERNAME = None
#MQTT_PASSWORD = None
#MQTT_USERNAME = ""
#MQTT_PASSWORD = ""

# MQTT_NOTIFICATION_TYPE: The type of notification to send. Valid values are: EVENT, STATE
# This setting controls the RETAIN attribute of MQTT messages.
# EVENT: Indicator topics are not retained
# STATE: Indicator topics are retained
MQTT_INDICATOR_TYPE = "STATE"

#MQTT_TIME_FORMAT: The format of the timestamp in the MQTT message. Valid values are: ISO, UNIX
# ISO = "YYYY-MM-DD HH:MM:SS"
# UNIX = Seconds since epoch (Usually: 1970-01-01 00:00:00)
MQTT_TIME_FORMAT = "UNIX"
#MQTT_CALLERID_FORMAT: The format of the callerid in the MQTT message. Valid values are: RAW, DISPLAY
# DISPLAY uses the PHONE_DISPLAY_FORMAT to format the number
# RAW uses the number from the provider
# MQTT_CALLERID_FORMAT: The format of the callerid in the MQTT message. Valid values are: RAW, DISPLAY
# DISPLAY uses the PHONE_DISPLAY_FORMAT to format the number
# RAW uses the number from the provider
MQTT_CALLERID_FORMAT = "RAW"

# GPIO_LED_..._PIN: These values are the GPIO pin numbers attached to the LED indicators
Expand Down
14 changes: 10 additions & 4 deletions callattendant/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ def run(self):

# An incoming call has occurred, log it
number = caller["NMBR"]
# Default to provider name
displayName = caller["NAME"]
print("Incoming call from {}".format(number))

# Vars used in the call screening
Expand All @@ -197,34 +199,38 @@ def run(self):
if not caller_permitted and "whitelist" in screening_mode:
print("> Checking whitelist(s)")
is_whitelisted, result = self.screener.is_whitelisted(caller)
# If true, result is a tuple (reason, name)
if is_whitelisted:
caller_permitted = True
action = "Permitted"
reason = result[0]
if result[1] is not None:
caller["NAME"] = result[1]
displayName = result[1]
self.approved_indicator.blink()

# Now check the blacklist if not preempted by whitelist
if not caller_permitted and "blacklist" in screening_mode:
print("> Checking blacklist(s)")
is_blacklisted, result = self.screener.is_blacklisted(caller)
# If true, result is a tuple (reason, name)
if is_blacklisted:
caller_blocked = True
action = "Blocked"
reason = result[0]
if result[1] is not None:
caller["NAME"] = result[1]
displayName = result[1]
self.blocked_indicator.blink()

if not caller_permitted and not caller_blocked:
caller_screened = True
action = "Screened"

# Log every call to the database (and console)
# Log every call to the database, console and optionally MQTT
# Caller log has provider name and number
call_no = self.logger.log_caller(caller, action, reason)
if (self.callerid_indicator is not None):
self.callerid_indicator.display(caller, action, reason)
# Use displayName for the caller name if found in the whitelist or blacklist
self.callerid_indicator.display(displayName, number, action, reason)
print("--> {} {}: {}".format(number, action, reason))

# Gather the data used to answer the call
Expand Down
11 changes: 7 additions & 4 deletions callattendant/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# and screened callers through to the home phone.
#
default_config = {
"VERSION": '2.0.3',
"VERSION": '2.0.4',

"DEBUG": False,
"TESTING": False,
Expand Down Expand Up @@ -89,11 +89,11 @@
"MQTT_BROKER": "localhost",
"MQTT_PORT": 1883,
"MQTT_TOPIC_PREFIX": "callattendant",
"MQTT_USERNAME": "",
"MQTT_PASSWORD": "",
"MQTT_USERNAME": None,
"MQTT_PASSWORD": None,
"MQTT_TIME_FORMAT": "UNIX",
"MQTT_CALLERID_FORMAT": "RAW",

"MQTT_INDICATOR_TYPE": "STATE",
}

CID_PATTERNS_DEFAULT_STRING = b"blocknames: {}\nblocknumbers: {}\n" \
Expand Down Expand Up @@ -255,6 +255,9 @@ def validate(self):
if self["MQTT_TIME_FORMAT"] not in ("UNIX", "ISO"):
print("* MQTT_TIME_FORMAT is invalid: {}".format(self["MQTT_TIME_FORMAT"]))
success = False
if self["MQTT_INDICATOR_TYPE"] not in ("STATE", "EVENT"):
print("* MQTT_INDICATOR_TYPE is invalid: {}".format(self["MQTT_INDICATOR_TYPE"]))
success = False

if not isinstance(self["BLOCKED_RINGS_BEFORE_ANSWER"], int):
print("* BLOCKED_RINGS_BEFORE_ANSWER should be an integer: {}".format(type(self["BLOCKED_RINGS_BEFORE_ANSWER"])))
Expand Down
22 changes: 12 additions & 10 deletions callattendant/hardware/mqttindicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def __init__(self, config):
# Formatting options
self.time_format = self.config['MQTT_TIME_FORMAT']
self.callerid_format = self.config['MQTT_CALLERID_FORMAT']
# Retension option
self.retain = True if self.config['MQTT_INDICATOR_TYPE'] == 'STATE' else False

# Create client root name
self.topic_prefix = self.config['MQTT_TOPIC_PREFIX'] + "/"
Expand Down Expand Up @@ -102,7 +104,7 @@ def queue_indicator(self, topic, state, period=0, count=0):
message['Count'] = count
self.mqtt_queue.put_nowait((topic, json.dumps(message)))

def queue_callerid(self, topic, caller, action, reason):
def queue_callerid(self, topic, name, number, action, reason):
"""
Queue a message to be published.
"""
Expand All @@ -111,14 +113,14 @@ def queue_callerid(self, topic, caller, action, reason):
else:
ts = int(time.time())

number = caller['NMBR']
displayNumber = number
if (self.callerid_format == "DISPLAY"):
number = format_phone_no(number, self.config)
displayNumber = format_phone_no(number, self.config)

message = {}
message['TimeStamp'] = ts
message['Name'] = caller['NAME']
message['Number'] = number
message['Name'] = name
message['Number'] = displayNumber
message['Action'] = action
message['Reason'] = reason
self.mqtt_queue.put_nowait((topic, json.dumps(message)))
Expand All @@ -138,7 +140,7 @@ def publish(self, topic, message):
if self.username is not None:
client.username_pw_set(self.username, self.password)
client.connect(self.server, self.port)
client.publish(self.topic_prefix + topic, message, retain=True)
client.publish(self.topic_prefix + topic, message, retain=self.retain)
client.disconnect()


Expand Down Expand Up @@ -251,13 +253,13 @@ def decimal_point(self, value):

class MQTTCallerIdIndicator(MQTTIndicator):
"""
The last caller ID indicator displays the last caller ID received.
The caller ID indicator displays the last caller ID received.
"""
def __init__(self):
super().__init__('LastCallerID', None)
super().__init__('CallerID', None)

def display(self, caller, action="Screened", reason=""):
def display(self, name, number, action="Screened", reason=""):
"""
Displays the last caller ID received.
"""
mqtt_client.queue_callerid(self.topic, caller, action, reason)
mqtt_client.queue_callerid(self.topic, name, number, action, reason)
1 change: 1 addition & 0 deletions callattendant/screening/blacklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def check_number(self, number):
args = {"number": number}
results = query_db(self.db, query, args, False)
if len(results) > 0:
# Return tuple (Reason, Name)
return True, (results[0][1], results[0][0])
else:
return False, None
Expand Down
1 change: 1 addition & 0 deletions callattendant/screening/whitelist.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def check_number(self, number):
args = {"number": number}
results = query_db(self.db, query, args, False)
if len(results) > 0:
# Return tuple (Reason, Name)
return True, (results[0][1], results[0][0])
else:
return False, None
Expand Down
1 change: 0 additions & 1 deletion callattendant/userinterface/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ def calls():
"""

# Get GET request args, if available
number = request.args.get('number')
search_text = request.args.get('search')
search_type = request.args.get('submit')

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

setuptools.setup(
name="callattendant", # Product name on PyPi (Callattendant2)
version="2.0.3", # Ensure this is in-sync with VERSION in config.py
version="2.0.4", # Ensure this is in-sync with VERSION in config.py
author="Ted Hess",
author_email="[email protected]",
description="An automated call attendant and call blocker using a USR5637 or CX930xx modem",
Expand Down

0 comments on commit 7b59486

Please sign in to comment.