X-Git-Url: http://nitlanguage.org diff --git a/contrib/physical_interface_for_mpd_on_rpi/physical_interface_for_mpd_on_rpi.nit b/contrib/physical_interface_for_mpd_on_rpi/physical_interface_for_mpd_on_rpi.nit index 95eb246..1c73e64 100644 --- a/contrib/physical_interface_for_mpd_on_rpi/physical_interface_for_mpd_on_rpi.nit +++ b/contrib/physical_interface_for_mpd_on_rpi/physical_interface_for_mpd_on_rpi.nit @@ -14,10 +14,9 @@ # 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,472 +27,188 @@ import bcm2835 import mpd import privileges -redef class Object - fun mpd: MPDConnection do return once new MPDConnection("localhost", 6600, "password") -end +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 -class RotaryEncoder - var pin_a: RPiPin - var pin_b: RPiPin - var old_a= false - var old_b= false + var but_play: Switch is noinit + var but_playlist_a: Switch is noinit - # 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 vol: RotaryEncoder is noinit + var vol_step = 2 - old_a = new_a - old_b = new_b - end + var lcd: HD44780 is noinit - return res - end -end + var lcd_backlight: RPiPin is noinit + var lcd_backlight_delay = 1000 -# Hitachi HD44780 or similar 2-4 lines LCD displays -class HD44780 - 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 - - # last text displayed - private var last_text: nullable String = null - - 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 buzzer: Buzzer is noinit - if lines == 2 then - fs = fs.bin_or(8) - else if lines != 1 then abort - - 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 - end - fun return_home - do - write(true,2) - 2.bcm2835_delay + bell end - fun text=(v: String) + fun play_playlist_a do - # do not redraw the samething - var last_text = last_text - if last_text != null and last_text == v then return - - clear - return_home - var count = 0 - for c in v do - if c == '\n' then - # FIXME, this should work - #write(true, "C0".to_hex) - # instead we use the following which may not be portable - - for s in [count..40[ do write(false, ' '.ascii) - count = 0 - else - write(false, c.ascii) - count += 1 - end - end - - self.last_text = v + mpd.load_playlist("alexis") end -end - -fun hit_play_stop -do - # get current status - var status = mpd.status - var playing = false - if status != null then - playing = status.playing - else - print "Cannot get state" - return - 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 HD44780(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7) -lcd.setup -lcd.clear - -var last_in = false -var led_on = false -var tick = 0 -loop - var force_lcd_update = false - - # 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 - force_lcd_update = true - end - - # 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 - 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 - - 10.bcm2835_delay - tick += 1 -end +var phy = new PhysicalInterface +phy.run