335444a6a07c4a13646b349e9a070a03c381683f
[nit.git] / contrib / physical_interface_for_mpd_on_rpi / physical_interface_for_mpd_on_rpi.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # This programs interprets the input of a physical interface thought the
18 # GPIO pins of a Raspberry Pi to control an MPD server.
19 #
20 # It suppot two inputs: a play/pause button and a rotary encoder to adjust
21 # the volume.
22 #
23 # The each data output of the volume control are connected to the board
24 # pins #3 and #5.
25 module physical_interface_for_mpd_on_rpi
26
27 import bcm2835
28 import mpd
29 import privileges
30
31 class PhysicalInterface
32 var mpd = new MPDConnection(server, port, password)
33 protected fun password: String do return "password"
34 fun server: String do return "localhost"
35 fun port: Int do return 6600
36
37 var but_play: Switch is noinit
38 var but_playlist_a: Switch is noinit
39
40 var vol: RotaryEncoder is noinit
41 var vol_step = 2
42
43 var lcd: HD44780 is noinit
44
45 var lcd_backlight: RPiPin is noinit
46 var lcd_backlight_delay = 1000
47
48 var buzzer: Buzzer is noinit
49
50 init
51 do
52 # commandline options for privileges drop
53 var opts = new OptionContext
54 var opt_ug = new OptionUserAndGroup.for_dropping_privileges
55 #opt_ug.mandatory = true
56 opts.add_option(opt_ug)
57
58 # parse and check command line options
59 opts.parse(args)
60 if not opts.errors.is_empty then
61 print opts.errors
62 print "Usage: {sys.program_name} [options]"
63 opts.usage
64 exit 1
65 end
66
67 assert bcm2835_init else print "Failed to init"
68
69 # drop privileges!
70 var user_group = opt_ug.value
71 if user_group != null then user_group.drop_privileges
72
73 # Play button
74 but_play = new Switch(new RPiPin.p1_13, new PUDControl.down)
75
76 # Playlist a button
77 but_playlist_a = new Switch(new RPiPin.p1_15, new PUDControl.down)
78
79 # Vol +
80 var vol3 = new RPiPin.p1_03
81 vol3.fsel = new FunctionSelect.inpt
82 vol3.pud = new PUDControl.up
83
84 # Vol -
85 var vol5 = new RPiPin.p1_05
86 vol5.fsel = new FunctionSelect.inpt
87 vol5.pud = new PUDControl.up
88
89 vol = new RotaryEncoder(vol3,vol5)
90
91 # LCD
92 var lcd_rs = new RPiPin.p1_23
93 var lcd_en = new RPiPin.p1_21
94 var lcd_d4 = new RPiPin.p1_19
95 var lcd_d5 = new RPiPin.p1_26
96 var lcd_d6 = new RPiPin.p1_24
97 var lcd_d7 = new RPiPin.p1_22
98 lcd = new HD44780(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7)
99 lcd.setup
100 lcd.clear
101
102 lcd_backlight = new RPiPin.p1_18
103 lcd_backlight.fsel = new FunctionSelect.outp
104
105 # Buzzer
106 var buzzer_pin = new RPiPin.p1_11
107 buzzer_pin.fsel = new FunctionSelect.outp
108 buzzer = new Buzzer(buzzer_pin)
109 end
110
111 fun run
112 do
113 var tick = 0
114 var last_event = 0
115 loop
116 var force_lcd_update = false
117
118 # play button
119 if but_play.changed and but_play.is_down then
120 print "but"
121 hit_play_stop
122 force_lcd_update = true
123 end
124
125 if but_playlist_a.changed and but_playlist_a.is_down then
126 play_playlist_a
127 force_lcd_update = true
128 end
129
130 # volume
131 var s = vol.update
132 if s != null then
133 if s == '<' then
134 print "vol down"
135 mpd.relative_volume = -vol_step
136 else # >
137 print "vol up"
138 mpd.relative_volume = vol_step
139 end
140 force_lcd_update = true
141 end
142
143 # update lcd
144 if tick % 100 == 0 or force_lcd_update then
145 var status = mpd.status
146 var song = mpd.current_song
147
148 var status_char
149 if status == null then
150 lcd.text = "Unknown status"
151 else if song == null then
152 lcd.text = "No song playing"
153 else
154 if status.playing then
155 last_event = tick
156 status_char = ">"
157 else status_char = "#"
158
159 var tr = status.time_ratio
160 var pos = "-"
161 if tr != null then pos = (status.time_ratio*10.0).to_i.to_s
162
163 lcd.text = "{status_char} {song.artist}\n{pos} {song.title}"
164 end
165 end
166
167 # manage backlight
168 if force_lcd_update then last_event = tick
169
170 var diff_with_last_event = tick - last_event
171 if diff_with_last_event == 0 then
172 lcd_backlight.write(true)
173 else if diff_with_last_event == lcd_backlight_delay then
174 lcd_backlight.write(false)
175 end
176
177 10.bcm2835_delay
178 tick += 1
179 end
180 end
181
182 fun hit_play_stop
183 do
184 # get current status
185 var status = mpd.status
186 var playing = false
187 if status != null then
188 playing = status.playing
189 else
190 print "Cannot get state"
191 return
192 end
193
194 if playing then
195 # stop
196 print "playing -> stop"
197 mpd.pause
198 else
199 print "stopped -> play"
200 mpd.play
201 end
202
203 bell
204 end
205
206 fun play_playlist_a
207 do
208 mpd.load_playlist("alexis")
209 end
210
211 fun bell do buzzer.buzz(1.5, 20)
212 end
213
214 var phy = new PhysicalInterface
215 phy.run