ded48fb0670e7c8ef1db9db729d614b97a93b37d
[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 redef class Object
32 fun mpd: MPDConnection do return once new MPDConnection("localhost", 6600, "password")
33 end
34
35 class RotaryEncoder
36 var pin_a: RPiPin
37 var pin_b: RPiPin
38 var old_a= false
39 var old_b= false
40
41 # returns '<', '>' or null accoring to rotation or lack thereof
42 fun update: nullable Char
43 do
44 var new_a = pin_a.lev
45 var new_b = pin_b.lev
46 var res = null
47
48 if new_a != old_a or new_b != old_b then
49 if not old_a and not old_b then
50 # everything was on
51 if not new_a and new_b then
52 res = '<'
53 else if new_a and not new_b then
54 res = '>'
55 end
56 else if old_a and old_b then
57 # everything was off
58 if not new_a and new_b then
59 res = '>'
60 else if new_a and not new_b then
61 res = '<'
62 end
63 end
64
65 old_a = new_a
66 old_b = new_b
67 end
68
69 return res
70 end
71 end
72
73 class LCD
74 var rs: RPiPin
75 var en: RPiPin
76 var d4: RPiPin
77 var d5: RPiPin
78 var d6: RPiPin
79 var d7: RPiPin
80
81 var ds = new Array[RPiPin]
82
83 # commands
84 fun flag_clear_display: Int do return 1
85 fun flag_return_home: Int do return 2
86 fun flag_entry_mode_set: Int do return 4
87 fun flag_display_control: Int do return 8
88 fun flag_cursor_shift: Int do return 16
89 fun flag_function_set: Int do return 32
90 fun flag_set_cgram_addr: Int do return 64
91 fun flag_set_ggram_addr: Int do return 128
92
93 # entry mode
94 fun flag_entry_right: Int do return 0
95 fun flag_entry_left: Int do return 2
96 fun flag_entry_shift_increment: Int do return 1
97 fun flag_entry_shift_decrement: Int do return 0
98
99 # display flags
100 fun flag_display_on: Int do return 4
101 fun flag_display_off: Int do return 0
102 fun flag_cursor_on: Int do return 2
103 fun flag_cursor_off: Int do return 0
104 fun flag_blink_on: Int do return 1
105 fun flag_blink_off: Int do return 0
106
107 # display/cursor shift
108 fun flag_display_move: Int do return 8
109 fun flag_cursor_move: Int do return 0
110 fun flag_move_right: Int do return 4
111 fun flag_move_left: Int do return 0
112
113 # function set
114 fun flag_8bit_mode: Int do return 16
115 fun flag_4bit_mode: Int do return 0
116 fun flag_2_lines: Int do return 8
117 fun flag_1_line: Int do return 0
118 fun flag_5x10_dots: Int do return 4
119 fun flag_5x8_dots: Int do return 0
120
121 fun function_set(bits, lines, dots_wide: Int)
122 do
123 var fs = flag_function_set
124 if bits == 8 then
125 fs = fs.bin_or(16)
126 else if bits != 4 then abort
127
128 if lines == 2 then
129 fs = fs.bin_or(8)
130 else if lines != 1 then abort
131
132 if dots_wide == 10 then
133 fs = fs.bin_or(4)
134 else if dots_wide != 8 then abort
135
136 write(true, fs)
137 end
138
139 fun display_control(on, cursor, blink: Bool)
140 do
141 var fs = flag_display_control
142
143 if on then
144 fs = fs.bin_or(flag_display_on)
145 else fs = fs.bin_or(flag_display_off)
146
147 if cursor then
148 fs = fs.bin_or(flag_cursor_on)
149 else fs = fs.bin_or(flag_cursor_off)
150
151 if blink then
152 fs = fs.bin_or(flag_blink_on)
153 else fs = fs.bin_or(flag_blink_off)
154
155 write(true, fs)
156 end
157
158 fun entry_mode(left, incr: Bool)
159 do
160 var fs = flag_entry_mode_set
161
162 if left then
163 fs = fs.bin_or(flag_entry_left)
164 else fs = fs.bin_or(flag_entry_right)
165
166 if incr then
167 fs = fs.bin_or(flag_entry_shift_increment)
168 else fs = fs.bin_or(flag_entry_shift_decrement)
169
170 write(true, fs)
171 end
172
173 fun setup
174 do
175 ds = [d4,d5,d6,d7]
176
177 rs.fsel = new FunctionSelect.outp
178 en.fsel = new FunctionSelect.outp
179 d4.fsel = new FunctionSelect.outp
180 d5.fsel = new FunctionSelect.outp
181 d6.fsel = new FunctionSelect.outp
182 d7.fsel = new FunctionSelect.outp
183
184 rs.write(false)
185 en.write(false)
186
187 # wait 20ms for power up
188 #50.bcm2835_delay
189
190 #write_4bits(true,true,false,false)
191 #write_4_bits(3)
192
193 #5.bcm2835_delay
194
195 #write_4bits(true,true,false,false)
196 #write_4_bits(3)
197
198 #5.bcm2835_delay
199
200 #write_4bits(true,true,false,false)
201 #write_4_bits(3)
202
203 #200.bcm2835_delay_micros
204
205 #write_4bits(false,true,false,false)
206 #write_4_bits(2)
207
208 # wait 5ms
209 #5.bcm2835_delay
210
211 # set interface
212 # 4bits, 2 lines
213 #write(true, flow)
214 #function_set(4, 2, 8)
215
216 # cursor
217 # don't shift & hide
218 #display_control(true, true, true)
219
220 # clear & home
221 #write(true, flag_)
222 #clear
223
224 # set cursor move direction
225 # move right
226 #write(true, 6)
227
228 # turn on display
229 #write(true, 4)
230
231 # set entry mode
232 #entry_mode(true, true)
233
234 write(true, "33".to_hex)
235 write(true, "32".to_hex)
236 write(true, "28".to_hex)
237 write(true, "0C".to_hex)
238 write(true, "01".to_hex)
239 end
240
241 fun write_4_bits(v: Int)
242 do
243 var lb = once [1,2,4,8]
244 for i in [0..4[ do
245 var b = lb[i]
246 var r = b.bin_and(v) != 0
247 var d = ds[i]
248 d.write(r)
249 end
250 pulse_enable
251 end
252
253 fun write_4bits(a,b,c,d:Bool)
254 do
255 d4.write(a)
256 d5.write(b)
257 d6.write(c)
258 d7.write(d)
259 pulse_enable
260 end
261
262 fun pulse_enable
263 do
264 en.write(false)
265 1.bcm2835_delay_micros
266 en.write(true)
267 100.bcm2835_delay_micros
268 en.write(false)
269 end
270
271 fun write(is_cmd: Bool, cmd: Int)
272 do
273 en.write(false)
274 rs.write(not is_cmd)
275
276 # high byte
277 var hb = once [16,32,64,128]
278 for i in [0..4[ do
279 var b = hb[i]
280 var r = b.bin_and(cmd) != 0
281 var d = ds[i]
282 d.write(r)
283 end
284
285 en.write(true)
286
287 # wait 450 ns
288 1.bcm2835_delay_micros
289
290 en.write(false)
291
292 if is_cmd then
293 # wait 5ms
294 5.bcm2835_delay
295 else
296 # wait 200us
297 200.bcm2835_delay_micros
298 end
299
300 # low byte
301 var lb = once [1,2,4,8]
302 for i in [0..4[ do
303 var b = lb[i]
304 var r = b.bin_and(cmd) != 0
305 var d = ds[i]
306 d.write(r)
307 end
308
309 en.write(true)
310
311 # wait 450ns
312 1.bcm2835_delay_micros
313
314 en.write(false)
315
316 if is_cmd then
317 # wait 5ms
318 5.bcm2835_delay
319 else
320 # wait 200us
321 200.bcm2835_delay_micros
322 end
323 end
324
325 fun clear
326 do
327 write(true,1)
328 2.bcm2835_delay
329 end
330
331 fun return_home
332 do
333 write(true,2)
334 2.bcm2835_delay
335 end
336
337 fun text=(v: String)
338 do
339 clear
340 return_home
341 for c in v do write(false, c.ascii)
342 end
343 end
344
345 fun hit_play_stop
346 do
347 # get current status
348 var status = mpd.status
349 var playing = false
350 if status != null then
351 playing = status.playing
352 else
353 print "Cannot get state"
354 return
355 end
356
357 if playing then
358 # stop
359 print "playing -> stop"
360 mpd.pause
361 else
362 print "stopped -> play"
363 mpd.play
364 end
365 end
366
367 # commandline options for privileges drop
368 var opts = new OptionContext
369 var opt_ug = new OptionDropPrivileges
370 #opt_ug.mandatory = true
371 opts.add_option(opt_ug)
372
373 # parse and check command line options
374 opts.parse(args)
375 if not opts.errors.is_empty then
376 print opts.errors
377 print "Usage: {program_name} [options]"
378 opts.usage
379 exit 1
380 end
381
382 assert bcm2835_init else print "Failed to init"
383
384 # drop privileges!
385 var user_group = opt_ug.value
386 if user_group != null then user_group.drop_privileges
387
388 # Debug LED
389 var out = new RPiPin.p1_11
390 out.fsel = new FunctionSelect.outp
391 out.write(false)
392
393 # Play button
394 var inp = new RPiPin.p1_13
395 inp.fsel = new FunctionSelect.inpt
396 inp.pud = new PUDControl.down
397
398 # Vol +
399 var vol3 = new RPiPin.p1_03
400 vol3.fsel = new FunctionSelect.inpt
401 vol3.pud = new PUDControl.up
402
403 # Vol -
404 var vol5 = new RPiPin.p1_05
405 vol5.fsel = new FunctionSelect.inpt
406 vol5.pud = new PUDControl.up
407
408 var vol = new RotaryEncoder(vol3,vol5)
409 var vol_step = 2
410
411 # LCD
412 var lcd_rs = new RPiPin.p1_23
413 var lcd_en = new RPiPin.p1_21
414 var lcd_d4 = new RPiPin.p1_19
415 var lcd_d5 = new RPiPin.p1_26
416 var lcd_d6 = new RPiPin.p1_24
417 var lcd_d7 = new RPiPin.p1_22
418 var lcd = new LCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7)
419 lcd.setup
420 #lcd.write(false, 'a'.to_i.to_ascii)
421 lcd.clear
422 lcd.write(false, 'a'.ascii)
423 lcd.write(false, 'C'.ascii)
424
425 var last_in = false
426 var led_on = false
427 var tick = 0
428 loop
429 # play button
430 var lev = inp.lev
431 if lev != last_in then
432 last_in = lev
433 if lev then
434 print "hps"
435 hit_play_stop
436 end
437 end
438
439 # volume
440 var s = vol.update
441 if s != null then
442 if s == '<' then
443 print "vol down"
444 mpd.relative_volume = -vol_step
445 else # >
446 print "vol up"
447 mpd.relative_volume = vol_step
448 end
449 end
450
451 if tick % 100 == 0 then
452 print tick
453 #var now_playing = mpd.status("")
454 #lcd.text = tick.to_s
455 end
456
457 10.bcm2835_delay
458 tick += 1
459 end