Skip to content

Commit

Permalink
Merge pull request #24 from deckerego/gmusic
Browse files Browse the repository at this point in the history
Google Music and Bug Fixes
  • Loading branch information
deckerego authored Feb 4, 2017
2 parents 3145c68 + f99ae13 commit 15ea6d8
Show file tree
Hide file tree
Showing 18 changed files with 255 additions and 124 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ home/pi/hack-clock/backups/run_clock.*
home/pi/hack-clock/backups/blocks_clock.*
home/pi/hack-clock/blocks_clock.xml
home/pi/hack-clock/run_clock.py
tests/localsettings.conf
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ I'm assuming that you are starting with the Raspian Minimal Linux distribution.

1. Make sure your Raspberry Pi is up to date with the latest packages & firmware with `sudo apt-get update; sudo apt-get dist-upgrade`
2. Enable I2C by executing `sudo raspi-config` as described in Adafruit's tutorial: https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c
3. Add the necessary Python and GStreamer dependencies using `sudo apt-get install wiringpi python-bottle python-setuptools python-pip python-dev python-dateutil python-smbus gstreamer0.10-x gstreamer-tools gstreamer0.10-plugins-base gstreamer0.10-plugins-good gstreamer0.10-plugins-bad python-gst0.10`
3. Add the necessary Python and GStreamer dependencies using the command: `sudo apt-get install wiringpi python-bottle python-requests python-oauth2client python-httplib2 python-setuptools python-pip python-dev python-dateutil python-smbus gstreamer0.10-x gstreamer-tools gstreamer0.10-plugins-base gstreamer0.10-plugins-good gstreamer0.10-plugins-bad gstreamer0.10-plugins-ugly python-gst0.10`
4. Install hack-clock via `wget https://github.com/deckerego/hack-clock/releases/download/2.0.0/python-hackclock_2.0.1-1_all.deb; sudo dpkg -i python-hackclock_2.0.1-1_all.deb`
5. Tweak `/etc/hack-clock.conf` and `/etc/default/hack-clock` to fit your needs (GPIO pins, correct weather station, etc.). A list of observed weather stations is available at http://forecast.weather.gov/stations.php
6. Reboot your Pi to re-load modules and start the IDE web server
Expand Down
4 changes: 2 additions & 2 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ Source: hackclock
Maintainer: DeckerEgo <[email protected]>
Section: python
Priority: optional
Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7.4.3)
Build-Depends: python-setuptools (>= 2.6.6-3), debhelper (>= 7.4.3)
Standards-Version: 3.9.1
X-Python-Version: >= 2.7



Package: python-hackclock
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, ntp, python-bottle, wiringpi, python-setuptools, python-pip, python-dev, python-dateutil, python-smbus, gstreamer0.10-x, gstreamer-tools, gstreamer0.10-plugins-base, gstreamer0.10-plugins-good, gstreamer0.10-plugins-bad, python-gst0.10
Depends: ${misc:Depends}, ${python:Depends}, ntp, python-bottle, python-requests, python-httplib2, python-oauth2client, wiringpi, python-setuptools, python-pip, python-dev, python-dateutil, python-smbus, gstreamer0.10-x, gstreamer-tools, gstreamer0.10-plugins-base, gstreamer0.10-plugins-good, gstreamer0.10-plugins-bad, python-gst0.10
Description: A hackable alarm clock for the Raspberry Pi
2 changes: 1 addition & 1 deletion debian/python-hackclock.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ chown -R pi:pi /home/pi/hack-clock

# Install those Python modules that only exist via pip
echo "Installing missing Python modules..."
pip install --upgrade -r /usr/share/doc/hack-clock/requirements.txt
pip install -r /usr/share/doc/hack-clock/requirements.txt

# Set the hifiberry-dac i2s module
echo "Installing I2S audio..."
Expand Down
2 changes: 1 addition & 1 deletion etc/default/hack-clock
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ BUTTON_BCM_PIN=(23 24)
SWITCH_BCM_PIN=(25)

# Volume level to feed to ALSA SoftVol
MASTER_VOLUME='75%'
MASTER_VOLUME='65%'
8 changes: 5 additions & 3 deletions etc/hack-clock.conf
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
{
"default_editor": "/blocks/edit",
"disable_editor_button": false,
"buttons_gpio": [23, 24],
"switches_gpio": [25],
"weather_station": "KGPI",
"ifttt_maker_key": "",
"google_username": "",
"google_password": "",
"python_file": "/home/pi/hack-clock/run_clock.py",
"blocks_file": "/home/pi/hack-clock/blocks_clock.xml",
"audio_files": "/home/pi/hack-clock/audio",
"backup_files": "/home/pi/hack-clock/backups",
"lesson_files": "/home/pi/hack-clock/lessons",
"webapp_files": "/srv/hackclock",
"file_filter": ["README.md", ".DS_Store"],
"buttons_gpio": [23, 24],
"switches_gpio": [25]
"file_filter": ["README.md", ".DS_Store"]
}
49 changes: 33 additions & 16 deletions lib/hackclock/runapp/Libs/GStreamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
import gst
import gobject
import os
import re
import logging
from hackclock.config import configuration

logger = logging.getLogger('speaker')

console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(console)

class Speaker:
__HTTP_PATTERN = re.compile("http[s]*://.*")

def __init__(self):
self.pl = None
Expand All @@ -17,26 +26,26 @@ def __init__(self):
def play(self, fileName):
self.playList([fileName])

def playList(self, fileNames):
def playList(self, trackNames):
if not self.isPlaying():
# Create a queue of URIs
audio_dir = configuration.get('audio_files')
self.playlist = [ "file://"+os.path.abspath("%s/%s" % (audio_dir, fileName)) for fileName in fileNames ]
self.playlist = trackNames
self.playlist.reverse()
self.play()

# Create the pipeline
self.pl = gst.element_factory_make("playbin2", "player")
self.pl.set_state(gst.STATE_READY)
def play(self):
# Create the pipeline
self.pl = gst.element_factory_make("playbin2", "player")
self.pl.set_state(gst.STATE_READY)

# Create the event bus
self.bus = self.pl.get_bus()
self.bus.add_signal_watch()
self.bus.connect("message", self.onMessage)
self.eventLoop = GtkEventLoop()
self.eventLoop.start()
# Create the event bus
self.bus = self.pl.get_bus()
self.bus.add_signal_watch()
self.bus.connect("message", self.onMessage)
self.eventLoop = GtkEventLoop()
self.eventLoop.start()

# Play next track on playlist
self.next()
# Play next track on playlist
self.next()

def stop(self):
self.pl.set_state(gst.STATE_NULL)
Expand All @@ -47,8 +56,16 @@ def isPlaying(self):

def next(self):
if self.playlist:
track = self.playlist.pop()

if not self.__HTTP_PATTERN.match(track):
# This is a filename - convert to URI
audio_dir = configuration.get('audio_files')
track = "file://"+os.path.abspath("%s/%s" % (audio_dir, track))

logger.info("Playing: %s" % track)
self.pl.set_state(gst.STATE_READY)
self.pl.set_property('uri', self.playlist.pop())
self.pl.set_property('uri', track)
self.pl.set_state(gst.STATE_PLAYING)
else:
self.stop()
Expand Down
58 changes: 58 additions & 0 deletions lib/hackclock/runapp/Libs/GoogleMusic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from gmusicapi import Mobileclient
from gmusicapi import exceptions
import logging
from hackclock.config import configuration
import json

logger = logging.getLogger('google_music')

console = logging.StreamHandler()
console.setLevel(logging.WARNING)
logger.addHandler(console)

class AudioStream:
__username = configuration.get('google_username')
__password = configuration.get('google_password')
__track_prefetch = 15
__client = None
__playlist = []

def __init__(self, station_id = 'IFL'):
self.__client = Mobileclient()
self.__client.login(self.__username, self.__password, Mobileclient.FROM_MAC_ADDRESS)
self.__playlist = self.__fetchTrackIDs(station_id)

def __del__(self):
if self.__client:
self.__client.logout()

def __fetchTrackIDs(self, station_id):
if not self.__client or not self.__client.is_authenticated():
logger.error("Client is not authenticated!")
return []

tracklist = self.__client.get_station_tracks(station_id, num_tracks=self.__track_prefetch)
logger.info("Received tracks: %r" % json.dumps(tracklist))

songids = [track['id'] for track in tracklist if 'id' in track]
nautids = [track['nid'] for track in tracklist if 'nid' in track]

return songids + nautids

def pop(self):
while self.__playlist:
try:
track_id = self.__playlist.pop()
stream_url = self.__client.get_stream_url(track_id, quality='low')
return stream_url
except(exceptions.CallFailure):
logger.warning("Failed to fetch Stream URL for ID %s" % trackid)

raise IndexError("pop from empty list")

def reverse(self):
# Reverse just returns itself, since the playlist is already chaos
return self

def __len__(self):
return len(self.__playlist)
23 changes: 16 additions & 7 deletions lib/hackclock/webapp/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def python_lesson_event_loop(clock, file_id):
try:
code_file = open(lesson_file, 'r')
python_save_event_loop(clock, code_file)
return template('python/editor', switch_visible=switch_visible, status="Saved")
return template('python/editor', switch_visible=switch_visible, status="Opened")
except:
return template('python/editor', switch_visible=switch_visible, status="Failed")

Expand All @@ -161,7 +161,7 @@ def python_restore_event_loop(clock, file_id):
try:
code_file = open(restored_file, 'r')
python_save_event_loop(clock, code_file)
return template('python/editor', switch_visible=switch_visible, status="Saved")
return template('python/editor', switch_visible=switch_visible, status="Opened")
except:
return template('python/editor', switch_visible=switch_visible, status="Failed")

Expand All @@ -181,7 +181,10 @@ def send_blocks_js(filename):
@application.get('/blocks/edit')
def blocks_edit_event_loop(clock):
switch_visible = not configuration.get('disable_editor_button')
return template('blocks/editor', switch_visible=switch_visible, status="Opened")
google_music = configuration.get('google_username') and configuration.get('google_password')
ifttt_maker = configuration.get('ifttt_maker_key')

return template('blocks/editor', switch_visible=switch_visible, google_music=google_music, ifttt_maker=ifttt_maker, status="Opened")

@application.get('/blocks/read')
def blocks_read_event_loop(clock):
Expand Down Expand Up @@ -258,29 +261,35 @@ def blocks_backup_list(clock):
def blocks_lesson_event_loop(clock, file_id):
lesson_dir = configuration.get('lesson_files')
lesson_file = "%s/%s/blocks_clock.xml" % (lesson_dir, file_id)

switch_visible = not configuration.get('disable_editor_button')
google_music = configuration.get('google_username') and configuration.get('google_password')
ifttt_maker = configuration.get('ifttt_maker_key')

try:
code_file = open(lesson_file, 'r')
blocks_save_event_loop(clock, code_file)
return template('blocks/editor', switch_visible=switch_visible, status="Saved")
return template('blocks/editor', switch_visible=switch_visible, google_music=google_music, ifttt_maker=ifttt_maker, status="Opened")
except:
return template('blocks/editor', switch_visible=switch_visible, status="Failed")
return template('blocks/editor', switch_visible=switch_visible, google_music=google_music, ifttt_maker=ifttt_maker, status="Failed")

@application.get('/blocks/restore/<file_id:int>')
def blocks_restore_event_loop(clock, file_id):
version_dir = configuration.get('backup_files')
files = listdir(version_dir)
restored_files = filter(lambda f: f.startswith('blocks_clock.') and int(parser.parse(f.lstrip("blocks_clock.")).strftime("%s")) == file_id, files)
restored_file = "%s/%s" % (version_dir, restored_files[0])

switch_visible = not configuration.get('disable_editor_button')
google_music = configuration.get('google_username') and configuration.get('google_password')
ifttt_maker = configuration.get('ifttt_maker_key')

try:
code_file = open(restored_file, 'r')
blocks_save_event_loop(clock, code_file)
return template('blocks/editor', switch_visible=switch_visible, status="Saved")
return template('blocks/editor', switch_visible=switch_visible, google_music=google_music, ifttt_maker=ifttt_maker, status="Opened")
except:
return template('blocks/editor', switch_visible=switch_visible, status="Failed")
return template('blocks/editor', switch_visible=switch_visible, google_music=google_music, ifttt_maker=ifttt_maker, status="Failed")

# Clock REST API
@application.post('/clock/restart')
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bitstring>=3.1.3
wiringpi2>=2.23.1
gmusicapi>=10.1.0
7 changes: 5 additions & 2 deletions srv/hackclock/views/blocks/editor.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<category name="Audio" colour="240">
<block type="speaker"></block>
<block type="play_list"></block>
% if google_music:
<block type="google_music_radio"></block>
% end
<block type="is_playing"></block>
<block type="audio_stop"></block>
</category>
Expand All @@ -131,9 +134,11 @@
<block type="weather_station"></block>
<block type="current_temp"></block>
</category>
% if ifttt_maker:
<category name="IFTTT" colour="330">
<block type="maker_send"></block>
</category>
% end
<category name="Loops" colour="360">
<block type="controls_repeat_ext">
<value name="TIMES">
Expand Down Expand Up @@ -182,15 +187,13 @@
<script src="/blockly/msg/js/en.js"></script>
<script src="/blocks/js/blocks/display.js"></script>
<script src="/blocks/js/blocks/clock.js"></script>
<script src="/blocks/js/blocks/speaker.js"></script>
<script src="/blocks/js/blocks/audio.js"></script>
<script src="/blocks/js/blocks/gpio.js"></script>
<script src="/blocks/js/blocks/weather.js"></script>
<script src="/blocks/js/blocks/ifttt.js"></script>
<script src="/blocks/js/blocks/list.js"></script>
<script src="/blocks/js/generators/display.js"></script>
<script src="/blocks/js/generators/clock.js"></script>
<script src="/blocks/js/generators/speaker.js"></script>
<script src="/blocks/js/generators/audio.js"></script>
<script src="/blocks/js/generators/gpio.js"></script>
<script src="/blocks/js/generators/weather.js"></script>
Expand Down
77 changes: 77 additions & 0 deletions srv/hackclock/views/blocks/js/blocks/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,83 @@ function loadAudioTools(workspace, toolbox, callback) {
request.send();
}

Blockly.Blocks['speaker'] = {
init: function() {
this.jsonInit({
"type": "block_type",
"message0": "Audio Speaker",
"output": 'Speaker',
"colour": 60,
"tooltip": "A speaker that lets you listen to music",
"helpUrl": "http://hackclock.deckerego.net/"
});

var thisBlock = this;

this.setTooltip(function() {
var parent = thisBlock.getParent();
return (parent && parent.getInputsInline() && parent.tooltip) ||
'A speaker that lets you listen to music';
});
}
};

Blockly.Blocks['play_list'] = {
init: function() {
this.jsonInit({
"type": "play_list",
"message0": "On %1 Play %2",
"args0": [
{
"type": "input_value",
"name": "speaker",
"check": "Speaker"
},
{
"type": "input_value",
"name": "songs",
"check": "Array"
}
],
"inputsInline": true,
"previousStatement": null,
"nextStatement": null,
"colour": 255,
"tooltip": "Play this list of songs",
"helpUrl": "http://hackclock.deckerego.net/"
});

var thisBlock = this;

this.setTooltip(function() {
var parent = thisBlock.getParent();
return (parent && parent.getInputsInline() && parent.tooltip) ||
'Play this list of songs';
});
}
};

Blockly.Blocks['google_music_radio'] = {
init: function() {
this.jsonInit({
"type": "block_type",
"message0": "Google Music Radio",
"output": "Array",
"colour": 60,
"tooltip": "Play the \"I'm Feeling Lucky\" radio station on Google Music",
"helpUrl": "https://play.google.com/music/listen"
});

var thisBlock = this;

this.setTooltip(function() {
var parent = thisBlock.getParent();
return (parent && parent.getInputsInline() && parent.tooltip) ||
'Play the "I\'m Feeling Lucky" radio station on Google Music';
});
}
};

Blockly.Blocks['is_playing'] = {
init: function() {
this.jsonInit({
Expand Down
Loading

0 comments on commit 15ea6d8

Please sign in to comment.