contrib: update the README of many contribs
[nit.git] / contrib / physical_interface_for_mpd_on_rpi / physical_interface_for_mpd_on_rpi.nit
index 455d109..1c73e64 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This programs interprets the input of a physical interface thought the
-# GPIO pins of a Raspberry Pi to control an MPD server.
+# Backend to a hardware interface to control an MPD server from a Raspberry Pi
 #
-# It suppot two inputs: a play/pause button and a rotary encoder to adjust
+# It supports two inputs: a play/pause button and a rotary encoder to adjust
 # the volume.
 #
 # The each data output of the volume control are connected to the board
@@ -28,429 +27,188 @@ import bcm2835
 import mpd
 import privileges
 
-redef class Object
-       fun mpd: MPDConnection do return once new MPDConnection("localhost", 6600, "password")
-end
-
-class RotaryEncoder
-       var pin_a: RPiPin
-       var pin_b: RPiPin
-       var old_a= false
-       var old_b= false
+class PhysicalInterface
+       var mpd = new MPDConnection(server, port, password)
+       protected fun password: String do return "password"
+       fun server: String do return "localhost"
+       fun port: Int do return 6600
 
-       # returns '<', '>' or null accoring to rotation or lack thereof
-       fun update: nullable Char
-       do
-               var new_a = pin_a.lev
-               var new_b = pin_b.lev
-               var res = null
-
-               if new_a != old_a or new_b != old_b then
-                       if not old_a and not old_b then
-                               # everything was on
-                               if not new_a and new_b then
-                                       res = '<'
-                               else if new_a and not new_b then
-                                       res = '>'
-                               end
-                       else if old_a and old_b then
-                               # everything was off
-                               if not new_a and new_b then
-                                       res = '>'
-                               else if new_a and not new_b then
-                                       res = '<'
-                               end
-                       end
+       var but_play: Switch is noinit
+       var but_playlist_a: Switch is noinit
 
-                       old_a = new_a
-                       old_b = new_b
-               end
+       var vol: RotaryEncoder is noinit
+       var vol_step = 2
 
-               return res
-       end
-end
+       var lcd: HD44780 is noinit
 
-class LCD
-       var rs: RPiPin
-       var en: RPiPin
-       var d4: RPiPin
-       var d5: RPiPin
-       var d6: RPiPin
-       var d7: RPiPin
-
-       var ds = new Array[RPiPin]
-
-       # commands
-       fun flag_clear_display: Int do return 1
-       fun flag_return_home: Int do return 2
-       fun flag_entry_mode_set: Int do return 4
-       fun flag_display_control: Int do return 8
-       fun flag_cursor_shift: Int do return 16
-       fun flag_function_set: Int do return 32
-       fun flag_set_cgram_addr: Int do return 64
-       fun flag_set_ggram_addr: Int do return 128
-
-       # entry mode
-       fun flag_entry_right: Int do return 0
-       fun flag_entry_left: Int do return 2
-       fun flag_entry_shift_increment: Int do return 1
-       fun flag_entry_shift_decrement: Int do return 0
-
-       # display flags
-       fun flag_display_on: Int do return 4
-       fun flag_display_off: Int do return 0
-       fun flag_cursor_on: Int do return 2
-       fun flag_cursor_off: Int do return 0
-       fun flag_blink_on: Int do return 1
-       fun flag_blink_off: Int do return 0
-
-       # display/cursor shift
-       fun flag_display_move: Int do return 8
-       fun flag_cursor_move: Int do return 0
-       fun flag_move_right: Int do return 4
-       fun flag_move_left: Int do return 0
-
-       # function set
-       fun flag_8bit_mode: Int do return 16
-       fun flag_4bit_mode: Int do return 0
-       fun flag_2_lines: Int do return 8
-       fun flag_1_line: Int do return 0
-       fun flag_5x10_dots: Int do return 4
-       fun flag_5x8_dots: Int do return 0
-
-       fun function_set(bits, lines, dots_wide: Int)
-       do
-               var fs = flag_function_set
-               if bits == 8 then
-                       fs = fs.bin_or(16)
-               else if bits != 4 then abort
+       var lcd_backlight: RPiPin is noinit
+       var lcd_backlight_delay = 1000
 
-               if lines == 2 then
-                       fs = fs.bin_or(8)
-               else if lines != 1 then abort
+       var buzzer: Buzzer is noinit
 
-               if dots_wide == 10 then
-                       fs = fs.bin_or(4)
-               else if dots_wide != 8 then abort
+       init
+       do
+               # commandline options for privileges drop
+               var opts = new OptionContext
+               var opt_ug = new OptionUserAndGroup.for_dropping_privileges
+               #opt_ug.mandatory = true
+               opts.add_option(opt_ug)
+
+               # parse and check command line options
+               opts.parse(args)
+               if not opts.errors.is_empty then
+                       print opts.errors
+                       print "Usage: {sys.program_name} [options]"
+                       opts.usage
+                       exit 1
+               end
 
-               write(true, fs)
-       end
+               assert bcm2835_init else print "Failed to init"
 
-       fun display_control(on, cursor, blink: Bool)
-       do
-               var fs = flag_display_control
+               # drop privileges!
+               var user_group = opt_ug.value
+               if user_group != null then user_group.drop_privileges
 
-               if on then
-                       fs = fs.bin_or(flag_display_on)
-               else fs = fs.bin_or(flag_display_off)
+               # Play button
+               but_play = new Switch(new RPiPin.p1_13, new PUDControl.down)
 
-               if cursor then
-                       fs = fs.bin_or(flag_cursor_on)
-               else fs = fs.bin_or(flag_cursor_off)
+               # Playlist a button
+               but_playlist_a = new Switch(new RPiPin.p1_15, new PUDControl.down)
 
-               if blink then
-                       fs = fs.bin_or(flag_blink_on)
-               else fs = fs.bin_or(flag_blink_off)
+               # Vol +
+               var vol3 = new RPiPin.p1_03
+               vol3.fsel = new FunctionSelect.inpt
+               vol3.pud = new PUDControl.up
 
-               write(true, fs)
-       end
+               # Vol -
+               var vol5 = new RPiPin.p1_05
+               vol5.fsel = new FunctionSelect.inpt
+               vol5.pud = new PUDControl.up
 
-       fun entry_mode(left, incr: Bool)
-       do
-               var fs = flag_entry_mode_set
+               vol = new RotaryEncoder(vol3,vol5)
 
-               if left then
-                       fs = fs.bin_or(flag_entry_left)
-               else fs = fs.bin_or(flag_entry_right)
+               # LCD
+               var lcd_rs = new RPiPin.p1_23
+               var lcd_en = new RPiPin.p1_21
+               var lcd_d4 = new RPiPin.p1_19
+               var lcd_d5 = new RPiPin.p1_26
+               var lcd_d6 = new RPiPin.p1_24
+               var lcd_d7 = new RPiPin.p1_22
+               lcd = new HD44780(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7)
+               lcd.setup
+               lcd.clear
 
-               if incr then
-                       fs = fs.bin_or(flag_entry_shift_increment)
-               else fs = fs.bin_or(flag_entry_shift_decrement)
+               lcd_backlight = new RPiPin.p1_18
+               lcd_backlight.fsel = new FunctionSelect.outp
 
-               write(true, fs)
+               # Buzzer
+               var buzzer_pin = new RPiPin.p1_11
+               buzzer_pin.fsel = new FunctionSelect.outp
+               buzzer = new Buzzer(buzzer_pin)
        end
 
-       fun setup
+       fun run
        do
-               ds = [d4,d5,d6,d7]
-
-               rs.fsel = new FunctionSelect.outp
-               en.fsel = new FunctionSelect.outp
-               d4.fsel = new FunctionSelect.outp
-               d5.fsel = new FunctionSelect.outp
-               d6.fsel = new FunctionSelect.outp
-               d7.fsel = new FunctionSelect.outp
-
-               rs.write(false)
-               en.write(false)
-
-               # wait 20ms for power up
-               #50.bcm2835_delay
-
-               #write_4bits(true,true,false,false)
-               #write_4_bits(3)
-
-               #5.bcm2835_delay
-
-               #write_4bits(true,true,false,false)
-               #write_4_bits(3)
-
-               #5.bcm2835_delay
-
-               #write_4bits(true,true,false,false)
-               #write_4_bits(3)
-
-               #200.bcm2835_delay_micros
-
-               #write_4bits(false,true,false,false)
-               #write_4_bits(2)
-
-               # wait 5ms
-               #5.bcm2835_delay
-
-               # set interface
-               # 4bits, 2 lines
-               #write(true, flow)
-               #function_set(4, 2, 8)
-
-               # cursor
-               # don't shift & hide
-               #display_control(true, true, true)
+               var tick = 0
+               var last_event = 0
+               loop
+                       var force_lcd_update = false
+
+                       # play button
+                       if but_play.changed and but_play.is_down then
+                               print "but"
+                               hit_play_stop
+                               force_lcd_update = true
+                       end
 
-               # clear & home
-               #write(true, flag_)
-               #clear
+                       if but_playlist_a.changed and but_playlist_a.is_down then
+                               play_playlist_a
+                               force_lcd_update = true
+                       end
 
-               # set cursor move direction
-               # move right
-               #write(true, 6)
+                       # volume
+                       var s = vol.update
+                       if s != null then
+                               if s == '<' then
+                                       print "vol down"
+                                       mpd.relative_volume = -vol_step
+                               else # >
+                                       print "vol up"
+                                       mpd.relative_volume = vol_step
+                               end
+                               force_lcd_update = true
+                       end
 
-               # turn on display
-               #write(true, 4)
+                       # update lcd
+                       if tick % 100 == 0 or force_lcd_update then
+                               var status = mpd.status
+                               var song = mpd.current_song
+
+                               var status_char
+                               if status == null then
+                                       lcd.text = "Unknown status"
+                               else if song == null then
+                                       lcd.text = "No song playing"
+                               else
+                                       if status.playing then
+                                               last_event = tick
+                                               status_char = ">"
+                                       else status_char = "#"
+
+                                       var tr = status.time_ratio
+                                       var pos = "-"
+                                       if tr != null then pos = (status.time_ratio*10.0).to_i.to_s
+
+                                       lcd.text = "{status_char} {song.artist}\n{pos} {song.title}"
+                               end
+                       end
 
-               # set entry mode
-               #entry_mode(true, true)
+                       # manage backlight
+                       if force_lcd_update then last_event = tick
 
-               write(true, "33".to_hex)
-               write(true, "32".to_hex)
-               write(true, "28".to_hex)
-               write(true, "0C".to_hex)
-               write(true, "01".to_hex)
-       end
+                       var diff_with_last_event = tick - last_event
+                       if diff_with_last_event == 0 then
+                               lcd_backlight.write(true)
+                       else if diff_with_last_event == lcd_backlight_delay then
+                               lcd_backlight.write(false)
+                       end
 
-       fun write_4_bits(v: Int)
-       do
-               var lb = once [1,2,4,8]
-               for i in [0..4[ do
-                       var b = lb[i]
-                       var r = b.bin_and(v) != 0
-                       var d = ds[i]
-                       d.write(r)
+                       10.bcm2835_delay
+                       tick += 1
                end
-               pulse_enable
-       end
-
-       fun write_4bits(a,b,c,d:Bool)
-       do
-               d4.write(a)
-               d5.write(b)
-               d6.write(c)
-               d7.write(d)
-               pulse_enable
-       end
-
-       fun pulse_enable
-       do
-               en.write(false)
-               1.bcm2835_delay_micros
-               en.write(true)
-               100.bcm2835_delay_micros
-               en.write(false)
        end
 
-       fun write(is_cmd: Bool, cmd: Int)
+       fun hit_play_stop
        do
-               en.write(false)
-               rs.write(not is_cmd)
-
-               # high byte
-               var hb = once [16,32,64,128]
-               for i in [0..4[ do
-                       var b = hb[i]
-                       var r = b.bin_and(cmd) != 0
-                       var d = ds[i]
-                       d.write(r)
-               end
-
-               en.write(true)
-
-               # wait 450 ns
-               1.bcm2835_delay_micros
-
-               en.write(false)
-
-               if is_cmd then
-                       # wait 5ms
-                       5.bcm2835_delay
+               # get current status
+               var status = mpd.status
+               var playing = false
+               if status != null then
+                       playing = status.playing
                else
-                       # wait 200us
-                       200.bcm2835_delay_micros
+                       print "Cannot get state"
+                       return
                end
 
-               # low byte
-               var lb = once [1,2,4,8]
-               for i in [0..4[ do
-                       var b = lb[i]
-                       var r = b.bin_and(cmd) != 0
-                       var d = ds[i]
-                       d.write(r)
-               end
-
-               en.write(true)
-
-               # wait 450ns
-               1.bcm2835_delay_micros
-
-               en.write(false)
-
-               if is_cmd then
-                       # wait 5ms
-                       5.bcm2835_delay
+               if playing then
+                       # stop
+                       print "playing -> stop"
+                       mpd.pause
                else
-                       # wait 200us
-                       200.bcm2835_delay_micros
+                       print "stopped -> play"
+                       mpd.play
                end
-       end
 
-       fun clear
-       do
-               write(true,1)
-               2.bcm2835_delay
+               bell
        end
 
-       fun return_home
+       fun play_playlist_a
        do
-               write(true,2)
-               2.bcm2835_delay
+               mpd.load_playlist("alexis")
        end
 
-       fun text=(v: String)
-       do
-               clear
-               return_home
-               for c in v do write(false, c.ascii)
-       end
-end
-
-fun hit_play_stop
-do
-       # get current status
-       var status = mpd.status
-       var playing = false
-       if status != null then
-               playing = status.state == "play"
-       end
-
-       if playing then
-               # stop
-               print "playing -> stop"
-               mpd.pause
-       else
-               print "stopped -> play"
-               mpd.play
-       end
-end
-
-# commandline options for privileges drop
-var opts = new OptionContext
-var opt_ug = new OptionDropPrivileges
-#opt_ug.mandatory = true
-opts.add_option(opt_ug)
-
-# parse and check command line options
-opts.parse(args)
-if not opts.errors.is_empty then
-       print opts.errors
-       print "Usage: {program_name} [options]"
-       opts.usage
-       exit 1
+       fun bell do buzzer.buzz(1.5, 20)
 end
 
-assert bcm2835_init else print "Failed to init"
-
-# drop privileges!
-var user_group = opt_ug.value
-if user_group != null then user_group.drop_privileges
-
-# Debug LED
-var out = new RPiPin.p1_11
-out.fsel = new FunctionSelect.outp
-out.write(false)
-
-# Play button
-var inp = new RPiPin.p1_13
-inp.fsel = new FunctionSelect.inpt
-inp.pud = new PUDControl.down
-
-# Vol +
-var vol3 = new RPiPin.p1_03
-vol3.fsel = new FunctionSelect.inpt
-vol3.pud = new PUDControl.up
-
-# Vol -
-var vol5 = new RPiPin.p1_05
-vol5.fsel = new FunctionSelect.inpt
-vol5.pud = new PUDControl.up
-
-var vol = new RotaryEncoder(vol3,vol5)
-var vol_step = 2
-
-# LCD
-var lcd_rs = new RPiPin.p1_23
-var lcd_en = new RPiPin.p1_21
-var lcd_d4 = new RPiPin.p1_19
-var lcd_d5 = new RPiPin.p1_26
-var lcd_d6 = new RPiPin.p1_24
-var lcd_d7 = new RPiPin.p1_22
-var lcd = new LCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7)
-lcd.setup
-#lcd.write(false, 'a'.to_i.to_ascii)
-lcd.clear
-lcd.write(false, 'a'.ascii)
-lcd.write(false, 'C'.ascii)
-
-var last_in = false
-var led_on = false
-var tick = 0
-loop
-       # play button
-       var lev = inp.lev
-       if lev != last_in then
-               last_in = lev
-               if lev then
-                       print "hps"
-                       hit_play_stop
-               end
-       end
-
-       # volume
-       var s = vol.update
-       if s != null then
-               if s == '<' then
-                       print "vol down"
-                       mpd.relative_volume = -vol_step
-               else # >
-                       print "vol up"
-                       mpd.relative_volume = vol_step
-               end
-       end
-
-       if tick % 100 == 0 then
-               print tick
-               #var now_playing = mpd.status("")
-               #lcd.text = tick.to_s
-       end
-
-       10.bcm2835_delay
-       tick += 1
-end
+var phy = new PhysicalInterface
+phy.run