Skip to content

Instantly share code, notes, and snippets.

@oneross
Created January 26, 2021 15:15
Show Gist options
  • Save oneross/6c3020724dd169e25a5203900f613803 to your computer and use it in GitHub Desktop.
Save oneross/6c3020724dd169e25a5203900f613803 to your computer and use it in GitHub Desktop.
Ships Bell
#!/usr/bin/env python3
"""
A little app that plays ship's bell sounds every 30 minutes.
"""
# Modified from the excellent https://github.com/ralfholly/ships-bell
# - run on python 2.7 on a mac I can't easily update from 2.7
# - renamed mp3 files (lines ~83,86) for some unknown reason
# - used mac's built-in afplay
# - updated super() reference in ShipsBell init to more flexible super(B, self)
# I personally run in an iterm2 visor window with `python ./ships_bell.py &`
from __future__ import print_function
import time
import threading
import subprocess
import argparse
import sys
import os
# If you don't have the 'mpg321' MP3 player, adapt this invocation to your needs.
# MP3_PLAYER_CALL = "mpg321 -q -g 10 {mp3_file}"
MP3_PLAYER_CALL = "afplay {mp3_file}"
class ShipsBellError(Exception):
pass
class ShipsBell(threading.Thread):
SECONDS_PER_MINUTE = 60
MINUTES_PER_HALF_HOUR = 30
MAX_DOUBLE_STRIKES = 4
def __init__(self, working_dir, start=0, end=24):
super(ShipsBell, self).__init__() # modified from parameterless super call
self.daemon = True
self.working_dir = working_dir
assert end >= start
self.start_time = start
self.end_time = end
def run(self): # pragma: no cover
while True:
current_time = time.localtime()
minutes = current_time.tm_min
hours = current_time.tm_hour
self.step(hours, minutes)
time.sleep(self.compute_sleep_time(minutes))
def step(self, hours, minutes):
if (hours >= self.start_time and hours < self.end_time) or (hours == self.end_time and minutes == 0):
# Strike bell at every half or full hour.
if (minutes % ShipsBell.MINUTES_PER_HALF_HOUR) == 0:
double_strikes, single_strikes = self.compute_strikes(hours, minutes)
for _ in range(double_strikes):
self.play_double_strike()
for _ in range(single_strikes):
self.play_single_strike()
@staticmethod
def compute_strikes(hours, minutes):
# Single strike only on half hour.
single_strikes = 1 if minutes == ShipsBell.MINUTES_PER_HALF_HOUR else 0
double_strikes = hours % ShipsBell.MAX_DOUBLE_STRIKES
if double_strikes == 0 and single_strikes == 0:
double_strikes = ShipsBell.MAX_DOUBLE_STRIKES
return (double_strikes, single_strikes)
@staticmethod
def compute_sleep_time(minutes):
# Remaining minutes till half or full hour.
delta_minutes = ShipsBell.MINUTES_PER_HALF_HOUR - minutes % ShipsBell.MINUTES_PER_HALF_HOUR
# If enough time is left, sleep through half of it.
if delta_minutes >= 2:
return delta_minutes / 2.0 * ShipsBell.SECONDS_PER_MINUTE
# During the last minute, check every second.
return 1.0
def play_double_strike(self):
ShipsBell.play_mp3(self.working_dir + "/bells-2.mp3")
def play_single_strike(self):
ShipsBell.play_mp3(self.working_dir +"/bells-1.mp3")
@staticmethod
def play_mp3(path):
cmd = MP3_PLAYER_CALL.format(mp3_file=path).split()
exit_code = subprocess.call(cmd)
if exit_code != 0:
raise ShipsBellError("Failed to play " + path)
def handle_args(args):
this_script = args[0]
parser = argparse.ArgumentParser(this_script, description="A little ship's bell app")
parser.add_argument("--from", type=int, default=0, help="Full hour, from which bell sound is emitted (default:0)")
parser.add_argument("--to", type=int, default=24, help="Full hour, until which bell sound is emitted (default:24)")
parsed_args = parser.parse_args(args[1:])
from_hour = getattr(parsed_args, "from")
to_hour = getattr(parsed_args, "to")
if from_hour < 0 or from_hour > 24 or to_hour < 0 or to_hour > 24:
raise ShipsBellError("Hours must be in range 0..24.")
if from_hour > to_hour:
raise ShipsBellError("Value of 'to' hour must be greater than or equal to value of 'from' hour.")
return ShipsBell(os.path.dirname(this_script), from_hour, to_hour)
if __name__ == "__main__": #pragma: no cover
# Now start app.
try:
SHIPS_BELL = handle_args(sys.argv)
# Play double-strike at startup, mainly to detect a missing MP3 player.
SHIPS_BELL.play_double_strike()
SHIPS_BELL.start()
SHIPS_BELL.join()
except (FileNotFoundError, ShipsBellError) as e:
print(str(e), file=sys.stderr) # this eats error messages
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment