Permalink
Browse files

Revamped it ENTIRELY

  • Loading branch information...
1 parent c6870f7 commit a3c153e66d6b657c2e7f21ca92644ef6a83c838f @ndbroadbent committed Nov 20, 2010
Showing with 398 additions and 209 deletions.
  1. +8 −16 README.textile
  2. +46 −0 lcd_icons/cd.chr
  3. +9 −0 lcd_icons/guitar.chr
  4. +18 −0 lcd_icons/love.chr
  5. +9 −0 lcd_icons/next.chr
  6. +9 −0 lcd_icons/notes.chr
  7. +9 −0 lcd_icons/pause.chr
  8. +9 −0 lcd_icons/play.chr
  9. +9 −0 lcd_icons/stop.chr
  10. +100 −0 rubyX2040.rb
  11. +111 −0 shell-fm_lcd_console.rb
  12. +0 −193 shellfm_dsp420lcd.rb
  13. +61 −0 widget.rb
View
@@ -1,25 +1,17 @@
-h1. Shell.fm info on a DSP-420 LCD display
+h1. Shell.fm interface on a Pertelian X2040 LCD display
+
+This is an LCD interface for shell.fm. It is designed to be used with a keyboard, and
+allows a user to control the shell.fm radio stream.
-This is a ruby script that displays the current artist, title and remaining time
-on a DSP-420 LCD display (RS232 LCD display found on POS systems).
It uses threads to manage scrolling and to fake a count-down of the remaining time
between shell.fm refreshes. It updates the display with any changes every 0.1 seconds.
+In the future, it will also show a clock when the track is paused, show weather info,
+poll an RSS feed for news, etc.
+
h3. Requirements:
* Ruby
* shell.fm running with the network service enabled
-* DSP-420 LCD display (via RS232)
-
-h3. Notes
-
-On its own, this script probably isn't worth anything to anybody..
-It's also not very modular (ie, it was written quickly with a single purpose in mind),
-but I am putting it on github simply because it could be used as a starting point
-for any kind of shell.fm display, or anyone that wants to use a DSP-420 LCD display on Linux.
-
-h3. DSP-420 Notes
-
-My DSP-420 was the 5V model, so I hacked a USB -> Serial Adapter to supply 5v to the display from the USB port.
-I can then power and run the DSP-420 entirely from a single USB port. (It usually requires an external 5V/9V DC power supply.)
+* Pertelian X2040 LCD display
View
@@ -0,0 +1,46 @@
+.##..
+#..#.
+#...#
+#.#.#
+#.#.#
+#...#
+.#..#
+..##.
+-----
+.##..
+##.#.
+#.#.#
+#.#.#
+#.#.#
+#...#
+.#..#
+..##.
+-----
+.##..
+#..#.
+#.###
+#.#.#
+#.#.#
+#...#
+.#..#
+..##.
+-----
+.##..
+#..#.
+#...#
+#.###
+#.###
+#...#
+.#..#
+..##.
+-----
+.##..
+#..#.
+#...#
+#.#.#
+#.#.#
+#..##
+.#.##
+..##.
+-----
+
View
@@ -0,0 +1,9 @@
+..#..
+..#..
+..#..
+.###.
+.###.
+##.##
+#####
+.###.
+
View
@@ -0,0 +1,18 @@
+.....
+.*.*.
+*****
+*****
+.***.
+.***.
+..*..
+.....
+-----
+.....
+.*.*.
+*.*.*
+*...*
+.*.*.
+.*.*.
+..*..
+.....
+
View
@@ -0,0 +1,9 @@
+.....
+#...#
+##..#
+###.#
+#####
+###.#
+##..#
+#...#
+
View
@@ -0,0 +1,9 @@
+.####
+.####
+.#..#
+.#..#
+.#..#
+##.##
+##.##
+.....
+
View
@@ -0,0 +1,9 @@
+.....
+##.##
+##.##
+##.##
+##.##
+##.##
+##.##
+.....
+
View
@@ -0,0 +1,9 @@
+.....
+#....
+##...
+###..
+####.
+###..
+##...
+#....
+
View
@@ -0,0 +1,9 @@
+.....
+#####
+#####
+#####
+#####
+#####
+#####
+.....
+
View
@@ -0,0 +1,100 @@
+# Ruby interface to the Pertelian X2040 LCD Display
+
+require 'rubygems'
+require 'serialport'
+
+class Pertelian
+ attr_accessor :icons
+
+ def initialize(tty='/dev/ttyUSB0')
+ @sp = SerialPort.new tty
+
+ @write_delay = 0.002 # Number of seconds to wait between character bytes.
+ @instruction_delay = 0.01 # Number of seconds to wait between instruction bytes.
+ @row_width = 20 # Number of characters that will fit on a line.
+ @row_offsets = [0x00, 0x40, 0x14, 0x54] # Offsets for ordering rows correctly.
+
+ setup
+ @icons = {}
+ end
+
+ def setup
+ # Set up the display.
+ # Function set with 8-bit data length, 2 lines, and 5x7 dot size.
+ # Entry mode set; increment cursor direction; do not automatically shift.
+ # Cursor/display shift; cursor move.
+ # Display On; cursor off; do not blink.
+ "\x38\x06\x10\x0c\x01".split.each do |byte|
+ send_instruction(byte)
+ end
+ end
+
+ def set_cursor(pos)
+ unless pos.is_a? Array
+ # Global pos (1 - 80)
+ row = 1
+ while pos > 20
+ row += 1
+ pos -= 20
+ end
+ pos = [row, pos]
+ end
+ # As an array of [row (1 - 4), column (1 - 20)]
+ send_bytes ["\xfe", (0b10000000 + @row_offsets[pos[0]-1] + pos[1]-1).chr]
+ end
+
+ def send_bytes(bytes, delay=@write_delay)
+ # Send a stream of bytes to the Pertelian.
+ # Also, sleep for delay seconds between sending each byte.
+ bytes.each do |byte|
+ @sp.write byte
+ sleep delay
+ end
+ end
+
+ def send_instruction(byte)
+ # Send an instruction byte to the Pertelian.
+ send_bytes ["\xfe", byte], @instruction_delay
+ end
+
+ def power(on)
+ # Turn the power on or off.
+ on ? send_instruction("\x0c") : send_instruction("\x08")
+ end
+
+ def backlight(on)
+ # Turn the backlight on or off.
+ on ? send_instruction("\x03") : send_instruction("\x02")
+ end
+
+ def clear
+ # Clear the display.
+ send_instruction("\x01")
+ end
+
+ def message(msg, pos=nil)
+ set_cursor(pos) if pos
+ # Display a message.
+ send_bytes(msg.split(''))
+ end
+
+ def load_char(mem_loc, char_data)
+ send_bytes ["\xfe", (0b01000000 + (8 * (mem_loc - 1))).chr]
+ send_bytes char_data
+ end
+
+ def write_char(mem_loc, pos=nil)
+ set_cursor(pos) if pos
+ send_bytes [(mem_loc-1).chr]
+ end
+
+ def load_char_from_file(filename, mem_loc, char_index=1)
+ rows = File.open(filename, 'r').read.split("-----\n")[char_index-1].split("\n")
+ # Map binary data based on presence of '#' character.
+ data = rows.map{|row| row.ljust(5).split('').inject(0b00000){|r, c| r = r << 1; r += 1 if c == "#" || c == "*"; r }.chr}
+ load_char(mem_loc, data)
+ # Save icon memory index and data.
+ @icons[File.basename(filename, ".chr")] = {:loc => mem_loc, :data => data}
+ end
+end
+
View
@@ -0,0 +1,111 @@
+#!/usr/bin/ruby
+# Simple script that displays the artist and title from shell-fm
+# on a DSP-420 LCD screen.
+# Scrolls any strings that are longer than their allowed lengths.
+# If a string is scrolled, it is padded with 2 spaces to the beginning and end.
+# (easier to read)
+
+
+
+require 'rubygems'
+require 'socket'
+require File.join(File.dirname(__FILE__), 'rubyX2040')
+require File.join(File.dirname(__FILE__), 'widget')
+
+# shell.fm network interface config
+IP = "localhost"
+PORT = "54311"
+
+Update_delay = 4.0 # Delay between shell.fm refreshes.
+Scroll_delay = 0.5 # speed of artist and title scrolling
+
+
+# Gets info from shell-fm
+def shellfm_info
+ # Gets the 'artist', 'title', and 'remaining seconds'
+ cmd = "info %a||%l||%t||%R"
+ t = TCPSocket.new(IP, PORT)
+ t.print cmd + "\n"
+ info = t.gets(nil).split("||")
+ t.close
+ return info
+ rescue
+ # On error, returns blank for everything
+ return [""]*4
+end
+
+def display_widget(widget)
+ $p.message widget.render, widget.pos
+end
+
+def splash_screen
+ $p.message "shell.fm LCD display", [2,1]
+ $p.message "(c) Nathan Broadbent", [3,1]
+ sleep 1
+ $p.clear
+end
+
+# ----------- at_exit code ---------------------
+
+at_exit {
+ # When we quit, display a final "bye" message.
+ $p.clear
+ $p.message "Bye!".center(20), [2,1]
+}
+
+# -------------- Script Start -------------------
+
+# initialize Pertelian display.
+$p = Pertelian.new
+
+# Display initial splash screen
+splash_screen
+
+# Get our first reading from shellfm and initialize artist and title arrays,
+# and write the first data to the lcd.
+# Also set up buffers to keep track of value changes.
+artist, title, album, remain = shellfm_info
+@artist = Widget.new(artist, [1,3], 18)
+@album = Widget.new(album, [2,3], 18)
+@title = Widget.new(title, [3,3], 18)
+@remain = Widget.new(remain, [4,3], 7, :time)
+
+# ------------------- Initialize threads -------------------
+
+# Thread to periodically update our artist/title/remaining time hash and loop.
+shellfm_refresh_thread = Thread.new {
+ while true
+ [@artist, @title, @album, @remain].zip(shellfm_info).each do |widget, value|
+ # Reset widget scroll positions if their values have changed.
+ widget.value, widget.scroll_pos = value, 1 if widget.value != value
+ end
+ sleep Update_delay
+ end
+}
+
+# Thread to count down the remaining time between refreshes.
+countdown_remain_thread = Thread.new {
+ while true
+ @remain.value = (@remain.value.to_i - 1).to_s
+ sleep 1
+ end
+}
+
+# Thread to scroll track and artist.
+scroll_thread = Thread.new {
+ while true
+ [@artist, @title, @album, @remain].each do |widget|
+ widget.increment_scroll
+ end
+ sleep Scroll_delay
+ end
+}
+
+# Loop to refresh widgets when needed.
+while true
+ [@artist, @title, @album, @remain].each do |widget|
+ display_widget(widget) if widget.needs_refresh
+ end
+ sleep 0.05
+end
+
Oops, something went wrong.

0 comments on commit a3c153e

Please sign in to comment.