Tuesday, December 30, 2014

cloudClock: DS3231 RTC

Bought one of this from Dealextreme a while ago for this project, but only now I had the time to look into trying it. The exact one I bought is the one in the picture above, link here.

In order to use it, I searched for available libraries and turns out someone had already done all the work. The library can be found here and requires installing also the Time library, available here.  To connect it to the arduino is plain easy, vcc, ground, SDA and SCL and that's all. Sounded easy... plugged it, compiled the example and... FAIL... hmmm... it did not work... nothing... After reviewing the code and the soldering, could not see anything wrong, so I went to the basics... Arduino Yun pinout. If you see the Arduino Yun pinout you may notice how SDA and SCL are in 2 places and if you see my wiring diagram for the arduino yun internet radio, you will noticed that my rotary encoder is plugged in the pins carrying the SDA and SCL signals... oops... unplug the rotary encoder and there I had it... RTC working. So this is how it looks like now (corresponding change will have to be done to the yun radio code to adapt to the rotary encoder pin change, also the RTC module does not correspond to the one I am using, but pins are named identically):




Sunday, November 23, 2014

Reverse engineering SJCAM4000 WiFi... well... kind of...



I got a so called Action Camera. Not enought interest on the whole video recording thing to spend a fortune on a GoPro, so I went for the Chinese option and bought a SJCAM 4000 wifi. I will not get into the whole review and unboxing discussion... that is not the purpose of this blog, you have plenty of pages already discussing that... google that...

Anyway, so the camera comes with a wifi option. How does that work? the camera opens an access point, you connect with an iOS or android app and you can configure and control the camera, see live video or download recordings (videos or pictured). That is great feature, I think, it saves you from having to extract the microSD card from the camera, which makes things easier, but I prefer using my laptop for downloading the pictures and videos than my handset, but no ubuntu app... grrrr.... had to figure out the protocol used in wifi mode...

I connected my laptop to the camera access point. I had to figure out the ip address of the camera:

arp -a

? (192.168.1.254) at 18:83:bf:XX:XX:XX [ether] on wlan0

Then I tried connecting to that IP with telnet and ssh without luck. Lastly I tried opening the webpage in that IP: http://192.168.1.254

Bingo!!! It opened a web page that allowed browsing the microSD card contents and even upload files, but no sight of configuration options or live video feed. That was really all I was looking for, but hell... I want the whole enchilada.

I installed an app in my android phone that allows capturing tcpdumps. I used tPacketCapture, which does not require having a rooted phone. If your phone is rooted, you can just use tcpdump from busybox. I captured a connection with my android app while I was doing some changes in the settings to inspect it later in the PC with wireshark. So here is what I found:

  1. The live video feed is just an RTSP connection, pretty standard and no encryption... cool... So connected the laptop back to the camera, opened VLC, Media->Open Network Stream and typed the URL I saw in my tcpdump pcap file: rtsp://192.168.1.254/sjcam.mov voila... live video from the camera
  2. The pcap file also had some http get requests. I could distinguish 2 types of requests, those with a parameter and those with out it. I assume the ones with no parameter are requests for info and the ones with a parameter are request for changing a setting. See the examples bellow (red is from laptop to camera, blue is from camera to laptop)
with no parameter:


GET /?custom=1&cmd=3016 HTTP/1.1
accept: */*
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.2; -------------------)
Host: 192.168.1.254
Connection: Keep-Alive
Accept-Encoding: gzip

HTTP/1.1 200 OK
Server: eCos/1.0
Cache-Control:no-store, no-cache, must-revalidate
Pragma: no-cache
Accept-Ranges: bytes
Content-length: 97
Content-type: text/xml
Connection: close

<?xml version="1.0" encoding="UTF-8" ?>
<Function>
<Cmd>3016</Cmd>
<Status>1</Status>
</Function>


with no parameter:


GET /?custom=1&cmd=2002&par=2 HTTP/1.1
accept: */*
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.2; -------------------)
Host: 192.168.1.254
Connection: Keep-Alive
Accept-Encoding: gzip

HTTP/1.1 200 OK
Server: eCos/1.0
Cache-Control:no-store, no-cache, must-revalidate
Pragma: no-cache
Accept-Ranges: bytes
Content-length: 97
Content-type: text/xml
Connection: close

<?xml version="1.0" encoding="UTF-8" ?>
<Function>
<Cmd>2002</Cmd>
<Status>0</Status>
</Function>

I did not dig further in this, but assume that each of those cmd numbers corresponds to a camera setting, and the parameter (par) is the value to set, but still need more research to figure out cmd and settings. I hope to make a python library to control the camera at some point, but will need some time to reverse engineer all the commands and parameters.

The last thing I did on my first weekend with the camera was try to watch the videos in my laptop and mobile phone, well, turns out the format of the recorded videos is ".MOV" and both mobile and laptop had issues with that format, even VLC which seems to play everything had issues: video was stopping and audio was out of sync. I converted the ".MOV" file to ".mp4" using avconv:

avconv -i input_file.MOV -vcodec copy -acodec mp2 output_file.mp4

What this does is generate an mp4 file in which the video is unaltered (no reencoding done, the video from the mov, h.264 compressed, is extracted and muxed again in the mp4 file without alteration). I have never been too keen on recompressing the video if it is already good from the source, unless there is a real need for it (reduce the file size). The audio gets compressed in mpeg layer 2 (audio codec in the ".MOV" is lpcm, no compression). Mp4 file format does not seem to support lpcm audio and I don't mind that much compressing the audio.

Sunday, November 9, 2014

cloudClock: Force an update of the time with ntpd

Whenever ntpd detects a huge different between the system time and the ntp server time, ntpd does not update the time. I should investigate further on why it behaves that way, but I think I know enough about ntpd already for now... so I investigated how to make ntpd to force the update and this is what I found:

To force a large NTP update, do the following:

Stop the NTPD service ("/etc/init.c/ntpd stop")
run ntpd -gnqd
Start the NTPD service again ("/etc/init.c/ntpd start")

Monday, October 20, 2014

cloudClock: ntpstat

In order to find out the status of the ntp daemon, there seem to be 2 possibilities: ntpq or ntpstat.
ntpq is by far the one that gives more info, really too much for my liking, I just want to find out if the clock is in sync or not!! so I went for ntpstat. As I mentioned in my previous post, there is no ntpstat in linino, so I had to compile it from scratch. This was easy. I got the ntpstat source code from here and compiled it doing:

../linino/trunk/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-gcc ntpstat.c -o ntpstat

Monday, October 13, 2014

cloudClock: time keeping

Part of the beauty of the cloudClock is the fact that it does not require having to set the time. For that I will use ntpd to keep linino on time. Turns out that linino already has ntpd running:

/usr/sbin/ntpd -n -p 0.openwrt.pool.ntp.org -p 1.openwrt.pool.ntp.org -p 2.openwrt.pool.ntp.org -p 3.open

First thing I thought was that I would like to change the ntp servers being used and select some servers near by. I also wanted to the change the timezone to my current timezone. Turns out both those things are configured in the same file: /etc/config/system

Original file was:

config system
option hostname 'Arduino'
option timezone 'UTC'
option timezone_desc 'Rest of the World (UTC)'

config timeserver 'ntp'
list server '0.openwrt.pool.ntp.org'
list server '1.openwrt.pool.ntp.org'
list server '2.openwrt.pool.ntp.org'
list server '3.openwrt.pool.ntp.org'
option enable_server '0'

And my modified file is now:

config system
option hostname 'Arduino'
option timezone 'CET-1CEST,M3.5.0,M10.5.0/3'
option timezone_desc 'Europe/Madrid'

config timeserver 'ntp'
list server '0.pool.ntp.org'
list server '1.pool.ntp.org'
list server '2.pool.ntp.org'
list server '3.pool.ntp.org'
option enable_server '0'

I got the timezone to set from this list: http://wiki.openwrt.org/doc/uci/system#time.zones

I need a way to find out if ntpd is working correctly. There seems to be 2 possibilities: ntpstat and ntpq, but I could not find ntpstat in linino, so tried with ntpq. Unfortunately ntpq was not working either. My conclusion was that it was using busybox ntpd, rather than a fully featured ntpd, so I installed ntpd doing:

opkg update
opkg install ntpd
/etc/init.d/sysntpd disable
/etc/init.d/ntpd enable
/etc/init.d/ntpd start

Now ntpq works!!! just need to figure out how do I parse the output of ntpq to get something meaningful.

On the arduino side, I bought an RTC chip module to make sure the clock is on time, even when there is no internet connection. I will get back to that whenever the RTC module arrives.


Saturday, October 11, 2014

cloudClock: getting flite to work in Arduino Yun

In my last entry I installed flite from opkg and when I tried to run it with a simple "flite -t hello" all I got was a Segmentation Fault, so next step was to fix that.

First thing was find out what was causing it to seg. fault, strace to the rescue. For newbies: strace is a linux tool for tracing system calls and signals... a must know for a linux developer. Strace was not included in linino by dafault, so "opkg install strace". Once installed I did: "strace flite" and got the following:

strace flite
execve("/usr/bin/flite", ["flite"], [/* 11 vars */]) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77435000
stat("/etc/ld.so.cache", 0x7fce6bf0)    = -1 ENOENT (No such file or directory)
open("/lib/libflite_cmu_us_kal16.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite_cmu_us_kal16.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=3705608, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0007\360\0\0\0004"..., 4096) = 4096
old_mmap(NULL, 3772416, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x77087000
old_mmap(0x77087000, 3693236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x77087000
old_mmap(0x7741c000, 15112, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x385000) = 0x7741c000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libflite_usenglish.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite_usenglish.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=88944, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0b\320\0\0\0004"..., 4096) = 4096
old_mmap(NULL, 155648, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x77061000
old_mmap(0x77061000, 68232, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x77061000
old_mmap(0x77082000, 19312, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x11000) = 0x77082000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libflite_cmulex.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite_cmulex.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=597680, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0!\360\0\0\0004"..., 4096) = 4096
old_mmap(NULL, 663552, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76fbf000
old_mmap(0x76fbf000, 590444, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x76fbf000
old_mmap(0x77060000, 3760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x91000) = 0x77060000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libm.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=85356, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\26\200\0\0\0004"..., 4096) = 4096
old_mmap(NULL, 151552, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f9a000
old_mmap(0x76f9a000, 84896, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x76f9a000
old_mmap(0x76fbe000, 3436, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x14000) = 0x76fbe000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libflite.so.1", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=125740, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\\ \0\0\0004"..., 4096) = 4096
old_mmap(NULL, 192512, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f6b000
old_mmap(0x76f6b000, 121476, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x76f6b000
old_mmap(0x76f99000, 2860, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x1e000) = 0x76f99000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=77080, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0006\320\0\0\0004"..., 4096) = 4096
old_mmap(NULL, 143360, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f48000
old_mmap(0x76f48000, 76672, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x76f48000
old_mmap(0x76f6a000, 3352, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x12000) = 0x76f6a000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\237\320\0\0\0004"..., 4096) = 4096
old_mmap(NULL, 446464, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76edb000
old_mmap(0x76edb000, 352964, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x76edb000
old_mmap(0x76f41000, 7343, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x56000) = 0x76f41000
old_mmap(0x76f43000, 17508, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76f43000
close(3)                                = 0
munmap(0x77434000, 4096)                = 0
open("/lib/libflite_cmulex.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite_cmulex.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=597680, ...}) = 0
close(3)                                = 0
open("/lib/libflite_usenglish.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite_usenglish.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=88944, ...}) = 0
close(3)                                = 0
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=77080, ...}) = 0
close(3)                                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
close(3)                                = 0
open("/lib/libflite.so.1", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=125740, ...}) = 0
close(3)                                = 0
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=77080, ...}) = 0
close(3)                                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
close(3)                                = 0
open("/lib/libflite.so.1", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/usr/lib/libflite.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=125740, ...}) = 0
close(3)                                = 0
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=77080, ...}) = 0
close(3)                                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
close(3)                                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
close(3)                                = 0
open("/lib/libm.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=85356, ...}) = 0
close(3)                                = 0
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=77080, ...}) = 0
close(3)                                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
close(3)                                = 0
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=359599, ...}) = 0
close(3)                                = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755, st_size=28968, ...}) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x77434000
set_thread_area(0x7743b750)             = 0
mprotect(0x76f41000, 4096, PROT_READ)   = 0
mprotect(0x77436000, 4096, PROT_READ)   = 0
ioctl(0, TIOCNXCL, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, TIOCNXCL, {B38400 opost isig icanon echo ...}) = 0
brk(0)                                  = 0x9f2000
brk(0x9f3000)                           = 0x9f3000
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xf0000} ---
+++ killed by SIGSEGV +++
Segmentation fault

From the highlighted text, you can see that it did not find any of the shared libraries that flite needs. A search for those libraries showed that they were on a different folder from where flite was searching. They were in /usr/lib as opposed to /lib:

root@Arduino:~# find / -name *flite*
/overlay/usr/bin/flite
/overlay/usr/lib/libflite_cmu_time_awb.so.1.3
/overlay/usr/lib/libflite_usenglish.so.1.3
/overlay/usr/lib/opkg/info/flite.control
/overlay/usr/lib/opkg/info/flite.list
/overlay/usr/lib/libflite_cmu_time_awb.so.1
/overlay/usr/lib/libflite_cmulex.so.1.3
/overlay/usr/lib/libflite_cmu_us_kal16.so.1.3
/overlay/usr/lib/libflite_usenglish.so.1
/overlay/usr/lib/libflite.so.1
/overlay/usr/lib/libflite_cmu_us_kal.so.1.3
/overlay/usr/lib/libflite_cmu_us_kal.so.1
/overlay/usr/lib/libflite.so.1.3
/overlay/usr/lib/libflite_cmulex.so.1
/overlay/usr/lib/libflite_cmu_us_kal16.so.1
/usr/bin/flite
/usr/lib/opkg/info/flite.control
/usr/lib/opkg/info/flite.list
/usr/lib/libflite_cmu_time_awb.so.1.3
/usr/lib/libflite_usenglish.so.1.3
/usr/lib/libflite_cmu_time_awb.so.1
/usr/lib/libflite_cmulex.so.1.3
/usr/lib/libflite_cmu_us_kal16.so.1.3
/usr/lib/libflite_usenglish.so.1
/usr/lib/libflite.so.1
/usr/lib/libflite_cmu_us_kal.so.1.3
/usr/lib/libflite_cmu_us_kal.so.1
/usr/lib/libflite.so.1.3
/usr/lib/libflite_cmulex.so.1
/usr/lib/libflite_cmu_us_kal16.so.1

I just copied them across to /lib, but that did not work. Strace did no longer complain about the libraries not being found, but seg. fault was still there. From here I figured out other people had gone through the same issue in openWRT and fixed it by compiling 1.4 instead of 1.3. They were so kind there to also share the new makefile, which did not quite fit, but it was a good starting point. So I replaced the Makefile for flite in my linino tree (you may want to see this again for how to compile packages for linino) in linino/trunk/feeds/packages/sound/flite with this one:

#
# Copyright (C) 2006-2010 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk

PKG_NAME:=flite
PKG_VERSION:=1.4-release
PKG_RELEASE:=1

PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2
PKG_SOURCE_URL:=http://www.speech.cs.cmu.edu/flite/packed/flite-1.4/
PKG_MD5SUM:=b7c3523b3bbc6f29ce61e6650cd9a428
PKG_CAT=bzcat

PKG_BUILD_PARALLEL:=1
PKG_INSTALL:=1

include $(INCLUDE_DIR)/package.mk

define Package/flite
  SECTION:=sound
  CATEGORY:=Sound
  DEPENDS:=@!GCC_VERSION_3_4_6
  TITLE:=Text-to-speech for embedded systems
  URL:=http://www.speech.cs.cmu.edu/flite/index.html
endef

define Package/flite/description
Festival Lite is a stripped down version of Festival,
    the well-developed text-to-speech program written in C++.
    This program is written in C to keep memory usage low.
endef

TARGET_LDFLAGS += -lm

define Build/Configure
$(call Build/Configure/Default, \
--enable-shared \
--with-audio="oss" \
--with-vox="cmu_us_kal16" \
--prefix="$(PKG_INSTALL_DIR)/usr" \
--exec-prefix="$(PKG_INSTALL_DIR)/usr" \
--bindir="$(PKG_INSTALL_DIR)/usr/bin" \
)
endef

define Build/InstallDev
$(INSTALL_DIR) $(1)/usr/include/flite
$(CP) $(PKG_INSTALL_DIR)/usr/include/flite/{cst,flite}*.h $(1)/usr/include/flite/
$(INSTALL_DIR) $(1)/usr/lib
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libflite*.so.* $(1)/usr/lib/
endef

define Package/flite/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/flite $(1)/usr/bin/
$(INSTALL_DIR) $(1)/usr/lib
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libflite*.so.* $(1)/usr/lib/
endef

$(eval $(call BuildPackage,flite))

I also remove the patches folder and compiled flite 1.4 by doing "make package/flite/compile V=s" in /linino/trunk. After that I removed the previously installed flite in linino by doing "opkg remove flite" and copied the result of my compilation by doing:

rsync -avz -e ssh linino/trunk/build_dir/target-mips_r2_uClibc-0.9.33.2/flite-1.4-release/ipkg-install/usr root@192.168.0.9:/

Running then "flite -t hello" resulted in my arduino saying hello. Unfortunately, randoms attempts of running the same line ended up on the board throwing an error message instead of talking. The error is the following:

flite -t hello
VAL: tried to access car in 119 typed val

Getting there, but not quite there yet...

In the same place where I got the suggestion of using version 1.4, they suggest running it many times until it succeeds. I am using this script (based on the one they had):

REP=1
until [ "$REP" -eq 0 ]
do
flite -voice kal16 -t  "$1"
REP="$?"
done

The birth of a new project: CloudClock

So my alarm clock broke recently. Actually it was only the powersupply. What a great excuse for new project with my arduino yun!!

Since I built my grooveshark radio, my yun has been unused, I do not find time to listen to music at home these days, so the grooveshark radio was not of much use, so my broken alarm was a good excuse to put the yun back on business.

My plan is the following:
1. No screen: The thing should talk
2. Just one button: All the control should be via web
3. Should be reliable: I want it to be a real replacement for my broken alarm, so I want it to be reliable as a commercial device... or more.

For 1 and 2, I might end up cheating and using the 16x2 screen and the rotary encoder... who knows...

So my first step was getting the yun to talk. For that I decided to use festival lite (or flite). So... ssh into linino, opkg update, opkg install flite:
good news: the package was there
bad news: it did not fit in the available memory

So I had to extend my linino memory and for that I could use the SD card port. Knowing that someone else would have probably done this already, did a google search and voila: Expanding Yun Disk Space

Worked...

Boot again, ssh into linino, opkg update, opkg install flite... now it fitted!!!

next step was to give it a try: flite -t hello
Segmentation Fault

Back to the begining... now need to figure out why it does not work...

Sunday, March 30, 2014

Android Midi Apps development

I am working on a new project which requires developing an android application to access a USB midi interface to receive and send MIDI messages. Being almost new to Android as I am, I would expose here the different steps I followed to get a basic demo to work.

Installing Android SDK

I installed the ADT bundle from here: https://developer.android.com/sdk/index.html?hl=sk
Process was straight forward: download, extract and run.

I also tried the new Android Studio, but had some issues migrating existing projects, so I decided to give that up.

If it is your first Android App, I recommend going through the building your first app.

Building the Android MIDI USB package sample Application

The package I used for using USB MIDI devices is this one: https://github.com/kshoji/USB-MIDI-Driver

The package comes with a test application that logs the MIDI activity and can do passthrough of the MIDI traffic (send the incoming MIDI to the output MIDI device).

I built the application and installed it on my android device. Using a USB on the go cable a MIDI to USB adapter and a MIDI keyboard I had it working. For future development of my own app, that process seemed slow and messy, so I investigated other alternatives. I first tried using the AVD, the android emulator that comes with the SDK, but somehow that was running very slow on my machine and I found out I can not access the laptop's USB devices from my emulator, so it was not a good choice. I decided to install an android-x86 virtual machine.

Installing an Android-x86 Virtual Machine 

That got me an Android-x86 Virtual Machine in VirtualBox. Next step was configuring the network and configuring the USB access on my Virtual Machine.

Network Configuration: I used the "bridged adapter" option, which makes the virtual machine behave as a new device connected to the local network. Once the network configuration is as desired, the next step was to set up adb to be able to access the virtual machine over the network. To do that, the first step is to find the ip address of the android virtual machine. Doing "ALT+F1" you can get into the linux a console terminal and once there doing "netcfg" you can find the IP address. From the host system you can do "adb connect ip_address:5555" and then you will be able to run adb commands just as you would do with a device connected to the computer, including running you applications directly from Eclipse.

USB passthrough from Host to Guest machine: This is what I did to get access to the USB MIDI device from my android virtual machine:
1. Install the Virtual Box extension pack
2. In the Machine USB settings select "Enable USB Controller" and "Enable USB 2.0 (EHCI) Controller"
3. In the host machine run "usermod bill -a -G vboxusers", replacing bill with your user name, and log out and log in again for the change to take effect.

And that is all, I run again my MIDI sample application, now on my virtual machine, and connected my USB MIDI cable and my MIDI keyboard and had the application working correctly (slow, but working).

Saturday, February 22, 2014

Fully featured grooveshark radio

I finally got all the basic functionality that I was looking for on my yun based grooveshark radio. Now I can play grooveshark radios, playlists and user collections. I made all the code and schematic available in github for everyone to be able to use. It is here:

https://github.com/desordenado77/internet_radio

Excuse my python... this is my first python program attempt... Arduino I had some experience from before, but not an awful lot either.

Next step is to make a nice case for it.

UPDATE: I have added a hook to my github account to publish all my commits in the blog also

Saturday, February 8, 2014

Adding 16x2 LCD and Rotary Encoder

As mentioned in my previous post, I got the remaining parts I needed for my grooveshark radio project: LCD, rotary encoder and hifi USB audio DAC.

First step was to get the LCD and rotary encoder to work with Arduino. For that purpose I made a board following the schematic on the next picture:


This is how the board looked like:


The two black things in the bottom are connectors for the cables that go to the arduino.

You can get instructions on how to use the LCD here: http://arduino.cc/es/Tutorial/LiquidCrystal
And regarding the rotary encoder, I used this library: http://www.pjrc.com/teensy/td_libs_Encoder.html

Works like a charm!!!

And this is the previous Arduino code modified to use both the LCD and the rotary encoder (the linino side remains unchanged):

#include <Encoder.h>
#include <LiquidCrystal.h>
#include <FileIO.h>

#define    GENRE_NONE             0
#define    GENRE_ROCK             12
#define    GENRE_BLUES            230
#define    GENRE_ELECTRONICA      67
#define    GENRE_CLASSICROCK      3529
#define    GENRE_INDIE            136
#define    GENRE_METAL            17


int stations[] = { GENRE_NONE,
    GENRE_ROCK,
    GENRE_BLUES,
    GENRE_ELECTRONICA,
    GENRE_CLASSICROCK,
    GENRE_INDIE,
    GENRE_METAL };


String stations_str[] = { "None",
    "Rock",
    "Blues",
    "Electronica",
    "Classic Rock",
    "Indie",
    "Metal" };
    
int genres;
int val = 0;
int enc_val = 0;
int prevVal = 0;
Process radio_process;
String label_song, label_artist;



LiquidCrystal lcd(13, 12, 11, 10, 9, 8);
Encoder knob(2,4);
int knob_sw = 3;

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);

  // Print a message to the LCD.
  lcd.print("Grooveshark!!");

  pinMode(knob_sw, INPUT);
  digitalWrite(knob_sw, HIGH);

  genres = sizeof(stations)/sizeof(int);
  Serial.begin(9600);
  Bridge.begin();

  start_radio(val);  

}

void loop() {
  long knob_value = knob.read();

  if(knob_value > 0) {
    enc_val++;
    if(enc_val>=genres) enc_val = genres - 1;
  }
  else {
    if(knob_value < 0) {
      enc_val--;
      if(enc_val<0) enc_val = 0;      
    }
  }
  
  int sw_value = digitalRead(knob_sw);  
  
  Serial.println(sw_value);
  if(!sw_value){
    val = enc_val;
  }
  
  if(knob_value!=0 || !sw_value) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Radio:");
    lcd.setCursor(0, 1);
    if(enc_val == val) {  
      lcd.print("-");
    }
    else {  
      lcd.print(" ");
    }

    lcd.print(stations_str[enc_val]);
  }
  knob.write(0);

  
  if(val != prevVal) {
    String text;
    text = "Value: ";
    text += val;

    start_radio(val);
    
    prevVal = val;
  } 
  
  char labelbuffer_song[256];
  char labelbuffer_artist[256];
  Bridge.get("songName", labelbuffer_song, 256); 

  if (String(labelbuffer_song).length() > 0 && label_song != String(labelbuffer_song)){
    lcd.clear();
    label_song = String(labelbuffer_song);
    
    lcd.setCursor(0, 1);
    lcd.print(label_song);
    Bridge.get("songArtist", labelbuffer_artist, 256);
    label_artist = String(labelbuffer_artist);
    
    lcd.setCursor(0, 0);
    lcd.print(label_artist);
    
  }
  
}

void start_radio(int val) {
    String parameter = "";
    radio_process.close();
    Process killer;
    killer.begin("killall");
    killer.addParameter("mpg123");
    killer.run();
    if(stations[val] != 0) {
      radio_process.begin("python");
      radio_process.addParameter("/root/examples-pygrooveshark/radio_mpg123.py");
      parameter+= stations[val];
      radio_process.addParameter(parameter);
      radio_process.runAsynchronously();
    }
    else {
      radio_process.begin("echo");
      radio_process.addParameter("Do Nothing!!!");
      radio_process.runAsynchronously();
    }
}


Moving the rotary encoder I can move through the radio genres and by pressing the rotary encoder shaft I can select it.

What is next? improve both the UI and the python script to add playlists, user favourites and popular songs.

Friday, January 31, 2014

Got parts

Just received the missing parts for my grooveshark radio project: 16x2 LCD and rotary encoder from http://www.dx.com and USB soundcard with optical output from eBay. The fun starts now!!!


Thursday, January 23, 2014

Capacitance measurement with the Arduino Uno

I guess this opens up a new section... "how did THEY do that?"
Now this is a cool use of arduino for those of us doing also analog stuff:

http://wordpress.codewrite.co.uk/pic/2014/01/21/cap-meter-with-arduino-uno/

The cap meter in my multimeter kind of sucks, so I will definatelly try this.

Friday, January 17, 2014

Using REST for communication between AVR and Linux

I changed my code on my previous post to use REST as a way to communicate between Arduino and Linux. REST is basically a web server running on the linux side which allows access to key/value pairs via web or through serial from Arduino. In particular I am using this now for sending the info about the song that is playing. From my python script I am calling curl to POST the info to the REST website and from Arduino I am doing Bridge.get calls to receive the data.

Here is the new python script:


from __future__ import print_function
import urllib
import urllib2
import subprocess
import sys

from grooveshark import Client
from grooveshark.classes import Radio

client = Client()
client.init()
cmdargs = str(sys.argv)

url_name='http://localhost/data/put/songName/'
url_artist='http://localhost/data/put/songArtist/'
url_album='http://localhost/data/put/songAlbum/'

for song in client.radio(sys.argv[1]):
    
    song_name = unicode(song.name).encode("utf-8")
    song_artist_name = unicode(song.artist.name).encode("utf-8")
    song_album_name = unicode(song.album.name).encode("utf-8")


    print(song_name)
    print(song_artist_name)
    print(song_album_name)

    url_name_curl = url_name + urllib.quote(song_name)
    urllib2.urlopen(url_name_curl)
    url_artist_curl = url_artist + urllib.quote(song_artist_name)
    urllib2.urlopen(url_artist_curl)
    url_album_curl = url_album + urllib.quote(song_album_name)
    urllib2.urlopen(url_album_curl)

    sys.stdout.flush()
    subprocess.call(['mpg123', song.stream.url])


UPDATE:
There is a pycurl library that I could have used instead (and there seem to be other alternatives, I am just not a python expert), but this seemed quicker to implement at the time. Will find an alternative... 
I am using urllib2 to do the POST requests to the REST website. Originally I was using curl (running it in a subprocess) but urllib2 avoids calling a separate process.
Important thing is to remember calling urllib.quote on the text to POST, as you will need to convert special characters to "%XX" to make it a valid URL.

On the Arduino side I am only getting the song name, but it is enough to get the idea of how this is done. This is how my sketch looks like now:


#include <FileIO.h>

#define    GENRE_NONE             0
#define    GENRE_ROCK             12
#define    GENRE_BLUES            230
#define    GENRE_ELECTRONICA      67
#define    GENRE_CLASSICROCK      3529
#define    GENRE_INDIE            136
#define    GENRE_METAL            17


int stations[] = { GENRE_NONE,
    GENRE_ROCK,
    GENRE_BLUES,
    GENRE_ELECTRONICA,
    GENRE_CLASSICROCK,
    GENRE_INDIE,
    GENRE_METAL };

int genres;
int val = 0;
int prevVal = 0;
Process radio_process;
String label;

void setup() {
  genres = sizeof(stations)/sizeof(int);
  Bridge.begin();
  Serial.begin(9600);

  while(!Serial);  // wait for Serial port to connect.
  Serial.println("Grooveshark Radio example");

  start_radio(val);  
}

void loop() {
  val = analogRead(3);
  val = (val*genres)/1023;

  
  if(val != prevVal) {
    String text;
    text = "Value: ";
    text += val;
    Serial.println(text);

    start_radio(val);
    
    prevVal = val;
  }
// removed old code to print the output of the script: 
/*
  while(radio_process.available() > 0){
    char c = radio_process.read();
    Serial.print(c);
  }
*/
  char labelbuffer[256];
  Bridge.get("songName", labelbuffer, 256); 

  if (String(labelbuffer).length() > 0 && label != String(labelbuffer)){
    label = String(labelbuffer);
    
    Serial.println(label);

  }
  
  
  delay(400);
}

void start_radio(int val) {
    String parameter = "";
    radio_process.close();
    Process killer;
    killer.begin("killall");
    killer.addParameter("mpg123");
    killer.run();
    if(stations[val] != 0) {
      radio_process.begin("python");
      radio_process.addParameter("/root/examples-pygrooveshark/radio_mpg123.py");
      parameter+= stations[val];
      radio_process.addParameter(parameter);
      radio_process.runAsynchronously();
    }
    else {
      radio_process.begin("echo");
      radio_process.addParameter("Do Nothing!!!");
      radio_process.runAsynchronously();
    }
}