diff --git a/contrib/heartbeat-monitor/PyHeartBeat.py b/contrib/heartbeat-monitor/PyHeartBeat.py
old mode 100644
new mode 100755
index 5b3e495dbdcbc4797170fa1d1f4e1dd0c7470473..0115d99bcf4986c52ca651fe56b945b4b6268bbb
--- a/contrib/heartbeat-monitor/PyHeartBeat.py
+++ b/contrib/heartbeat-monitor/PyHeartBeat.py
@@ -1,19 +1,19 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python3
 
 # Copyright (c) 2001, Nicola Larosa
 # All rights reserved.
-# Redistribution and use in source and binary forms, with or without 
-# modification, are permitted provided that the following conditions 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
 # are met:
-#    * Redistributions of source code must retain the above copyright 
-#      notice, this list of conditions and the following disclaimer. 
+#    * Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
 #    * Redistributions in binary form must reproduce the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer in the documentation and/or other materials provided
-#      with the distribution. 
-#    * Neither the name of the <ORGANIZATION> nor the names of its 
-#      contributors may be used to endorse or promote products derived 
-#      from this software without specific prior written permission. 
+#      with the distribution.
+#    * Neither the name of the <ORGANIZATION> nor the names of its
+#      contributors may be used to endorse or promote products derived
+#      from this software without specific prior written permission.
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
@@ -34,54 +34,72 @@ clients that sent at least one packet during the run, but have
 not sent any packet since a time longer than the definition of the timeout.
 
 Adjust the constant parameters as needed, or call as:
-    PyHBServer.py [timeout [udpport]]
+
+    PyHeartBeat.py [udpport [timeout]]
+
+Set the environment variable "DEBUG" to "1" in order to emit more detailed
+debug messages.
+In addition "127.0.0.1" is marked as a previously active peer.
+
+
+Manual heartbeat messages can be easily sent via "netcat":
+
+    echo foo | nc -q 1 -u localhost 43334
+
 
 https://www.oreilly.com/library/view/python-cookbook/0596001673/ch10s13.html
 """
 
-HBPORT = 43334
-CHECKWAIT = 10
-
-from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
+import os
+import socket
+import sys
 from threading import Lock, Thread, Event
 from time import time, ctime, sleep
-import sys
+
+
+DEFAULT_HEARTBEAT_PORT = 43334
+DEFAULT_WAIT_PERIOD = 10
+DEBUG_ENABLED = os.getenv("DEBUG", "0") == "1"
+
 
 class BeatDict:
     "Manage heartbeat dictionary"
 
     def __init__(self):
         self.beatDict = {}
-        if __debug__:
-            self.beatDict['127.0.0.1'] = time(  )
-        self.dictLock = Lock(  )
+        if DEBUG_ENABLED:
+            self.beatDict["127.0.0.1"] = time()
+        self.dictLock = Lock()
 
     def __repr__(self):
-        list = ''
-        self.dictLock.acquire(  )
-        for key in self.beatDict.keys(  ):
-            list = "%sIP address: %s - Last time: %s\n" % (
-                list, key, ctime(self.beatDict[key]))
-        self.dictLock.release(  )
-        return list
+        result = ""
+        self.dictLock.acquire()
+        for key in self.beatDict.keys():
+            result += "IP address: %s - Last time: %s\n" % (
+                key,
+                ctime(self.beatDict[key]),
+            )
+        self.dictLock.release()
+        return result
 
     def update(self, entry):
         "Create or update a dictionary entry"
-        self.dictLock.acquire(  )
-        self.beatDict[entry] = time(  )
-        self.dictLock.release(  )
+        self.dictLock.acquire()
+        self.beatDict[entry] = time()
+        self.dictLock.release()
 
     def extractSilent(self, howPast):
         "Returns a list of entries older than howPast"
         silent = []
-        when = time(  ) - howPast
-        self.dictLock.acquire(  )
-        for key in self.beatDict.keys(  ):
+        when = time() - howPast
+        self.dictLock.acquire()
+        for key in self.beatDict.keys():
             if self.beatDict[key] < when:
                 silent.append(key)
-        self.dictLock.release(  )
+        self.dictLock.release()
         return silent
 
+
 class BeatRec(Thread):
     "Receive UDP packets, log them in heartbeat dictionary"
 
@@ -90,52 +108,62 @@ class BeatRec(Thread):
         self.goOnEvent = goOnEvent
         self.updateDictFunc = updateDictFunc
         self.port = port
-        self.recSocket = socket(AF_INET, SOCK_DGRAM)
-        self.recSocket.bind(('', port))
+        self.recSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        self.recSocket.settimeout(0.2)
+        self.recSocket.bind(("", port))
 
     def __repr__(self):
-        return "Heartbeat Server on port: %d\n" % self.port
+        return f"Heartbeat Server on port: {self.port}"
 
     def run(self):
-        while self.goOnEvent.isSet(  ):
-            if __debug__:
-                print "Waiting to receive..."
-            data, addr = self.recSocket.recvfrom(6)
-            if __debug__:
-                print "Received packet from " + `addr`
-            self.updateDictFunc(addr[0])
-
-def main(  ):
+        while self.goOnEvent.isSet():
+            if DEBUG_ENABLED:
+                print("Waiting to receive...")
+            try:
+                data, addr = self.recSocket.recvfrom(6)
+            except socket.timeout:
+                # no incoming message -> no timestamp update -> check again
+                pass
+            else:
+                if DEBUG_ENABLED:
+                    print(f"Received packet from {addr}")
+                self.updateDictFunc(addr[0])
+
+
+def main():
     "Listen to the heartbeats and detect inactive clients"
-    global HBPORT, CHECKWAIT
-    if len(sys.argv)>1:
-        HBPORT=sys.argv[1]
-    if len(sys.argv)>2:
-        CHECKWAIT=sys.argv[2]
-
-    beatRecGoOnEvent = Event(  )
-    beatRecGoOnEvent.set(  )
-    beatDictObject = BeatDict(  )
-    beatRecThread = BeatRec(beatRecGoOnEvent, beatDictObject.update, HBPORT)
-    if __debug__:
-        print beatRecThread
-    beatRecThread.start(  )
-    print "PyHeartBeat server listening on port %d" % HBPORT
-    print "\n*** Press Ctrl-C to stop ***\n"
-    while 1:
+    if len(sys.argv) > 1:
+        heartbeat_port = int(sys.argv[1])
+    else:
+        heartbeat_port = DEFAULT_HEARTBEAT_PORT
+    if len(sys.argv) > 2:
+        wait_period = float(sys.argv[2])
+    else:
+        wait_period = DEFAULT_WAIT_PERIOD
+
+    beatRecGoOnEvent = Event()
+    beatRecGoOnEvent.set()
+    beatDictObject = BeatDict()
+    beatRecThread = BeatRec(beatRecGoOnEvent, beatDictObject.update, heartbeat_port)
+    if DEBUG_ENABLED:
+        print(beatRecThread)
+    beatRecThread.start()
+    print(f"PyHeartBeat server listening on port {heartbeat_port}")
+    print("\n*** Press Ctrl-C to stop ***\n")
+    while True:
         try:
-            if __debug__:
-                print "Beat Dictionary"
-                print `beatDictObject`
-            silent = beatDictObject.extractSilent(CHECKWAIT)
+            if DEBUG_ENABLED:
+                print(f"Beat Dictionary: {beatDictObject}")
+            silent = beatDictObject.extractSilent(wait_period)
             if silent:
-                print "Silent clients"
-                print `silent`
-            sleep(CHECKWAIT)
+                print(f"Silent clients: {' '.join(silent)}")
+            sleep(wait_period)
         except KeyboardInterrupt:
-            print "Exiting."
-            beatRecGoOnEvent.clear(  )
-            beatRecThread.join(  )
+            print("Exiting.")
+            beatRecGoOnEvent.clear()
+            beatRecThread.join()
+            break
+
 
-if __name__ == '__main__':
-    main(  )
\ No newline at end of file
+if __name__ == "__main__":
+    main()