Self-hosting my alarm clock radio

I don’t like waking up, before going to sleep I tend to schdule many alarms and the next morning I turn off each one of them.

When I was younger I remember having an old radio alarm that would turn on at a scheduled time, I used it as an wake up alarm and I remember being effective.

Maybe because hearing people talking would be less numbing that hearing the same song every day, and it would motivate me more to not turn off the alarm, maybe, I don’t know.

But I wanted to try this.

Radio

The trivial solution would be to just buy a radio with an alarm, but I discarded this from the start because

  1. I don't like the radio static, and even less when I can listen to a live broadcast on the internet very clearly.
  2. I didn’t want to spend money

Mobile

My first attempt was downloading an app that would do this. As I’m already waking up with my cellphone I might as well continue using it.

I tried the RadioDroid app, it seems like it has the features I want, it connects to a radio URL and can be used as an alarm clock. But upon using a few nights I’ve noted that the alarm doesn’t always go off. I guess it might be because it’s trying to attempt an active connection and Android goes into idle mode after a while?

Besides I like to turn Wi-Fi off at night, and I realise that it lacks some other nice-to-haves, like adjusting the volume incrementally.

I tried others apps but this was the more promising, I don't want payware, and I surely didn’t feel like programming in Android, so it seemed like I would need another option.

Raspberry Pi

I have a couple of raspberry pis through the house, one of them I use it as a server for various services that are running all day long. So I thought about using it for this purpose as well.

I moved the raspi to the bedroom and connected to it some old PC speakers I had.

Ignore the poor state of the raspberry

Having the interface of a whole computer now I had much more control, as I can program it however I want. All I had to do is play audio through the speakers at a certain time.

The player

The core of the schedule is to just to reproduce audio through the raspberry’s speakers, so I needed an audio player for it.

My priorities were that I needed a program that would be lightweight, CLI so I could run it directly on the terminal, and that could also play radio files.

I wasn’t going to install anything fancy like VLC or MPV just to play an audio file. So after searching around I found mpg123, which fullfilled my needs perfectly, it is extremely lightweight, is CLI, doesn’t have an interface and reproduces radio files directly from the URL.

The radio

As I said on the beginning, I wanted to hear people talking, so I searched for an AM radio. The most popular here regardless of political spectrum is Radio Mitre, which I don't find it entirely despicable, so it works for me.

I scrapped it’s web and found a link for a MP3 file that could be reproduced directly

http://27323.live.streamtheworld.com/AM790_56.mp3

And after executing the following on the raspberry pi.

$ mpg123 http://27323.live.streamtheworld.com/AM790_56.mp3
High Performance MPEG 1.0/2.0/2.5 Audio Player for Layers 1, 2 and 3
	version 1.26.4; written and copyright by Michael Hipp and others
	free software (LGPL) without any warranty but with best wishes

Directory: http://27323.live.streamtheworld.com/

Terminal control enabled, press 'h' for listing of keys and functions.

Playing MPEG stream 1 of 1: AM790_56.mp3 ...
ICY-NAME:
ICY-URL: https://radiomitre.com.ar/

MPEG 1.0 L III cbr96 44100 j-s

ICY-META: StreamTitle='SEP WHATSAPP MITRE -SMD';

It’s working! The live radio is coming out from the speakers.

Off button

Now that it works, I need a way to turn it off when I’m already awake.

As I was thinking about how I could attach a physical button to the raspi and having to attach wires and other electronical things and such. I thought to myself

”Wait, I’m a Node.js developer, I don’t need buttons, I can host a bloated express server just to listen for an action, and then send a HTTP request from my phone to trigger it.”

So I had all the elements to start programming.

Programming

I started with the core functionality, spawning the radio process.

const { spawn } = require('node:child_process');

const play = spawn('mpg123', ['http://27323.live.streamtheworld.com/AM790_56.mp3']);

And then handling the process killing on an express endpoint.

const express = require('express');

const app = express();

app
  .get('/apagar', (_req, res) => {
    try {
      play.kill(9);
      res.send('Apagado\n');
      process.exit(0);
    } catch (error) {
      res.status(500).send(error);
      process.exit(1);
    }
  })
  .listen(3000);

On my phone, I created a termux shortcut script that does the HTTP request.

curl $RASPBERRY_IP:3000/apagar

From this I can turn the alarm off with a single tap on my phone.

And the core functionality is done!

Cron job

All I had to do now is set the alarm in motion each morning, which I simply added a cron job to execute it at a determined time. In this case at 08:30.

30 8 * * * cd radespertador && npm run start

Increasing volume and parametrizing

One last feature I’d like to have is to the volume being increasing slowly, I don’t want to get startled all of the sudden of my sleep.

I had to spawn another child process with a call to control the volume, and in intervals call the controller with smaller increments of the percentage for the volume.

const setVolume = volume => spawn('amixer', ['sset', "'Headphone'", `${volume}%`]);

setVolume(0);
let currentVolume = 0;
setInterval(() => {
  setVolume(currentVolume > 99 ? currentVolume : ++currentVolume);
}, 2000);

And of course it would be a bad practice to leave all of this with literals and magic numbers, so I had everything parameterized, and the values can be tweaked on the config file.

You can see the final code here.

Final result

Here’s a recording of how it works, for demostration purpose the increment for the volume is way more frequent than it is in reality

Wait for it

Conclusion

Did it work? Kind of, I still struggle to wake up. But at least now I can listen to boomers say random things on the morning before turning off the alarm, and I can say that I build that.

Hacking my local trains schedules API

Here in Buenos Aires we rely mainly on 3 modes of public transportation: buses, subways and trains. Buses are often the primary choice because they're everywhere, but in terms of efficiency and speed subways and trains are by far the best choice, if you can take advantage of the proximity of a railroad branch line.

Say you want to take a train somewhere, and you want to make sure you are on time, you can download the android app " Trenes Argentinos " from the Google Play store, developed by the private company SOFSE, and check the time schedules there.

Upon installing it you'll see right away that the app starts requiring permissions, permissions that a simple app for checking the trains schedule shouldn't require, like GPS location and file storage access.

If you deny these requests it would still prompt you every time you select a station.

Shady dialogs in the app, the right one appears everytime you select a station

If you're like most people you probably wouldn't care much about these things because the app in the end actually fulfills its purpose.

You don't mind having your privacy being invaded any more than it already is every day, or having to navigate to a horrible UX by closing a dozen of pop-ups like it's the early internet days.

Thankfully I'm not, so I began searching for the API behind it so I could just write a bash script and curl it away.

Research

Being a public transportation service, I expected the API to also be public.

For example, in the internal jurisdiction of the city of Buenos Aires they implemented an unified public transport API specification that provides information on buses, subways and even the status of the city's traffic lights.

However, this does not seem to be the case for trains, after googling a little bit I didn't find anything, and everything pointed to this being a private service. Even in violation of the national law 27,275 that guarantees the right of access to public information.

This leaves me with only one option.

Hacking the App

I downloaded the app's raw apk to my pc with a generic apk downloader, and after researching a little bit about how to decompile an android app file, I ended up using a program called jadx.

>: jadx com.mininterior.trenesenvivo.apk
INFO  - loading ...
INFO  - processing ...
ERROR - finished with errors, count: 8

I didn't care much about those errors because I wasn't going to recompile the app anyway, I just wanted to look for the internal service it used.

So I started browsing the decompiled codebase and found the URL right away.

<string name="trenesApiUrl">https://apiarribos.sofse.gob.ar/</string>

And the endpoint's paths and params.

@GET("v1/estaciones/buscar")
Call<PaginationContainer<Estacion>> buscarEstaciones(@Query("nombre") String str, @Query("lineas") RetrofitArray<Integer> retrofitArray, @Query("ramales") RetrofitArray<Integer> retrofitArray2, @Query("exclude") RetrofitArray<Integer> retrofitArray3, @Query("limit") Integer num, @Query("orderBy") String str2);

@GET("v1/alertas")
Call<List<AlertaResponse>> getAlertas();

...

Before I started testing, and thinking I had everything I needed, to my suprise I also found a little file called TokenAuthenticator.java.

Thinking to myself: "Well, they probably added an authentication token that is freely obtainable with an endpoint, in order to avoid getting spammed".

But after browsing the file a little bit:

String userApi = getUserApi();
String codificar = encode(userApi);
...
tokenRequest.setUsername(userApi);
tokenRequest.setPassword(codificar);

Why would there be a user and a password, if the app doesn't require a login or anything like that?

And then I found it:

public static String getUserApi() {
  Date date = new Date();
  return Base64.encodeToString(
    (new SimpleDateFormat("yyyyMMdd").format(date) + "sofse").getBytes(),
    2
  );
}

public static String encode(String str) {
    String stringBuffer = new StringBuffer(
    Base64.encodeToString(
      new StringBuffer(
        Base64.encodeToString(str.getBytes(), 2)
          .replace("a", "#t")
          .replace("e", "#x")
          .replace("i", "#f")
          .replace("o", "#l")
          .replace("u", "#7")
          .replace("=", "#g")
      )
        .reverse()
        .toString()
        .getBytes(),
      2
    )
      .replace("a", "#j")
      .replace("e", "#p")
      .replace("i", "#w")
      .replace("o", "#8")
      .replace("u", "#0")
      .replace("=", "#v")
  )
    .reverse()
    .toString();
}

Up until this point I thought that maybe they didn't publish their API because they were lazy, or because they did not see the need to do so.

But no, not only did they fill their app with shady storage and GPS accesses, not only did they not publish their API specification evading a national law, but they also obscured the access to it with some encoding, being 100% assholes.

I felt that I had a moral obligation to not only circumvent this, but also to publish it so anyone can use it freely.

Remaking it

I wanted to make a public proxy that would bypass all of this, so I made a simple nodejs program using express

The core of the program would just be something like:

const app = express();
app.get('*', redirection)
app.listen(PORT);

And in the redirection middleware it would be located the generation and maintenance of such obscured tokens, of which they should be made in the same way as in the app.

After a bit of trial and error, after having to root my phone in order to be able to sniff HTTP requests, because I couldn't get to replicate the same exact encoding, I finally did it, I was able to bypass the token generation and make direct use of the API.

You can find the full source code here.

Publication and usage

I set up a public instance of this proxy service on the same server where this blog is hosted, by the path /api-trenes.

With this service anyone can make request to it, and it would take care of the internal authentication, without the user having to worry about it.

For example you can get the lines information:

curl 'https://ariedro.dev/api-trenes/lineas'

Or the schedules to go from "Drago" to "Miguelete" (Previously you would need to find the corresponding ids for such stations):

curl 'https://ariedro.dev/api-trenes/estaciones/236/horarios?hasta=271'

You can find more info and the full endpoints specification in the repository.

Having all this live, I can finally make scripts on for example Termux and be able to get the schedules information instantly, without having to go through all the spyware in the app.

Further things

I am not worried if SOFSE finds this and wants to make a legal fuss about it, after all, they are the ones who are legally wrong in this issue, being a state-subsidized company, they should keep its information public.

And in the end, this was more an exercise in discovery and reverse engineering rather than actively trying to affect anyone.

I hope you liked this blogpost, and I look forward to keep posting other interesting stuff like this more often.