Saturday, July 30, 2016

The Universal Remote

Hello, world. I know it's been a long time. Partly, I've been doing non-MSM related things, and partly I got sucked in to a huge project.

My thinking was this: I'm getting impatient to start building things. My landlord's probably going to be annoyed if I start drilling holes in the bottom of my kitchen cupboards, so I should hold off on the hardware for now, but there's nothing stopping me from getting some of the software in place. Remember that universal remote that controls everything? I could get to work on that, right? There's no hardware for it to actually control, of course, but I could get the software infrastructure set up.

All I needed to do was learn to write Android apps, figure out how to get data from the app to my computer (and vice versa), parse the data, and run a program either now or later, depending on the command. How hard could it be?

Let's start with that "learn to write Android apps" part. This was non-trivial. I've never written apps before and my Java is rusty. Honestly, I've never been a huge fan of Java. The "absolutely everything is a class" paradigm is a little counter-intuitive to me. My first language was C and I like the flexibility of C++. Things that make sense to put in classes can be classes. Things that don't make sense to be classes don't have to be. The Java static class does help a bit, but, really, if you're never going to instantiate the class, why make it a class in the first place?

But, at the same time, everyone has different philosophies about how to organize code and intensely class-based is not a bad option for a user interface, so I can live with it.

Unfortunately, remembering Java did not mean I knew how to write Android apps. Figuring out all the APIs was almost like learning an entirely new programming language in itself. However, the Internet is actually a wonderful teacher and once I resolved to actually work through the tutorials instead of taking shortcuts to "just the things I need," I was able to pick it up all right.

Having done that, I was equipped to write the front end. User interfaces have never been my strong suit and I'm downright lousy at graphic design, so it's mostly just lists of buttons that open up more lists of buttons, but it gets the job done.

The interesting stuff is the back end. The universal remote needs to send commands to the computer, for example "Cook oatmeal at 9:00am" or "Start the laundry." I used the sockets functions in Java and C to get the computer and phone talking, and, I'll be honest, I copied most of that code from the Internet. (It's a good thing Google knows everything.) I learned a lot about networking in the mean time, but, ultimately, I didn't really need to be doing anything too fancy here.

Where I did want to get slightly fancy was with the data I was sending. As I was thinking about the right way to organize this, it occurred to me that I was basically developing a simple machine language and that the entire Mad Scientist Mansion could be considered the world's most unconventional processor.

So, with that organization in mind, I came up with the following command packet:

hour minute tag repeat command task params

where
hour is the hour to start (-1 for immediately)
minute is the minute to start (-1 for immediately)
repeat is how often to repeat the process (never, daily, or weekly)
tag associates this process with a specific program so that programs can be turned off or on together.
command is the basic command. I should probably have come up with a better name for this. It refers to essentially which part of the house this process deals with. For example, kitchen, laundry room, or lights.
task is the task to do in the given command region. For example, get status or cook.
params are any other parameters the task requires. For example, the recipe to cook.

I'm fairly happy with this structure. It's sufficiently general to meet all my foreseeable needs and so far it's been tolerably easy for the receiving C code to parse. (Obviously, I wrote the receiver in C.) Except. Except that the sender and the receiver must agree precisely on where everything is in the packet and what all the constants mean.

And this is where things almost got a little wacky. See, the sender is in Java and the receiver is in C, so it's not like they can share code in any sort of straightforward way. Java doesn't even have a notion of a preprocessor. For a moment, I was tempted to write a bash script that would take my C header files and use that data to go edit the constants in my Java files so that everything would match up automatically.

I didn't do it. I concluded that the probability of errors from the script was at least as great as the probability of errors doing it by hand and that writing the script would take longer than doing it by hand since these things are not likely to change a whole lot once they're initially defined.

The problem remained that I had to figure out what to do about the constants in Java. See, not only does Java not have a preprocessor, Java hates the idea of sharing constants across classes. All the Java forums I went to trying to figure out what a Java equivalent of #define was said that if you're using constants in more than one class, you're violating the whole point of modularized class-based design.

To be fair, after thinking about it, I concluded that argument did have some merit. On the other hand, if I need to define constant commands kitchen=0, laundry=1, lights=2, etc, and to be sure that the same two numbers were not used twice, does it really make sense to define the kitchen constant in the kitchen class, the laundry constant in the laundry class, the lights constant in the lights class, and so forth, so that I have to look through every single class to be sure there are no collisions? So I went ahead and violated good style and made a constants class. What can I say? I'm trying to turn my future house into a processor. I'm going to have to do some unconventional things.

So the hour, minute, tag, repeat, and command constants all went into a common class. I did somewhat localize the task and parameters constants, though they are still in separate constants classes accessible by any of the code. The problem is, in my attempt to create a good user interface, there's a bit of redundancy. For example, there's a kitchen screen where you can do things related to the kitchen (including read its status), and there's also a status screen where you can get the status of everything in the house (including the kitchen). Since different screens are different classes in the Android organization scheme, these two need to share constants. Maybe I could have created some sort of intermediate class that both the kitchen screen and the status screen use, but...at some point good enough is good enough.

That was the sender side. Now for the receiver, which had its own issues. The main difficulty is that I want to be able to run things at preset times. It is entirely reasonable that I will want to say something like, "Make oatmeal at 8:30 every morning." Linux has a nice way to do this in the form on cron, which can run a program at a specified time. I did not know about cron when I started this adventure, so there was a bit of a learning curve to figure that out. Also, cron is really meant to be used from the command line. In fact, you use command line tools to get you to a file that you edit. The problem is that I want to automatically create a cron entry based on the data from the command packet the computer receives from the phone.

Once again, I think I'm doing something a little unconventional here. The Internet is full of ideas about how to run a program from cron. That is, after all, the purpose of cron. It is considerably less helpful when trying to figure out how to run cron from a program. I ended up splitting my receiver code into two programs: One to receive a packet and schedule it and one to parse and execute the instruction. The "receive a packet" program uses system() and some command line acrobatics to communicate with cron, and cron then runs the "parse and execute" program.

In retrospect, the "receive a packet" program could probably have been a bash script, but I was initially thinking the whole thing would be one program and it was easier to just separate the code than to knock the rust of my bash scripting.

The parser/executor, fortunately, has not, so far, been too bad. Just lots of switch statements. It will get a lot more interesting once there's hardware.

So, to recap:

I have a user interface on my phone which sends network packets to my computer which are received by a program that interfaces with the operating system via cron to schedule a call to another program that can parse the packet and then print, "This function is not yet implemented." Nothing to it!

The big TODO at this point is getting status data back from the computer to the phone, but I am exhausted and will deal with that later.

Finally, about the code. My original intention was to post my code as I went, both to make it available to the universe and so I would have a back up copy, but it turns out there are an enormous number of files involved, and I'm too lazy. So, until it's a final draft (ha ha, like this is ever going to be a final draft) I'm not going to worry about it.

No comments:

Post a Comment