人猪z0oz

人猪z0oz

Monday, June 15, 2020

Building a GERTY 3000 Computer from Moon (2009) - Part 1: Getting Started

The movie "Moon (2009)", by Duncan Jones, was a minor commercial, but major critical success. It was a low budget production, and chances are that you have not (yet?) heard about it. If this is the case, I strongly recommend to stop reading here - and go, watch the movie first. It won numerous film critic and film festival awards and it was nominated for the BAFTA Award for Best British Film. Moon pays homage to quite a few Sci-Fi movies, and in particular to "2001: A Space Odyssey". 

As a big fan of Sci Fi movie props, I was immediately drawn into the board computer, GERTY 3000, which, with a soothing voice (by Kevin Spacey) and an illuminated eye, is a wonderful nod to HAL 9000.


Some time ago, I built a HAL 9000 replica (well, actually two) with a sound module and an infrared motion sensor. When sensing motion, it triggers any of the HAL 9000 phrases from the movie. This really makes it come to life, and it has been a wonderful office companion since.
When watching Moon for the very first time, I liked the idea of building a GERTY 3000 replica. I was looking forward, in particular, to that little screen with Gerty's changing emoticons. Of course, GERTY 3000 has a more complex shape, including many angled pieces, but challenges are fun! What makes this particularly interesting, is that (as far as my Google searches indicate) so far nobody has yet attempted to build a life-sized GERTY 3000 replica. 

References

As usual, at the beginning of a build, I start looking for references. Some are specific for the build, and some are more general about the movie. And although there were not too many (in particular, when compared to HAL 9000), I still found some extraordinary ones.

人猪z0oz

If you watched the movie, or the production pictures in the links above, you find that, in contrast to HAL 9000, GERTY 3000 is a huge device.


For me who is planning to build GERTY 3000 at (approximately) original size, this would occupy too much space. Therefore, I decided to build only the front side, so I could hang it on a wall. As far as I know, this is the first GERTY build at (approx.) the original size.

The main feature that attracts me is GERTY's screen displaying the emoticons (or: "Gerticons" as suggested at "Typeset in the Future"). So, I took this as a starting point. In his blog, production designer Gavin Rothery describes that they used a portable 7-inch DVD player for the screen, but later replaced those images by CGI renderings of the Gerticons. I want to use a Raspberry Pi for this, and ordered a 7-inch LCD display with a resolution of 1024 x 600 pixels.

Starting from the size of the screen, I design the rest in order to keep the proportions. My final result is maybe a little bit smaller as compared to the movie prop, maybe somewhere in the range of 80%-90%. I am building this from plywood (3/16" thick) using a laser cutter. The laser cutter files are made using the open source software librecad. Here is a very preliminary sketch of GERTY 3000, together with my HAL 9000 replica (and my daughter helped me with the Gerticon). 


The final prop will have the following main functions: Like my HAL 9000 build, it will feature an infrared motion sensor, and when it senses motion it will play GERTY's sound clips from the movie.
This responsiveness is what makes my HAL 9000 such a nice office companion (of course: one must limit how often the sounds get played - otherwise it just becomes annoying). Continuously, it will also change the Gerticons displayed on the screen. These functions make it like a "real" GERTY 3000.
In addition, I will add a few practical features like a tea timer, an alarm clock for regular scheduled meetings, and maybe options to display weather information and news. The Raspberry Pi should also be able to do voice recognition which would really be impressive ("GERTY, display the news", "GERTY, start the tea timer"), but that may be some substantial programming effort, and I'm not sure if I really want to pursue that. But I do not have to decide that now...

人猪z0oz

人猪z0ozThe faces ("Gerticons") are the essential elements that allow GERTY to express emotions. Gavin Rothery's original images, that were supposed to play on the 7" DVD player look like this.

For the movie, however, they replaced these with CGI images like these two, more yellow, and on a blue background that becomes darker towards the corners.



While waiting for the delivery of the LCD screen, I am creating the Gerticons to look like the CGI versions that we see in the movie, with the 1024x600 pixel resolution of my screen. Starting with an empty blue screen that is darker towards the corners...


I am adding the "Lunar Industries" logo in the corner...


...and the bottom text in Eurostile Bold Extended font.


These two screen will later also be used for other purposes.
On top of this, I am adding GERTY's ten faces.











Other Screens

I mentioned that I am planning to use GERTY also as a tea timer, so I prepare two additional pairs of screens indicating that the tea timer is running, and when the tea is ready (using the same background).





Another screen will be used when the alarm rings.


In addition, I produce a set of various screens to be displayed while GERTY is booting or shutting down.


I can't remember seeing the following image in the movie, but it appears somewhere in the making-of photos in one of the links above:

And if there is one for booting, then there has to be one for shutdown, right?



And, finally, two more screens. I have no idea yet what they will useful for, but I just had to make them. The first one is an image that I found in this large collection of photos on Flickr, indicating that the rescue unit is arriving.

Nice detail: on the left is a photo of the production designer Gavin Rothery, GERTY's dad. And another photo from the collection above shows Sam Bell on a completely white background, from which I created this.

GERTY's Voice

To play GERTY's sound clips, I need all the individual phrases as separate audio files. First, I made a list of all of GERTY's sound clips that I listed on this blog. Then I am using free software ("handbrake", "VLC player", and "Audacity") to rip these from the DVD, normalize the individual phrases, and apply fade-in and fade-out as described in another blog entry. I skip a few that have too much background noise, and end up with 97 good ones. 
I have uploaded all the individual .wav files on my GitHub page


... to be continued
 



Saturday, June 13, 2020

How to Transfer Data from one Arduino to Another

The Arduino makes it very easy to add simple electronics functions to a device. These include outputs (lights and sounds) as well as inputs (switches, push buttons, motion sensors, temperature sensors, real time clock modules, and many more). In many cases, it can add quite a bit when two devices can communicate and therefore respond to each other.
The most flexible way to achieve this is the serial connection for data transfer. This only requires two or three cables (with no additional electronics) and a few lines of code as described below.

Example of Serial Communication

A simple example are my Back-to-the-Future movie props, which consist of six individual devices that are sitting in the DeLorean Time Machine: the TFC switch, the speedometer, the Time Circuits, the Analog gauges, the SID panel, and the Flux Capacitor. All of these communicate with each other via serial connections. E.g. the TFC switch tells all other devices when it's power switch is turned on, so the other devices start their activity, and the sound module in the Time Circuits plays the "start-up" sound . The speedometer communicates to the other devices the current speed, so the SID panel and the Flux capacitor can increase their activity with increasing speed - and, at 88Mph, the Time Circuits make the "time-jump" and display a new date.

Example Code (One Way Communication)

In this example, I assume that you just need to transfer a single byte, i.e. values in the range 0-255. Of course, this can easily be extended to multiple bytes.
The byte(s) can easily be transferred using a serial connection, using the "TX" (transmit) and "RX" (receive) pins of the two Arduinos. You just connect two cables: the "TX" pin from Arduino #1 to the "RX" pin of Arduino #2, and the GND pins of the two.

In the setup code, you need to start the serial connection with the same baud rate, e.g. 9600 bits per second.  Other possible choices are: 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200 - but 9600 is the standard value that is usually used, so unless speed becomes an issue, I would recommend to use this. Remember: whatever you use, it has to be the same for both Arduinos. So, the setup code for both Arduinos should include this:

 void setup(){
   Serial.begin(9600); // ... other setup code }

Arduino #1 is sending a byte using the following command:

byte data = 0;         // declare the byte-type variable "data"data = 3;              // set the variable to any value in the range [0-255]// or: fill "data" by reading an input pin, or from the result of a calculationSerial.write(data);    // transmit the byte-variable "data"

Arduino #2 is receiving the byte. Therefore it needs to continuously check for new serial data.

void loop() {
  byte data = 0;
   // - check for new serial data - and respond accordingly
  if (Serial.available() > 0) {
      int x = Serial.read();    // The "Serial.read" command returns integer-type
      data = x;
    // - now: do something with "data"
    if (data == 0) { 
      // "0" was received ... do something ...
    } else if (data == 1) {
      // "1" was received ... do something ...
    } else if (data == 2) {
      // ... and so on, and so on ...
    } 
  }
    // ... code to do other tasks(no delays!!!)
}

You absolutely have to make sure that in the "... code to do other tasks" you are not using the command "delay()", as this would prevent your code from checking the serial for new data in a timely manner. If your code needs to wait, you have to implement this as in the "blink without delay" example in he Arduino IDE.

Bi-Directional Communication between Two Arduinos

In the example above, Arduino #1 is sending data to Arduino #2. If you want your Arduino #2 to reply, and send data back to Arduino #1, you only need a third cable, connecting "RX" from #1 to "TX" of #2. Then, using exactly the same code as shown above, your Arduino #2 can also send data, and your Arduino #1 can read these.

Sending Data from One Arduino to Multiple Other Arduinos

For my Back-to-the-Future movie props, I want all other devices to know when the power switch of the TFC is turned on. For this purpose I connect the "TX" pin of Arduino in the TFC to the "RX" pins of all other Arduinos - and I connnect all of their GND pins.
The Arduino in the TFC is then sending the data as in the example above using "Serial.write(data)" and the Arduinos in all the other devices are independently reading the data, each running the "receiving example" above.
But notice, that only one is able to send - it is not possible for all to send data.

 
A More Complex Example

What you could also do: Have two Arduinos (say, #1 and #2) communicate with each other (i.e. bi-directional) and have a few others receive the data from either #1 or #2 (but not from both). Let's say #3 and #4 receive the data from #1. And #5, #6, and #7 receive the data from #2.

For this purpose you would connect:
  • the GND pins of all Arduinos involved
  • the TX pin from #1 to the RX pins of #2, #3, #4
  • the TX pin from #2 to the RX pins from #1, #5, #6, #7

The Timing: Be Reasonable

When implementing the transmit/receive code you should somehow consider the timing. In general, make sure that Arduino #1 is not trying to send data at a faster rate than possible at the given baud rate; and also not faster than Arduino #2 is able to process this. Personally, I try to reduce the amount of serial data as much as reasonably possible.
Two examples:
  • Device #1 has a switch or a push button, and it wants to communicate it's state to device #2. There is no point to transmit the state-of-the-switch information 1000 times per second. Device #1 can keep track of the current state of the switch, and only send serial data to device #2 whenever the state of the switch has changed.
    Alternatively, if you really want to send data continuously, it will likely be sufficient, if you do this 20 times per second (i.e. every 50 milliseconds). This way, there will be a little delay before device #2 will know that the switch has changed it's state - but for most real life applications, a delay of less than 50ms should not be noticeable for a hand-operated switch.
  • Device #1 is a speedometer and sends the current speed to device #2 (which wants to display the speed on an LED display). You do not need to have 100 or 50 or 20 readings per second. 10 readings per second would be plenty, 3 or 4 still probably enough. You could write code, according to the "blink without delay" example that transmits the current speed every 250 milliseconds.
So, do not continuously pump data through the serial connection. Adjust the frequency of the transmissions according to what is really required. 
If the data transfer does not work, maybe try to reduce the frequency at which you send data to see if that helps. If yes, then increase the frequency step by step to see how fast you can get.

Check the Voltages!!!

For all the examples above, it is, of course, essential that all Arduinos involved are operating at the same voltage. So, do NOT mix 5V and 3.3V Arduinos!! However, as long as they are operating at the same voltage you are free to mix different models. For example, you can connect Arduino Unos, Megas, Nano, Pro Minis, etc.  

Tuesday, June 9, 2020

Bidirectional Serial Communication between Arduino and Raspberry Pi



For most of my movie replica props, the Arduino is well-suited to control the lights, sounds, push buttons, etc. Sometimes, however, a prop requires more, like a fast-responding high resolution LCD screen. In those cases am still keeping the Arduino to handle the other electronics, but I am adding a Raspberry Pi to operate the screen and, possibly, access time, date, weather, news information from the internet.

This setup requires the Arduino and the Raspberry Pi to communicate with each other. Conceptually, there are different options to achieve this. Some of these require additional hardware (voltage level shifters) to convert the different operating voltages (most Arduinos: 5V / RPi: 3.3V).



The easiest solution is a simple "serial" connection of the two via a standard USB cable. Just take the cable that you usually use to connect the Arduino to your PC for programming, and plug this into the Raspberry Pi. As a nice side effect, this also provides power to the Arduino, so no additional power connection is needed. So, obviously, this project requires an Arduino with a USB interface, like an Arduino Uno, Mega, or Nano.

For the Arduino, the regular software framework is used  - the Raspberry Pi code is written in Python (Python3, to be precise).

Planning the Data Exchange

The Arduino and Raspberry Pi will be exchanging bytes - in my example a single byte per transfer. One byte can have values in the range 0-255. So, when planning my projects, I'm creating a protocol that includes all possible pieces of information that the two may send each other. Such a protocol for the Arduino-Raspberry Pi serial connection may look like this:

Arduino sends
1        short press of push button #1 
2        long press of push button #1 
3         the PIR infrared motion sensor fired
11-17     request the RPi to display images 1-7 on the screen
21-25     request the RPi to show video clips 1-5
31-87     request the RPi to play audio clips 1-57
91        request the RPi to download new weather data and display it
92        request the RPi to download new news data and display it
93        request the RPi to check Google calendar for upcoming events
RPi sends
1  request the Arduino to turn on LED #1 for 3 sec 
2  request the Arduino to turn on LED #2 for 3 sec
3  request the Arduino to turn on LED #3 
4  request the Arduino to turn off LED #3 
11  request the Arduino to start a short beep on a connected piezo
12  request the Arduino to start a long beep on a connected piezo
21  tell the Arduino that a new hour has started
31-35   tell the Arduino that alarm 1-5 was fired

So, if it is not required to transmit more complex information like dates or times, but only simple requests, a single byte is often sufficient. If a project requires more than a single byte to be transferred, the example below can easily be extended to multiple bytes.

In the following I introduce all the single code pieces required for the Serial communication: The transmit and received codes for the Arduino and the Raspberry Pi. Below, everything is put together in a simple example that demonstrates the full functionality.

The Arduino Code (send & receive)

For any serial communication, the Arduino and Raspberry Pi need to agree on the speed of the communication. Here, I choose a baud rate of 9600. In the Arduino code, this is set in the "setup()" function.
// =======================================================
void setup(){
  Serial.begin(9600);
}
The Arduino code to transfer one byte "n" via serial is:
 Serial.write(n);
Reading a byte received by the Arduino, is done in the following code:
 byte n = 0;
 if (Serial.available() > 0) {
    int var = Serial.read();    // data type: int
    n = var;        
 }
 The "Serial.read" command returns an "integer" result, which is then stored in the byte-type variable "n". While the code to transmit/send a byte is only called when needed, the code for reading a byte must, of course, be called continuously.

The Raspberry Pi Code (send & receive)

For the Raspberry Pi, one first needs to find out the logical port at which the Arduino is connected. After connecting the Arduino via the USB cable, the port can be found by typing (before and after connecting the Arduino):
ls /dev/tty* 
From the difference before/after one can figure out the name of the Arduino port. Typical locations for the port are: 
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyACM0
In my case, it is the first one: "/dev/ttyUSB0". In the Python code this information is used to start the serial connection in the following statement: 
# --- start serial
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
It has to be noted that the Raspberry Pi Python serial code does not send byte- or integer-type variables, but character variables. This requires to convert the bytes before sending, and converting the received characters to bytes.
Sending a byte "x" is done by
x = 11
ser.write(x.to_bytes(1, 'little'))
where the inner function "x.to_bytes" converts the integer variable "x" to the corresponding character, which is then transmitted via serial by the function "ser.write" (note: the function ".to bytes" only works in Python3 - not in Python version 2).

Reading serial data is done by: 
if (ser.in_waiting > 0):
    b = ser.read(1)
    num = int.from_bytes(b, byteorder='big')
Initially the data is read as the character (string) variable "b" which is then converted into the corresponding integer value using the function "int.from_bytes()" (again, the latter only works in Python3 - not in Python version 2).
As already noted for the Arduino, the code for reading a byte must be called continuously.

Example of Arduino-RPi Bidirectional Communication

Here is a complete example for the bidirectional communication between the Arduino and the Raspberry Pi. 
This is what the example code does
  • Twice per second, the Arduino sends a random number between 0 and 49
  • The Raspberry Pi reads the number
  • When the received number is less than 10 the RPi sends a 10 to the Arduino
  • When the received number is larger than 40, the RPi sends a 40 to the Arduino
  • If the Arduino receives a 10, it turns the on-board LED on
  • If the Arduino receives a 40, it turns the on-board LED off

Arduino Example Code

The Arduino code consists of the standard "setup()", "loop()", plus two functions "sendRandom()" and "getSerial()". Continuously the main "loop()" loops over the "sendRandom()" and "getSerial()" functions. The "sendRandom()"routine keeps track of the time and sends every 500 milliseconds a random number to the RPi. The "getSerial()" checks continuously if new data has arrived from the RPi. The additional code in the "loop()" checks if the received data was either 10 or 40 and, correspondingly, turns the on-board LED on or off. Further documentation/explanations are in the code.
// ===========================================
void setup(){
  Serial.begin(9600);delay(2000);// wait 2 seconds
}

// ===========================================
void loop(){
  byte n = 0;sendRandom();// call function to send a random number
  n = getSerial();if (n == 10) {digitalWrite(LED_BUILTIN, HIGH);} else if (n == 40) {digitalWrite(LED_BUILTIN, LOW);}} 
// ------------------------------------------------// --- twice per second, send a byte in range 0-49// --- via serial to RPi// ------------------------------------------------
void sendRandom() { static unsigned long previousTime = 0;byte n = 0;if ( (millis() - previousTime) > 500) { // repeat every 500 mspreviousTime = millis();n = random(50);// random number between 0 and 49Serial.write(n);} }
// -----------------------------------------------------------// --- read a byte from RPi serial // returns zero if nothing was received// -----------------------------------------------------------byte getSerial() {byte n = 0;
if (Serial.available() > 0) {int var = Serial.read();// data type: intn = var;}return (n);}// ===========================================================// ===end of Arduino code// ===========================================================

Raspberry Pi Example Code

And here is the Raspberry Pi Python code (as stated above, it requires Python 3). It imports the module "serial". The code starts the serial connection. Then it starts an infinite loop during which it continues to check if serial data is available. If yes, it reads the data, and if the data is <10 or >40, returns a value of 10 or 40 to the Arduino. 
Note: when the Python code initializes the serial connection (in the command: "serial.Serial('/dev/ttyUSB0', 9600, timeout=1)"), this is resetting the Arduino. So, when you turn on the Raspberry Pi, it powers the Arduino, which is starting to run. When the RPi has finished booting and you start the Python program, this is restarting the Arduino again. Depending on the application, this needs to be taken into account in the Arduino code.

Save the code in a file e.g. as "serial-test.py" and run it from the command line as
python3 serial-test.py 

And here starts the code:

import serial
print("  ============================================")
print("       ---> Serial Python code")
# --- start serial      -->> check port number !!!
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
# ----------------------------------------------
# ---        infinite loop 
# ----------------------------------------------
while True:
  # --- read Serial - display faces & play audio
  if (ser.in_waiting > 0):
    a = ser.read(1)
    num = int.from_bytes(a, byteorder='big')
    print(" received from serial:")
    print(num)
    if num < 10:
      x = 10
      ser.write(x.to_bytes(1, 'little'))
      print("                     turn LED on")
    elif num > 40:
      x = 40
      ser.write(x.to_bytes(1, 'little'))
      print("                     turn LED     off")
# ---------------------------------------------# ---       end of Python code# ---------------------------------------------

When I was looking for help with the serial communication between the Arduino and Raspberry Pi, I had to combine many bits and pieces that I found at different places. I hope that this example includes all aspects that get you up and running with your projects!
Please let me know if it worked for you - or if not.  

Monday, June 8, 2020

Accessing Time and Setting Alarms in Python on the Raspberry Pi


Recently I started coding in Python. Sometimes it was easier and sometimes it was harder for me to find help on the web. Therefore, I decided to document the pieces that I figured out, for myself as a future reference, and also for others.
The following tutorial is about how to access the date and time information in Python on a Raspberry Pi (but it is not restricted to the RPi) and how this can be applied to build an alarm clock or to add such a feature to an existing RPi set-up. Personally, I'm using Python3, but the code below should also work in Python version 2.

Prerequisite: Playing Audio with "aplay"

The example below for the alarm clock is playing a prerecorded audio clip as the alarm signal. For this purpose, it requires a sound file "alarm.wav" (in .wav format!) to be stored in the home directory (/home/pi/alarm.wav). And it assumes that the audio output is set to the 3.5mm headphone jack (using sudo raspi-config -> audio settings). So, before proceeding with the alarm clock example below, make sure you are able to play audio using the following example. Save the following code in a file "audio-test.py", and start it from the command line as "python3 ./audio-test.py".

import subprocess
# -----------------------------------------------------------
# --- set the audio volume -> here for headphone output
# -----------------------------------------------------------
volume = subprocess.Popen(["amixer", "set", "Headphone,0", "100%"])
clip = '/home/pi/alarm.wav'
alarm = subprocess.Popen(["aplay", clip])# -----------------------------------------------------------# --- end of code# -----------------------------------------------------------

Accessing Time Information

If the above code did not work, maybe get back to that later. To follow the alarm clock example, just remove (or comment "#") the lines with the "aplay" commands, and use the printouts.
While there are many Python modules out there that do fancy things, I wanted to keep this as minimal as possible, and only use standard Python module "time". A simple application of the time module functions is to make a program wait for a given amount of seconds, like
time.sleep(1.5)
or
time.sleep(0.1)
Please note that the argument does not have to be an integer, so this can be used to wait for any fraction of a second.

The time module is accessing the current date and time information via the system function "localtime()" of the RPi. Since the RPi does not have a clock, this requires a internet connection. The current date/time can be checked from the command line by typing "date <Return>" for which the output looks like
Sun Jun7 23:26:17 CDT 2020
Compare this with your current time to make sure that the timezone is set correctly. If not, use "sudo raspi-config" and adjust the settings in "Change Timezone".

To access the date and time information, the time module provides the function "time.strftime()". This function (which returns a character string) requires an argument that specifies the formatting. In my code, I prefer to work with numbers instead of strings. So I am not looking into the options, how to nicely format the output of this command. I am only interested in the simplest possible way how to extract the relevant information and convert it into integer numbers.

The command:
hour = time.strftime(%H)
extracts the current hour (in 24-hour format) as a string (00-23) and save the result in the string variable "hour". This string can be converted and stored in an integer variable using
h = int(hour)
Other quantities are extracted correspondingly, using the following arguments in "time.strftime()":
%H        hour (24-hour format) 00-23
%I        hour (12-hour format) 01-12%M        minute 00-59%S        second 00-59%d        day of month as decimal number 01-31%m        month as decimal number 01-12%y        year without century 00-99%Y        year with century (eg: 2020)%w        weekday as decimal number (Sun:0, Mon:1, ..., Sat:6)%U        week number of the year 00-53 (days before the first Sunday are in week 0) 
Other possible arguments return strings for the month and day, but as I stated, I'm only interested in numbers.

Code for an Alarm Clock

With all of the above we can understand the code for the alarm clock. Copy and paste the following code into "alarm.py", and run it with "python3 ./alarm.py" (or "python ./alarm.py"). I added plenty of comments, so (with the discussion above) the code should be clear and easy to read.
Remember to store a .wav file called ("alarm.wav") in the home directory of the RPi (or modify the definition of the variable "clip" if you use a file with a different name and/or location). 
Where it states "# here: insert ... task", you can call other Python code to perform any possible tasks that you like.

import timeimport subprocess
print(" ")
print("  ============================================")
print("       ---> demo code for time access")
print("                and coding alarms")
print(" ")


# --- store the audio file to be played when the alarm startsclip = '/home/pi/alarm.wav'
# --- example: wait for 1.5 sectime.sleep(1.5)
# --- example: wait for 0.1 sectime.sleep(0.1)
# -----------------------------------------------------------
# --- set the audio volume -> here for headphone output
# -----------------------------------------------------------
volume = subprocess.Popen(["amixer", "set", "Headphone,0", "100%"])
# -----------------------------------------------------------
# --- get the initial time
# -----------------------------------------------------------
#
# --- time.strftime() gets information from system via localtime()
# --- get weekday, hour (in 24h format), min as strings
weekday = time.strftime("%w") 
hour = time.strftime("%H") 
minute = time.strftime("%M") ## --- convert all into integer variableslasthour = int(hour)lastminute = int(minute)

print(" current time  ",hour,":",minute,sep='')
print(" day:",weekday,"  (0-6 for Sun-Sat)")
print("")
# -------------------------------------------------------------
# ---              infinite loop
# --- continuously read the time - and start alarm when appropriate# -------------------------------------------------------------
while True:

  # - get day/hour/minute as strings
  weekday = time.strftime("%w") 
  hour = time.strftime("%H") 
  minute = time.strftime("%M") 
  # - convert the strings to integer variables
  w = int(weekday)          
  h = int(hour)
  m = int(minute)# ------ now comes a list of examples for alarms

  # - for hourly alarms
  if (h != lasthour):
    print("  >>> a new hour has begun:",hour)
    alarm = subprocess.Popen(["aplay", clip])# here: insert hourly task 

  # - every half-hour alarm
  if (m != lastminute and m == 30):
    print("  >>> new half-hour:",hour,":",minute)
    alarm = subprocess.Popen(["aplay", clip])# here: insert half-hourly task 

  # - for alarms every 10 minutesif (m != lastminute and (m%10) == 0):
    print("  >>> ten minutes have passed:",minute) alarm = subprocess.Popen(["aplay", clip])
    # here: insert task to be done every 10 minutes 
# - for alarms every minuteif (m != lastminute):
    print("  >>> a new minute begun:",minute)alarm = subprocess.Popen(["aplay", clip])
    # here: insert task to be done every minute

# - for alarms at 8:15  if (m != lastminute and h == 8 and m == 15): 
    print("  >>> alarm at:",hour,":",minute)alarm = subprocess.Popen(["aplay", clip])
    # here: insert task to be done

# - alarm Mon-Fri at 9:30  if (m != lastminute and w > 0 and w < 6 and h == 9 and m == 30): 
    print("  >>> alarm (only Mon-Fri) at:",hour,":",minute)alarm = subprocess.Popen(["aplay", clip])
    # here: insert task to be done
# - alarm on Mon, Wed, Fri at 14:45 (= 2:45pm)   if (m != lastminute and (w%2) == 1 and h == 14 and m == 45): 
    print("  >>> Mon,Wed,Fri alarm at:",hour,":",minute)alarm = subprocess.Popen(["aplay", clip])
    # here: insert task to be done
# - remember the last values of hour and minute, so the above alarms# are only fired once (and not over and over again)  lasthour = h
  lastminute = m
  time.sleep(0.2)
# ---------------------- end of code ---------------------------
# --------------------------------------------------------------
With the comments, the code should (hopefully!) be pretty clear. There are only two details that may require an explanation, the statements (m%10)  and (w%2).

The Modulo Operator

The "%" sign stands for the "modulo" operator which returns the rest in an integer division. Examples:
  • 15 / 3 = 5 and there is zero rest. So: 15%5 = 0 
  • 17 / 3 = 5 with a rest of two. So: 17%5 = 2
  • 40/10 = 4 with a rest of zero. So: 40%10 = 0
  • 43/10 = 4 with a rest of three. So: 43%10 = 3
  • 3/10 = 0 with a rest of three. So: 3%10 = 3
Therefore, the expression (m%10) is equal to zero, when the minutes "m" are equal to 0, 10, 20, 30, 40, 50. So the corresponding alarm fires every ten minutes.
The expression (w%2) is one, whenever, the weekday "w" is equal to 1, 3, 5 - and these correspond to Monday, Wednesday, Friday.

Please let me know if this was helpful - or if not.


Monday, June 1, 2020

HAL 9000 replica - version 2


Some time ago, I built my first HAL 9000 replica. In addition to the famous faceplate (on the right), it also had four displays, a webcam, a IR motion sensor, and some push buttons. The build is documented in a previous blog post. 


A little later, I wanted to build a few for friends. My first version had plenty of "bells and whistles" and it would not be feasible to reproduce all of this. So, for HAL 9000 version #2, I reduced it to the main functionality: Just the faceplate, but keeping the infrared motion sensor which triggers HAL's sound clips from the movie - this is what brings it to life and makes it a perfect office companion (provided, that there is a limit, how often audio clips are played).

Improvements

HAL v2 is built in a very similar way, however, with a few smaller improvements. Again, the design was based on the idea from Adafruit of using a 100 mm arcade button for the eye. This time, however, I decided to go with the clear button (instead of the red one recommended by Adarfuit, that I also used in my first version). The reasoning is that the original HAL had a fisheye lens which is made of clear glass - and the red color came from an internal filter. So, I decided to go with the clear button, and operate it with a red LED (instead of red button with white LED). And I'm glad, I did that - the result look really better! I keep the additional flat marble (which looks like a small internal lens) and the black cardboard.





The second big difference, is that this one is built using a laser cutter. With the laser cutter at hand, I decided to use it also for the speaker grille (which is now also made of plywood) to achieve the original spacing of the holes.





Here are the holders for the clear, red-colored 8mm straw hat LEDs.






At first, I was a little skeptical how the wooden speaker grille would look like. However, after applying the aluminum spray paint, it looks great.



And here comes the enclosure on which the face plate is mounted.



I admit, I was a little lazy regarding the box. I should have spent a little more time filling and sanding the slits and edges... 


Here are the 8mm straw hat LEDs, mounted on their holders.


The PIRs (infrared motion sensors) come with white covers. But at AliExpress, But I found a seller who offered black covers. They cost as much as the sensors, but it's a real improvement for HAL's black case. The sensor is mounted at the bottom of the enclosure, and a white plastic piece wouldn't look good.


Here is the PCB with the Arduino pro mini and the DFPLayer mini.


And a view from the backside with the electronics.


And here is the final HAL 9000 v2. At the bottom, it has three elements:
  • a 2.1mm socket for 5V power (e.g. from a USB charger)
  • the PIR (infrared motion sensor) with a black cover
  • a switch with three positions

The eye LED is constantly at low intensity. When the PIR senses motion, the eye LED goes to high intensity for 3-5 seconds (randomly) and HAL plays a sound clip from the movie (sound clips are not played more than every 1-5 minutes (the time interval increases when there is too much activity) in order not to make it too annoying. The three positions of the switch are: higher volume, lower volume, and sound off. If during operation, the switch is set to "off", HAL 9000 plays all sound clips from the "disable-sequence" in the movie, ending with "Daisy".

Related Blog Posts