Digital Clock using Rust (toy project)
Tags: rust
Published: 2022-12-15 16:01:48

---
title: Digital Clock using Rust (toy project)
published: true
description: An attempt to model a digital clock using boolean logic and 7-LED display
tags: rust
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n942p91g4pr5x08mpjjg.png
# Use a ratio of 100:42 for best results.
---

Attribution: Cover image by <a href="https://www.freepik.com/free-vector/watch_3099029.htm#query=digital%20alarm%20clock&position=5&from_view=keyword">Image by rawpixel.com</a> on Freepik

I am a Rust newbie. Like many other Rust (#rustlang) learners, I am also cutting my teeth on the language by paging through the Rust book ([Link](https://doc.rust-lang.org/edition-guide/rust-2021/index.html/)) and the one from Oreilly ([Link](https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/)), solving small puzzles, writing code snippets, and rummaging through a host of helpful blog-posts, as well as questions/answers on _Stackoverflow_ and Rust users' group ([Link](https://users.rust-lang.org/)).

On one such evening, while wandering through Youtube, I had come across this particular video ([Link](https://www.youtube.com/watch?v=gX6EFBICIcY)) by ([Andy Thomason] (https://www.youtube.com/@andythomason8112)). In the video, Andy codes online, and takes us through a small application which brings up a digital clock on the screen. I found that quite interesting, especially the way he modeled the arms of the digits ([code is here](https://github.com/simple-rust-projects/simple-rust-clock)). The code was wonderfully concise and posed no problem for a newbie like me to follow.

I thought I will take this idea further, primarily to strengthen my Rust skills. My intention has been to emulate a few things while writing this toy application:

* To model a 7-Led BCD decoder for each digit
* To use special ASCII characters to represent each Led  and adopt ANSI Escape Sequences for cursor positioning and drawing on a text - xterm, in my case - terminal (note: Andy did the same, I just followed his footsteps)
* To behave as if the clock's ticks are coming in as external signals, as opposed to sleeping between two successive calls to get current time
* To trap a CTRL-C command (that stops the application) and ensure that the cursor is back to its regular blinking behaviour

----------------------------------------------------------

Note: Let me repeat that all the above are _emulations_, an attempt to highlight the core behaviour; no hardware is interfaced with the toy application I have written.

-----------------------------------------------------------

#### BCD for each digit

For a realistic clock ( **HH:MM:SS** ) to appear on the screen, 6 digits are needed, namely H, H, M, M, S, S. Each of these digits is supposed to be a 7-segment display unit.

![Typical 7-segment display electronic unit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oi64rzx8a42zvninrp3.png)
(Source: https://en.wikipedia.org/wiki/Seven-segment_display)

For a digit to be visible, several of these segments - also called LEDs (or LCDs) - should glow and other should remain unlit. The LEDs are numbered - _lettered_, to be more accurate - thus:
![Enumerated 7 LEDs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utximg6ywhrzxlbh50yl.png)

Source: https://en.wikipedia.org/wiki/Seven-segment_display

Each digit can be one of the 10 values, namely '0' to '9'. For example, for displaying digits '6', '0' and '2', the LEDs should light up as below:

![Lit and unlit LEDs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i0curiayz8724zo3yl20.png)

Source: https://www.electricaltechnology.org/2018/05/bcd-to-7-segment-display-decoder.html

The following table shows, the input signal combinations vs the corresponding LED-borne digits:
![Truth Table for 3 digits](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0bfejp9hirvkd9d9bbt.png)

The point is that using a 4-bit input set - referred to as **B**inary **C**oded **D**ecimals (BCD) - all the 10 digits can be displayed. (the truth-table is in the README).

I have modeled:
* every LED as a _struct_ :

```rust
// Datatype that captures a Led
pub struct Led {
    name: String,
    show_character: String,
    hide_character: String ,
    light_status: bool,
    on_receiving_next_signal: fn(&u8) -> bool // Closure that evaluates a BCD-signal, before deciding what the light_status should become
}
```

* A BCD signal as an `u8` (unsigned 8-bit): 

```rust
pub struct Nibbles(pub u8);
```

The 4 most-significant-bits (MSB) of this _u8_ are ignored always and the 4 least-significant-bits (LSB) represent the BCD for each of 10 digits. Moreover, 4 LSB combinations which represent values '10' to '15' are ignored as well (the truth-table is in the README).

##### Logic to determine if the LED should be lit

Using Karnaugh Map, the minimal boolean signal combination is arrived at, for every LED. For example, based on the table above, the boolean logic that leads to a lit or unlit LED 'a' is this:

```rust
const LED_A_GATE_LOGIC: fn(&u8) -> bool = | input: &u8 | {
    // 8-bits and BCD (MSBs start from leftmost)
    // 0       0       0       0       A       B      C      D
    // Using karnaugh Map and don't care conditions: A + C + B.D + ~B.~D

    (input & 0b00001000 == 0x08)              // *nibble_a == 1u8
        || (input & 0b00000010 == 0x02)       // *nibble_c == 1u8
        || (input & 0b00000101 == 0x05)       // (*nibble_b == 1u8 && *nibble_d == 1u8)
        || (input & 0b00000101 ==  0x00)      // (*nibble_b == 0u8 && *nibble_d == 0u8)
};
```

While constructing any specific LED, the logic is passed on as a _closure_. By defining them as `const`, I find it easier to read, test and if necessary, modify them:
```rust
pub fn new(name: &str, displayChar: &str, hide_character: &str, evaluator: fn(&u8) -> bool) -> Led {
        Led {
            name: name.to_string(),
            show_character: displayChar.to_string(),
            hide_character: hide_character.to_string(),
            light_status: false,
            on_receiving_next_signal: evaluator
        }
    }
```
---------------------------------------------------

Every LED has a `show_character` and a `hide_character` to represent its display in the _lit_ and _unlit_ mode. I have used special ASCII characters to help show a LED on the terminal. For example, for LED 'a', it is "━━━━" and for LED 'b', it is  " ┃": the space prefixed is for easier positioning on the screen. The section below exemplifies this.

----------------------------------------------------

At the call-site of calling the constructor of LED 'a':
```
Led::new("a", "━━━━", "    ", LED_A_GATE_LOGIC /* closure as a const */);
```



#### Data and Code structure

This is the easier part. Using regular OO technique:
* A *DisplayUnit* is composed of 7 LEDs of its own and is responsible for constructing those
* A *ScreenClock* is composed of 6 DisplayUnits and is responsible for constructing those

A `DisplayUnit` is a `struct`:
```rust
pub struct DigitDisplayUnit {
    // TODO: These leds should be in a map, identifiable  by the letter associated
    led_a: Led,
    led_b: Led,
    led_c: Led,
    led_d: Led,
    led_e: Led,
    led_f: Led,
    led_g: Led,
}

impl DigitDisplayUnit {
    pub fn new() -> DigitDisplayUnit {
        let leda = Led::new("a", "━━━━", "    ", LED_A_GATE_LOGIC);
        let ledb = Led::new("b", " ┃", "  ",     LED_B_GATE_LOGIC);
       // ...

        DigitDisplayUnit {
            led_a: leda,
            led_b: ledb,
            // ..
        }
    }
```

So, to create a ScreenClock, we do this:
```rust
pub struct ScreenClock {
    top_left_row: u8,
    top_left_col: u8,
    display_units: [DigitDisplayUnit;6],
}

impl ScreenClock {
    pub fn new( start_at_row: u8, start_at_col: u8 ) -> ScreenClock {

        // From left to right, on the display panel!
        let digital_display_unit0 = DigitDisplayUnit::new(); // h_ of hh
        let digital_display_unit1 = DigitDisplayUnit::new(); // _h of hh
        let digital_display_unit2 = DigitDisplayUnit::new(); // m_ of mm
        let digital_display_unit3 = DigitDisplayUnit::new(); // _m of mm
        let digital_display_unit4 = DigitDisplayUnit::new(); // s_ of ss
        let digital_display_unit5 = DigitDisplayUnit::new(); // _s of ss


        ScreenClock {
            top_left_row: start_at_row,
            top_left_col: start_at_col,
            display_units: [
                digital_display_unit0,
                digital_display_unit1,
                digital_display_unit2,
                digital_display_unit3,
                digital_display_unit4,
                digital_display_unit5,
            ]
        }
    }
// ... rest of it
```
Once the skeleton is in place, solution becomes apparent to one:

- Decide the position of the clock on the screen (row / column)
- Create a `ScreenClock` ( refer to the constructor `new`)
- Every second on the system's own date/time
-- Pass the current hour/minute/second readings to `ScreenClock`
-- Refresh the `ScreenClock` (i.e. produce the LEDs on the screen)

-----------------------------------------------------

On the left, this digital clock and on the right, Unix `date` command's output, on my Ubuntu 22.10 laptop, with terminal type _xterm-256color_ :

![Screen play](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qnpem9ollx48kkn4k00.gif)

-----------------------------------------------------


#### Respond to a tick

The simplest way to get the current system time every second is to use `Local::now()` from the `chrono::` crate, after a second's duration and `sleep`ing in between. However, my intention has been to implement a behaviour of reaction: let a nudge arrive at a second's frequency indicating that a second has elapsed and then, the current time be read again. This way there is no enforced `sleep`ing.

It turns out that `crossbeam::` crate has exactly the facility that I need, a function named `tick`:

```rust
crossbeam_channel::channel
pub fn tick(duration: Duration)
```
This is how I set it up:
```rust
let notification_on_next_second = tick(Duration::from_secs(1));
```
Whenever a tick arrives, the application reads the current time and uses that to drive the digital boolean logic, which lights up the LEDs as needed.

#### Trap CTRL-C to restore the cursor

At the beginning, the application hides the cursor by issuing a ANSI Escape sequence, thus:
```rust
print!("\x1b[?25l");
```
When the user terminates the program by pressing CTRL-C, I need to restore the blinking cursor; a well-behaved `DigitalClock` must do that. Some kind of _signal handler_ is required, so that just before it exits, the application issues the complementary ANSI escape sequence that restores the cursor. This time, the standard library provides the handler facility, aptly named: `ctrlc`:

```rust
fn notify_me_when_user_exits() -> Result<Receiver<u8>, ctrlc::Error> {
    let (sender, receiver) =  bounded(8);
    ctrlc::set_handler(move || {
        print!("\x1b[?25h");  // Restore the hidden cursor!
        let _ = sender.send(0xFF);
    })?;

    Ok(receiver)
}
```
That little closure, named `ctrlc::set_handler`  does the job. It makes use of a facility that `crossbeam_channel::bounded(n)` provides. Upon trapping the CTRL-C, the closure issues an appropriate ANSI Escape sequence, and _sends_ a 0xFF (an arbitrary value, not used) in the _channel_.

#### Handling two non-deterministic asynchronous inputs

The main thread sets itself up for two inputs:

* A periodic tick: when it arrives, current time is picked up and the `DigitalClock` displays the digits 
* An intimation by the user to exit: when it arrives, the handler gets into action, keeps the house as it were and leaves.

Thus, a structure is required to respond to either of these two inputs which can arrive in an unforeseen order; as if, the application is *passively waiting* for either of two interrupts and then *reacting to* the one that arrives first.

While strolling through rust-cli ([handbook](https://rust-cli.github.io/book/in-depth/signals.html)), I have come across a nifty arrangement of `select!` macro. This macro sits perfectly at the _receiving end_ of a (bi-ended) channel and executes logic associated with the receipt of data through that channel. Structurally, it caters to multiple such receiving ends but responds to any one of them at a given point in time:, much like its homographic elder cousin named `select()` Unix system call: 

```rust
select! {
            recv(notification_on_next_second) -> _ => {
                let time = read_clock_now()      // Current HH:MM:SS as a string
                .chars()                         // An iterator of characters it contains
                .filter( | c  | c != &':')       // Scrape the ':' character from the middle
                .map(| c | c as u8 - '0' as u8)  // Get the numeric digit from ascii digit
                .collect();

                screen_clock
                .on_next_second(
                    hr_and_min_and_sec[0],
                    hr_and_min_and_sec[1],
                    hr_and_min_and_sec[2],
                    hr_and_min_and_sec[3],
                    hr_and_min_and_sec[4],
                    hr_and_min_and_sec[5]
                )
                .refresh();

                print!("\x1b[7A");
            }

            recv(notification_on_user_exiting) -> _ => {
                println!("Goodbye!");
                break;
            }
        }
```

The complete code is [here](https://github.com/nsengupta/rust-exploration-digital-clock).

There it is, a simple application that makes use of some Rust facilities, idioms and techniques that I have managed to learn so far. It is not optimized for memory and speed and certainly, the code can be improved at few places, I know. Yet, I would love to hear from Rustaceans if and where do they think,the design can be cleaner and Rust's features can be adopted better. Educate me.

---------------------------------------------------------
I am a software programmer with ~3 decades of experience, having learnt through many successes I have influenced or caused, and many failures and mistakes I have made. I [work](https://www.linkedin.com/in/nirmalyasengupta/) as a platform technologist with a consortium of consultants: [Swanspeed Consulting](https://www.linkedin.com/company/swanspeed-consulting/about/).