<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Trevor Lee</title>
    <description>The latest articles on Forem by Trevor Lee (@trevorwslee).</description>
    <link>https://forem.com/trevorwslee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1284024%2F5cf6b5a5-ddd9-4a1d-a519-9b0e37a3cba9.jpeg</url>
      <title>Forem: Trevor Lee</title>
      <link>https://forem.com/trevorwslee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/trevorwslee"/>
    <language>en</language>
    <item>
      <title>PyTorch Introductory Experiments</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Sat, 02 Aug 2025 09:34:10 +0000</pubDate>
      <link>https://forem.com/trevorwslee/pytorch-introductory-experiments-378h</link>
      <guid>https://forem.com/trevorwslee/pytorch-introductory-experiments-378h</guid>
      <description>&lt;h1&gt;
  
  
  PyTorch Introductory Experiments
&lt;/h1&gt;

&lt;p&gt;In this &lt;a href="https://github.com/trevorwslee/PyTorchIntroductoryExperiments" rel="noopener noreferrer"&gt;project&lt;/a&gt;, I will revisit some of my previous basic TensorFlow Deep Learning experiments / training exercises&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.instructables.com/Trying-Out-TensorFlow-Lite-Hello-World-Model-With-/" rel="noopener noreferrer"&gt;Trying Out TensorFlow Lite Hello World Model With ESP32 and DumbDisplay&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.instructables.com/Mnist-Dataset-From-Training-to-Running-With-ESP32-/" rel="noopener noreferrer"&gt;Mnist Dataset -- From Training to Running With ESP32 / ESP32S3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.instructables.com/Sliding-Puzzle-Next-Move-Suggesting-Simple-DL-Mode/" rel="noopener noreferrer"&gt;Sliding Puzzle 'Next Move' Suggesting Simple DL Model With ESP32 TensorFlow Lite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nevertheless this time, those experiments will be retried (reimplemented)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The experiments will be using &lt;strong&gt;&lt;em&gt;PyTorch&lt;/em&gt;&lt;/strong&gt; as the deep learning framework (and still be with "dense" layers only)&lt;/li&gt;
&lt;li&gt;They will &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; be targeted for microcontroller; but still be demonstrable, with the help of DumbDisplay using &lt;strong&gt;&lt;em&gt;regular Python&lt;/em&gt;&lt;/strong&gt; and PyTorch &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Installation with VSCode
&lt;/h1&gt;

&lt;p&gt;This project is developed with VSCode (with Python extension); here, I will assume VSCode development environment as well.&lt;/p&gt;

&lt;p&gt;To try along, please clone this project -- &lt;a href="https://github.com/trevorwslee/PyTorchIntroductoryExperiments" rel="noopener noreferrer"&gt;&lt;code&gt;PyTorchIntroductoryExperiments&lt;/code&gt;&lt;/a&gt; -- from GitHub to your local machine, and open the folder with VSCode.&lt;/p&gt;

&lt;p&gt;Create a virtual environment by selecting the command &lt;code&gt;Python: Select Interpreter&lt;/code&gt; from the command palette and choose to create a new virtual environment.&lt;/p&gt;

&lt;p&gt;In the process, you will be given an option to also install the required dependent packages specified in &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;br&gt;
In case you missed installing those dependent package, you still can install them, including &lt;a href="https://github.com/trevorwslee/MicroPython-DumbDisplay" rel="noopener noreferrer"&gt;MicroPython DumbDisplay Library&lt;/a&gt;, with an opened terminal (virtual environment activated) by running &lt;code&gt;pip&lt;/code&gt;&lt;br&gt;
like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have tested the Python code of the project to work with Python 3.12.8&lt;/p&gt;

&lt;p&gt;For older version of Python, when installing the packages, you might see error like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModuleNotFoundError: No module named 'setuptools.config.expand'; 'setuptools.config' is not a package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;try upgrade &lt;code&gt;pip&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pip install --upgrade pip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;again.&lt;/p&gt;

&lt;p&gt;When open the Jupyter Notebook, the needed components will be installed when necessary.&lt;/p&gt;

&lt;h1&gt;
  
  
  "Hello World" Deep Learning Model Training
&lt;/h1&gt;

&lt;p&gt;The "Hello World" of Deep Learning here refers to the training of a DL model for the &lt;code&gt;sine&lt;/code&gt; Mathematical function -- &lt;code&gt;train_sine.ipynb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I guess modeling the Mathematical function &lt;code&gt;sine&lt;/code&gt; is considered the "Hello World" of Deep Learning because&lt;br&gt;
1) The training data is very easy to generate.&lt;br&gt;
2) The architecture of the model can simply be just a few "dense" layers.&lt;br&gt;
3) When it comes to implementation, the Mathematical function &lt;code&gt;sine&lt;/code&gt; might not be as trival as it sounds, and therefore a good demonstration of the Deep Learning "magic".&lt;/p&gt;

&lt;p&gt;Moreover, just for fun, I extended the architecture of the &lt;code&gt;sine&lt;/code&gt; model to output &lt;code&gt;cosine&lt;/code&gt; values at the same time -- &lt;code&gt;train_sine_cosine.ipynb&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fsine_chart.png" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabz6h5da1kiw4d3wbwmm.png" width="707" height="555"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Highlights of &lt;code&gt;train_sine.ipynb&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The target of this "Hello World" model is the &lt;code&gt;sine&lt;/code&gt; Mathematic function for the input range from 0 to 2π (0° to 360°).&lt;/p&gt;

&lt;p&gt;Hence, to create the data for the training the model, can generate randomized data like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_values = np.random.uniform(low=0, high=2*math.pi, size=SAMPLES)
np.random.shuffle(x_values)
y_values = np.sin(x_values)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the default datatype for PyTorch is &lt;code&gt;float32&lt;/code&gt;, first convert the data to &lt;code&gt;float32&lt;/code&gt;, and reshape them as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_values = x_values.astype(np.float32).reshape(-1, 1)
y_values = y_values.astype(np.float32).reshape(-1, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x_values&lt;/code&gt; is a two-dimensional matrix, with a single column, as the input to the model is a single value (the input angle in radian)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;y_values&lt;/code&gt; is also a two-dimensional matrix, with a single column, as the output of the model is also a single value (the &lt;code&gt;sine&lt;/code&gt; value of the input angle)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, 70% of the data will be treated as "train" data set, and the rest is treated as "test" data set&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train_split = int(0.7 * SAMPLES)

x_train, x_test = np.split(x_values, [train_split])
y_train, y_test = np.split(y_values, [train_split])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, turn the above data into something suitable for the PyTorch DL framework.&lt;/p&gt;

&lt;p&gt;First define the dataset class to capture the input data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SineDataset(Dataset):
    def __init__(self, x_values, y_values):
        self.x_values = x_values
        self.y_values = y_values

    def __len__(self):
        return len(self.x_values)

    def __getitem__(self, idx):
        return (self.x_values[idx], self.y_values[idx])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;__len__&lt;/code&gt; -- the size of the data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__getitem__&lt;/code&gt; -- given an index of the data, returns the (input, label) pair&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;SineDataset&lt;/code&gt;, instantiate data loaders (&lt;code&gt;DataLoader&lt;/code&gt;) for the "train" and "test" data sets&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train_dataset = SineDataset(x_train, y_train)
test_dataset = SineDataset(x_test, y_test)

train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Here, &lt;code&gt;BATCH_SIZE&lt;/code&gt; is the size of each training batch for each training epoch.&lt;/li&gt;
&lt;li&gt;For the "train" data set, it is better to shuffle it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the core, define the model class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SineModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(1, 16),
            nn.ReLU(),
            nn.Linear(16, 16),
            nn.ReLU(),
            nn.Linear(16, 1)
        )

    def forward(self, x):
        return self.linear_relu_stack(x)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The model basically consist of:

&lt;ul&gt;
&lt;li&gt;an input "dense" layer -- a single input (the input angle in radian); 16 output, with &lt;code&gt;relu&lt;/code&gt; activation&lt;/li&gt;
&lt;li&gt;another "dense" layer -- 16 input; 16 output, with &lt;code&gt;relu&lt;/code&gt; activation&lt;/li&gt;
&lt;li&gt;an output "dense" layer -- 16 input; a single output (the &lt;code&gt;sine&lt;/code&gt; value of the input angle)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;During training / inference, the method &lt;code&gt;forward&lt;/code&gt; will be called. Here, it simply calls the stack of layers to do the forward pass

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x&lt;/code&gt; is the input -- from the dataset &lt;code&gt;SineDataset&lt;/code&gt; during training; from inference input during inference&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;It is equally important to define the training and testing functions, which involves&lt;br&gt;
the "loss function" and the "optimizer". Later on, the "loss function" and "optimizer" be selected from the standard ones that come with the PyTorch framework.&lt;/p&gt;

&lt;p&gt;For training:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def train(device, dataloader, model, loss_fn, optimizer) -&amp;gt; float:
    num_batches = len(dataloader)
    model.train()
    train_loss = 0
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # Compute prediction error
        pred = model(X)
        loss = loss_fn(pred, y)
        train_loss += loss.item()

        # Backpropagation
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

    train_loss /= num_batches
    return train_loss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The function &lt;code&gt;train&lt;/code&gt; will be called during training (as will be apparent in later traning loop)&lt;/li&gt;
&lt;li&gt;During training, "grad info" will be accumulated, as &lt;code&gt;model.train()&lt;/code&gt; is called.&lt;/li&gt;
&lt;li&gt;For each batch:

&lt;ul&gt;
&lt;li&gt;prediction is made with the model -- &lt;code&gt;pred = model(X)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;loss is calculated -- &lt;code&gt;loss = loss_fn(pred, y)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;back-propagation involves:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loss.backward()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;optimizer.step()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;optimizer.zero_grad()&lt;/code&gt; -- reset the "grad info", for the next batch&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test(device, dataloader, model, loss_fn) -&amp;gt; float:
    num_batches = len(dataloader)
    model.eval()
    test_loss = 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
    test_loss /= num_batches
    return test_loss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The function &lt;code&gt;test&lt;/code&gt; will be called during training as well, but for testing, the model will not accumulate &lt;em&gt;grad info&lt;/em&gt;, as &lt;code&gt;torch.no_grad()&lt;/code&gt; is called&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At last, the actual training loop can be implemented as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EPOCH_COUNT = 600

loss_fn = nn.MSELoss()
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.001)

for t in range(EPOCH_COUNT):
    train_loss = train(device, train_dataloader, model, loss_fn, optimizer)
    test_loss = test(device, test_dataloader, model, loss_fn)
    print(f"$ Epoch {t+1} / {EPOCH_COUNT} -- Train loss: {train_loss:.6f} / Test loss: {test_loss:.6f}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EPOCH_COUNT&lt;/code&gt; -- the number of epoch for the training&lt;/li&gt;
&lt;li&gt;The "loss function" selection is &lt;code&gt;MSELoss&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The "optimizer" selection is &lt;code&gt;RMSprop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In lock steps, &lt;code&gt;train()&lt;/code&gt; and &lt;code&gt;test()&lt;/code&gt; are called for each epoch iteration.

&lt;ul&gt;
&lt;li&gt;all batches of the "train" dataset are walked through in &lt;code&gt;train()&lt;/code&gt;, with back-propagation to alter the model's parameters&lt;/li&gt;
&lt;li&gt;all batches of the "test" dataset are walked through just to find out the average of loss of the "test" dataset&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;To evaluate the trained model, can run the test data again like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_values = [math.pi * (5 * a) / 180.0 for a in range(0, 73)]
x_values = np.array(x_values).reshape(-1, 1)
x_values = torch.tensor(x_values, device=device, dtype=torch.float32)
model.eval()
pred_y_values = model(x_values).tolist()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59sn5mot5qa0omos6lb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59sn5mot5qa0omos6lb6.png" width="722" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Highlights of &lt;code&gt;train_sine_cosine.ipynb&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As previous "Hello World" model, this extended model (&lt;code&gt;sine&lt;/code&gt; + &lt;code&gt;cosine&lt;/code&gt;) will also have input range from 0 to 2π (0° to 360°).&lt;/p&gt;

&lt;p&gt;Hence, to create the data for training the model, need to randomize to get both -- &lt;code&gt;y_sin_values&lt;/code&gt; and &lt;code&gt;y_cos_values&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_values = np.random.uniform(low=0, high=2*math.pi, size=SAMPLES)
np.random.shuffle(x_values)
y_sin_values = np.sin(x_values)
y_cos_values = np.cos(x_values)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, since the default datatype for PyTorch is &lt;code&gt;float32&lt;/code&gt;, convert the data to &lt;code&gt;float32&lt;/code&gt;, and reshaped, as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_values = x_values.astype(np.float32).reshape(-1, 1)         
y_sin_values = y_sin_values.astype(np.float32).reshape(-1, 1) 
y_cos_values = y_cos_values.astype(np.float32).reshape(-1, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The input data &lt;code&gt;x_values&lt;/code&gt; is the same as the previous model -- a two-dimensional array with a single column&lt;/li&gt;
&lt;li&gt;Both of the output data &lt;code&gt;y_sin_values&lt;/code&gt; and &lt;code&gt;y_cos_values&lt;/code&gt; are also two-dimensional arrays with a single column -- the &lt;code&gt;sine&lt;/code&gt; and &lt;code&gt;cosine&lt;/code&gt; values respectively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As there are two sets of output values, it follows that there will also be two sets of values for the &lt;code&gt;train&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt; data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_train, x_test = np.split(x_values, [train_split])
y_sin_train, y_sin_test = np.split(y_sin_values, [train_split])
y_cos_train, y_cos_test = np.split(y_cos_values, [train_split])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the two sets of output data are stacked together for the model like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y_train = np.stack((y_sin_train, y_cos_train), axis=-1).reshape(-1, 2)
y_test = np.stack((y_sin_test, y_cos_test), axis=-1).reshape(-1, 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;y_train&lt;/code&gt; is two-dimensional with two columns -- 1st column is for &lt;code&gt;sine&lt;/code&gt;; 2nd column is for &lt;code&gt;cosine&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;hence, &lt;code&gt;y_train[0]&lt;/code&gt; are the &lt;code&gt;sine&lt;/code&gt; and &lt;code&gt;cosine&lt;/code&gt; values of the first input angle for the "train" data&lt;/li&gt;
&lt;li&gt;similarly, &lt;code&gt;y_test&lt;/code&gt; has the same shape as &lt;code&gt;y_train&lt;/code&gt;, but for the "test" data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, for the training, there are still two output values -- &lt;code&gt;train&lt;/code&gt; output values &lt;code&gt;y_train&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt; output values &lt;code&gt;y_test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To capture the data set, the following classes -- &lt;code&gt;SineCosineDataset&lt;/code&gt; and &lt;code&gt;SineCosineModel&lt;/code&gt; -- are defined&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SineCosineDataset(Dataset):
    def __init__(self, x_values, y_values):
        self.x_values = x_values
        self.y_values = y_values

    def __len__(self):
        return len(self.x_values)

    def __getitem__(self, idx):
        return self.x_values[idx], self.y_values[idx]

class SineCosineModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(1, 16),
            nn.ReLU(),
            nn.Linear(16, 16),
            nn.ReLU(),
            nn.Linear(16, 2)
        )

    def forward(self, x):
        return self.linear_relu_stack(x)        
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that there are 2 output values as the last layer of &lt;code&gt;SineCosineModel&lt;/code&gt; -- one for &lt;code&gt;sine&lt;/code&gt; and one for &lt;code&gt;cosine&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The rest of the code for training the model is basically the same as the &lt;code&gt;sine&lt;/code&gt; one.&lt;/p&gt;

&lt;p&gt;To evaluate the trained model, can run the test data again like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x_values = [math.pi * (5 * a) / 180.0 for a in range(0, 73)]
x_values = np.array(x_values).reshape(-1, 1)
x_values = torch.tensor(x_values, device=device, dtype=torch.float32)
model.eval()
pred_y_values = model(x_values).tolist()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3s0dqxhfjbq5w6yh13f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3s0dqxhfjbq5w6yh13f.png" width="704" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Mnist Dataset DL Training and Demo App
&lt;/h1&gt;

&lt;p&gt;The popular Mnist dataset is also frequently used as introductory demonstration to Deep Learning -- &lt;code&gt;train_mnist.ipynb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this project, not only will a simple DL training of Mnist dataset be presented, demonstration UI will be realized wireless on your Android mobile phone with the help of DumbDisplay -- &lt;code&gt;start_dd_mnist.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;UI is coded with (and driven by) Python using &lt;a href="https://github.com/trevorwslee/MicroPython-DumbDisplay" rel="noopener noreferrer"&gt;MicroPython DumbDisplay Library&lt;/a&gt; package; and is realized wirelessly on your Android mobile phone with &lt;a href="https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay" rel="noopener noreferrer"&gt;DumbDisplay Android App&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nowadays, code generation with AI is a common practice. Indeed, I did prompt LLM to generate a Jupyter Notebook to train a PyTorch DL model for the Mnist dataset -- &lt;code&gt;train_mnist_ai.ipynb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The AI-generated DL model is more complex (and certainly more standard) than the one presented in &lt;code&gt;train_mnist.ipynb&lt;/code&gt;. At least, mine basically only involves "dense" layers, while the AI-generated one involves "convolutional" layers.&lt;/p&gt;

&lt;p&gt;The actual model architecture (the simple version) is captured by the class &lt;code&gt;MnistModel&lt;/code&gt; defined in the file &lt;code&gt;model_mnist.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MnistModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(784, 64),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(64, 64),
            nn.ReLU(),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        logits = self.linear_relu_stack(x)
        return F.log_softmax(logits, dim=1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please refer to &lt;code&gt;train_mnist.ipynb&lt;/code&gt; for the complete training exercise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeoo2c03vj3iesw4clis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeoo2c03vj3iesw4clis.png" width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo UI for the Trained Mnist DL Model
&lt;/h2&gt;

&lt;p&gt;The Python script &lt;code&gt;start_dd_mnist.py&lt;/code&gt; starts the DumbDisplay program which uses &lt;a href="https://github.com/trevorwslee/MicroPython-DumbDisplay" rel="noopener noreferrer"&gt;MicroPython DumbDisplay Library&lt;/a&gt; to drive a wireless UI on your Android mobile phone with &lt;a href="https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay" rel="noopener noreferrer"&gt;DumbDisplay Android App&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After starting the Python script &lt;code&gt;start_dd_mnist.py&lt;/code&gt;, you should see from VSCode terminal that it waits for connection from DumbDisplay Android App.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**********
*****
*** reading model output/mnist_model.pth ...
*****
**********



**********
*****
*** starting mnist with model output/mnist_model.pth ...
*****
**********

connecting socket ... listing on 192.168.0.46:10201 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can open the DumbDisplay Android App installed in your Android phone -- &lt;a href="https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay" rel="noopener noreferrer"&gt;DumbDisplay Android App&lt;/a&gt; -- and make connection like&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fdd_connect_wifi_00.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fdd_connect_wifi_01.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fdd_connect_wifi_02.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh6r9l2oniw71scpb3tq.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When connected, the VSCode should show something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;... connected 192.168.0.46:10201 from ('192.168.0.98', 41578)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Once connected, on the [black] canvas of the UI, draw a dight you want the DL model to recognize, like the digit &lt;code&gt;8&lt;/code&gt;, and press the &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; button to trigger inference of the drawn digit data (stored in the memory of the running Python process)&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hlsbf5n0ud4k1z4tnk3.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The inference with PyTorch is actually performed by the following Python function &lt;code&gt;mnist_inference&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def mnist_inference(inference_data, model) -&amp;gt; int:
    try:
        x = np.array(inference_data).reshape((1, 784))
        torch_x = torch.tensor(x, dtype=torch.float32).reshape(1, 784)
        output = model(torch_x)
        pred = output.argmax()
        return pred.item()
    except Exception as e:
        print(f"XXX error during inference: {e}")
        raise e
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;mnist_inference&lt;/code&gt; is just a callback function for the DumbDisplay "driver" Python code implemented as an &lt;a href="https://github.com/trevorwslee/MicroPython-DumbDisplay/blob/master/dumbdisplay_examples/mnist/mnist_app.py" rel="noopener noreferrer"&gt;example&lt;/a&gt; of MicroPython DumbDisplay Library&lt;/p&gt;

&lt;p&gt;Now, draw the digit &lt;code&gt;9&lt;/code&gt; on the canvas ...&lt;/p&gt;

&lt;p&gt;If you want to clear what have drawn, press the &lt;code&gt;clear&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;center&lt;/code&gt; button toggles whether the drawn digit will be auto centered before calling &lt;code&gt;mnist_inference&lt;/code&gt; for inference.&lt;/p&gt;

&lt;p&gt;It is interesting to see that even without auto-centering, the digit recognition is pretty good, especially with the AI generated model -- &lt;code&gt;start_dd_mnist_ai.py&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sliding Puzzle DL Training and Demo App
&lt;/h1&gt;

&lt;p&gt;The DL model presented in this project for the classical Sliding Puzzle game is a &lt;strong&gt;&lt;em&gt;simple and naive&lt;/em&gt;&lt;/strong&gt; "next move" suggesting model -- &lt;code&gt;train_sliding_puzzle.ipynb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I came up with this &lt;strong&gt;&lt;em&gt;simple and naive&lt;/em&gt;&lt;/strong&gt; "next move" suggesting model by referencing to the above-mentioned "Hello World" and Mnist DL models, &lt;strong&gt;&lt;em&gt;just for fun&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Say, for a 4x4 board.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There will be 16 tiles, with the 0th tile being the empty space; e.g. the board "orientation" can be represented as
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  |  0 |  1 |  2 |  3 |
  |  4 |  5 |  6 |  7 |
  |  8 |  9 | 10 | 11 |
  | 12 | 13 | 14 | 15 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With respect to the empty space (i.e. the 0th tile), there can be 4 possible moves (but some might be invalid)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;0&lt;/code&gt;: from left&lt;/li&gt;
&lt;li&gt; &lt;code&gt;1&lt;/code&gt;: from right&lt;/li&gt;
&lt;li&gt; &lt;code&gt;2&lt;/code&gt;: from top&lt;/li&gt;
&lt;li&gt; &lt;code&gt;3&lt;/code&gt;: from bottom&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;For example, the above solved board can be randomized by a single step of the move &lt;code&gt;1&lt;/code&gt; (from right) to&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  |  1 |  0 |  2 |  3 |
  |  4 |  5 |  6 |  7 |
  |  8 |  9 | 10 | 11 |
  | 12 | 13 | 14 | 15 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The "next move" of this randomized board is apparently an "undo" move to undo the randomization step, in this case, the move &lt;code&gt;0&lt;/code&gt; (from left) &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; =&amp;gt; undo with &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; =&amp;gt; undo with &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt; =&amp;gt; undo with &lt;code&gt;3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3&lt;/code&gt; =&amp;gt; undo with &lt;code&gt;2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In other words, the "undo" moves are the "next moves" toward solving a randomized board&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;The way to capture the "undo" moves can be as easy as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;during a randomization step, a valid "move" is select&lt;/li&gt;
&lt;li&gt;the "undo" move is recorded as the "next move" for the randomized board "orientation"&lt;/li&gt;
&lt;li&gt;so, for a board randomized by 5 steps, there will be 5 "next moves" recorded -- one for each board "orientation"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The board "orientations" is the input to the DL model. The "undo" moves is the output of the DL model.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The actual model architecture is captured by the class &lt;code&gt;SlidingPuzzleModel&lt;/code&gt; defined in the file &lt;code&gt;model_sliding_puzzle.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SlidingPuzzleModel(nn.Module):
    def __init__(self, tile_count):
        super().__init__()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(tile_count * tile_count, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, 256),
            nn.ReLU(),
            nn.Linear(256, 4)
        )

    def forward(self, x):
        logits = self.linear_relu_stack(x)
        return F.softmax(logits, dim=1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please refer to &lt;code&gt;train_sliding_puzzle.ipynb&lt;/code&gt; for the complete training exercise, which is a bit more involving, mostly because generation of the training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo UI for the Trained Sliding Puzzle Model
&lt;/h2&gt;

&lt;p&gt;The demo UI Python script this time is &lt;code&gt;start_dd_sliding_puzzle.py&lt;/code&gt;, which is mostly based on the &lt;a href="https://github.com/trevorwslee/MicroPython-DumbDisplay/blob/master/dumbdisplay_examples/sliding_puzzle/sliding_puzzle_app.py" rel="noopener noreferrer"&gt;example&lt;/a&gt; of MicroPython DumbDisplay Library&lt;/p&gt;

&lt;p&gt;After starting the Python script &lt;code&gt;start_dd_sliding_puzzle.py&lt;/code&gt;, you should see from VSCode terminal that it waits for connections from the DumbDisplay Android App.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**********
*****
*** reading model output/sp_model_4.pth ...
*****
**********



**********
*****
*** starting sliding puzzle with model output/sp_model_4.pth ...
*****
**********

connecting socket ... listing on 192.168.0.46:10201 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can open the DumbDisplay Android App and make connection like previously.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Once connect, double-press the sliding puzzle game board to randomize it by 5 steps. You can try to solve the puzzle manually by moving / sliding the appropriate tile to the empty space. If stuck, press the &lt;code&gt;suggest&lt;/code&gt; button to have the DL model suggest the "next move" for you.&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lf8361fadutm1w2r6la.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When "next move" is needed, the following Python function &lt;code&gt;suggest_next_move&lt;/code&gt; will be called&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def suggest_next_move(board_manager: BoardManager, model, tile_count) -&amp;gt; int:
    x = torch.tensor([board_manager.board_tiles], dtype=torch.float32) / (tile_count * tile_count)
    prediction = model(x)
    move_ans = prediction.argmax(dim=1).item()
    return move_ans
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Messed up or not, double-press the &lt;code&gt;🔄 Reset 🔄&lt;/code&gt; button to reset the game; but this time, press the &lt;code&gt;continuous&lt;/code&gt; button to have "next move" suggested [and made] continuously.&lt;/p&gt;

&lt;p&gt;If things go well, the game should be solved in 5 suggested "next moves".&lt;/p&gt;

&lt;p&gt;Once solved, double-press the board to randomize it again; but this time, it will be randomized by 10 steps (5 more than last time).&lt;/p&gt;

&lt;p&gt;Once solved again, double-press the board again ...&lt;/p&gt;

&lt;p&gt;It is interesting to see how randomized the board is, the DL model can still suggest the correct "next moves" to solve it.&lt;br&gt;
My experience is, around 15 to 20 randomize steps.&lt;/p&gt;

&lt;p&gt;If you are interested, try tuning the model to see if it can achieve more randomize steps!&lt;/p&gt;

&lt;h1&gt;
  
  
  Two Simple DumbDisplay Samples
&lt;/h1&gt;

&lt;p&gt;In the Python script &lt;code&gt;test_run_dd.py&lt;/code&gt; are two simple DumbDisplay samples&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;run_blink&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;run_graphical&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fdd-blink.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fme0qbe8nmwnkm5l6azgq.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hopefully, these two samples can kick start those interested friends to implement Android apps using Python with DumbDisplay,&lt;br&gt;
for purposes like that of this project. &lt;/p&gt;

&lt;h1&gt;
  
  
  Have Fun!
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>deeplearning</category>
      <category>pytorch</category>
    </item>
    <item>
      <title>A Weather Clock (with Alarms) for ESP32 / Raspberry Pi Pico Implemented with Arduino Framework</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Sat, 10 May 2025 03:47:25 +0000</pubDate>
      <link>https://forem.com/trevorwslee/a-weather-clock-with-alarms-for-esp32-raspberry-pi-pico-implemented-with-arduino-framework-5db3</link>
      <guid>https://forem.com/trevorwslee/a-weather-clock-with-alarms-for-esp32-raspberry-pi-pico-implemented-with-arduino-framework-5db3</guid>
      <description>&lt;h1&gt;
  
  
  Arduino Weather Clock -- &lt;code&gt;AWeatherClock&lt;/code&gt; -- v1.1
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yoy0p0506g7mzz6g00a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yoy0p0506g7mzz6g00a.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/trevorwslee/AWeatherClock" rel="noopener noreferrer"&gt;little microcontroller project&lt;/a&gt; &lt;code&gt;AWeatherClock&lt;/code&gt; (Arduino Weather Clock) was inspired by &lt;a href="https://github.com/01studio-lab/pyClock" rel="noopener noreferrer"&gt;pyClock&lt;/a&gt;, and is implemented using the Arduino framework.&lt;/p&gt;

&lt;p&gt;VSCode with PlatformIO extension is the primarily development environment for the project, in the similar fashion as described by the post -- &lt;a href="https://www.instructables.com/A-Way-to-Run-Arduino-Sketch-With-VSCode-PlatformIO/" rel="noopener noreferrer"&gt;A Way to Run Arduino Sketch With VSCode PlatformIO Directly&lt;/a&gt; &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Naturally, the hardware used for the initial development of the project was exactly the one mention in the &lt;code&gt;pyClock&lt;/code&gt; GitHub repository.&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl5kjhzm8ttf0cg29sks.jpg" width="800" height="963"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nevertheless, the sketch of &lt;code&gt;AWeatherClock&lt;/code&gt; should be adaptable to other hardware configurations, with a big enough colored TFT LCD screen.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fsparkbot_main_00.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fpyclock_ip_00.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fast_watch_alarm_00.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fast_watch_alarm_01.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvzq3z97wj2g1gu1ki85.jpg" width="800" height="486"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The functions of &lt;code&gt;AWeatherClock&lt;/code&gt; are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display of a digital clock synchronized with NTP&lt;/li&gt;
&lt;li&gt;Display of the current weather info gathered from &lt;a href="https://home.openweathermap.org/" rel="noopener noreferrer"&gt;OpenWeather&lt;/a&gt; with &lt;strong&gt;&lt;em&gt;version 2.5&lt;/em&gt;&lt;/strong&gt; APIs [for &lt;a href="https://openweathermap.org/full-price#onecall" rel="noopener noreferrer"&gt;free account&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;Alarms that can be repeated daily, with selectable days [of the week] for the repeat. Note that if hardware permits, alarm sound (beep / melody / music) can be produced when alarm is due.&lt;/li&gt;
&lt;li&gt;Idle slideshow of photos (JPEG images) uploaded to the MCU, much like the slideshow function as described by  &lt;a href="https://www.instructables.com/Simple-Arduino-Framework-Raspberry-Pi-Pico-ESP32-T/" rel="noopener noreferrer"&gt;Simple Arduino Framework Raspberry Pi Pico / ESP32 TFT LCD Photo Frame Implementation With Photos Downloaded From the Internet Via DumbDisplay&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;AWeatherClock&lt;/code&gt; requires hardware with the following capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESP32 line of MCU that supports WiFi, or Raspberry Pi PicoW&lt;/li&gt;
&lt;li&gt;240x240 (or bigger) colored TFT LCD screen&lt;/li&gt;
&lt;li&gt;Able to provide with simple "trigger" input, like with

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BUTTON&lt;/code&gt; -- note that the "boot" button for ESP32 can be used as &lt;code&gt;BUTTON&lt;/code&gt; here&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TOUCH SCREEN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Optionally -- buzzer, speaker or audio module (like ES8311 for ESP32) -- for sounding of alarms.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Since &lt;code&gt;AWeatherClock&lt;/code&gt; needs to gather current weather info from &lt;a href="https://home.openweathermap.org/users/sign_up" rel="noopener noreferrer"&gt;OpenWeather&lt;/a&gt;,&lt;br&gt;
please, &lt;strong&gt;&lt;em&gt;sign up&lt;/em&gt;&lt;/strong&gt; for an &lt;code&gt;APP_ID&lt;/code&gt; of their &lt;strong&gt;&lt;em&gt;version 2.5&lt;/em&gt;&lt;/strong&gt; APIs. &lt;br&gt;
You will need this &lt;code&gt;APP_ID&lt;/code&gt; in order to setup for &lt;code&gt;AWeatherClock&lt;/code&gt; -- please refer to the section Basic Hardcoded Configurations&lt;/p&gt;

&lt;p&gt;Also, &lt;a href="https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay" rel="noopener noreferrer"&gt;DumbDisplay Android app&lt;/a&gt;&lt;br&gt;
for Android phone (or Chrome OS / Android simulator etc) is an essential part of &lt;code&gt;AWeatherClock&lt;/code&gt; since most settings -- including alarm setups and slideshow photos upload -- can be done with remote UI realized on your Android phone with the help of DumbDisplay Android app&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Photos to upload can be acquired in the following ways:

&lt;ul&gt;
&lt;li&gt;randomly from the Internet&lt;/li&gt;
&lt;li&gt;&lt;a href="https://picsum.photos/" rel="noopener noreferrer"&gt;picsum.photos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://loremflickr.com/#google_vignette" rel="noopener noreferrer"&gt;loremflickr.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://placedog.net/" rel="noopener noreferrer"&gt;placedog.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;randomly from &lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;unsplash.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;for downloading photos from &lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;unsplash.com&lt;/a&gt;, you will need to &lt;a href="https://unsplash.com/developers" rel="noopener noreferrer"&gt;sign up&lt;/a&gt; and create a demo app for an &lt;code&gt;Access Key&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;pick from your phone&lt;/li&gt;
&lt;li&gt;take with your phone's camera
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Location for weather info

&lt;ul&gt;
&lt;li&gt;according to hardcode query string (&lt;code&gt;DEF_OPEN_WEATHER_API_LOCATION&lt;/code&gt; defined in &lt;code&gt;config.h&lt;/code&gt;), or&lt;/li&gt;
&lt;li&gt;based on GPS location of your phone queried via DumbDisplay Android app
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NTP timezone

&lt;ul&gt;
&lt;li&gt;the initial timezone of your MCU is hardcoded to the macro &lt;code&gt;INIT_TIMEZONE&lt;/code&gt; defined in &lt;code&gt;config.h&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;after weather info gathering, timezone of your MCU is set to the timezone returned with the weather info&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When connected to DumbDisplay Android app, &lt;code&gt;AWeatherClock&lt;/code&gt; is considered not idle, and hence slideshow will not start
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that the remote UI on your Android phone is driven by the sketch -- i.e. the control flow of the UI is programmed in the sketch -- hence, other than the DumbDisplay Android app, there is no specific mobile app for &lt;code&gt;AWeatherClock&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You may want to refer to &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt;&lt;br&gt;
for a bit more details about  &lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;DumbDisplay Arduino library&lt;/a&gt; and &lt;a href="https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay" rel="noopener noreferrer"&gt;DumbDisplay Android app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Concerning "sounding" of alarm. Actually &lt;code&gt;AWeatherClock&lt;/code&gt; not only will flash the screen when alarm is due,&lt;br&gt;
if buzzer / speaker / audio module (ES8311 for ESP32) is installed, audible alarm-specific sound will be generated.&lt;br&gt;
Please refer to Alarm Sound for more details.&lt;/p&gt;
&lt;h1&gt;
  
  
  Out-of-the-Box Supported Hardware
&lt;/h1&gt;

&lt;p&gt;The sketch &lt;code&gt;arduino_weather_clock.ino&lt;/code&gt; of this project is tailored for various compatible hardware that I can get hold of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;PYCLOCK&lt;/code&gt; -- ESP32 C3 &lt;a href="https://github.com/01studio-lab/pyClock" rel="noopener noreferrer"&gt;pyClock&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;it's button on the back is configured as a &lt;code&gt;BUTTON&lt;/code&gt; for "trigger" input&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;AST_WATCH&lt;/code&gt; -- ESP32 C3 &lt;a href="https://spotpear.cn/wiki/ESP32-C3-Ornament-Trinket-LVGL-Astronaut-Clock-Watch-MINI-TV-1.69inch-Round-LCD-TouchScreen-ST7789-240x280.html" rel="noopener noreferrer"&gt;Astronaut-Clock-Watch (touch)&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;it's touch screen is configured as a &lt;code&gt;TOUCH SCREEN&lt;/code&gt; for "trigger" input&lt;/li&gt;
&lt;li&gt;it has a buzzer, which is used to generate alarm sound &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;TW3&lt;/code&gt; -- ESP32 &lt;a href="https://github.com/Xinyuan-LilyGO/TTGO_TWatch_Library" rel="noopener noreferrer"&gt;LILYGO_WATCH_2020_V3&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;it's touch screen is configured as a &lt;code&gt;TOUCH SCREEN&lt;/code&gt; for "trigger" input &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;ESP_SPARKBOT&lt;/code&gt; -- ESP32 S3 &lt;a href="https://oshwlab.com/hawaii0707/esp-sparkbot" rel="noopener noreferrer"&gt;ESP-SparkBot&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;it's right side is touchable, which is configured as a &lt;code&gt;BUTTON&lt;/code&gt; for "trigger" input, but from my experience, this "touch-pin" mechanism doesn't work very well 😞&lt;/li&gt;
&lt;li&gt;it also has the &lt;code&gt;ES8311&lt;/code&gt; audio codec module, which is used to generate alarm sound&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;MUMA&lt;/code&gt; (touch) / &lt;code&gt;MUMA_NT&lt;/code&gt; (non-touch) -- ESP32 S3 &lt;a href="https://spotpear.cn/shop/ESP32-S3-AI-1.54-inch-LCD-Display-TouchScreen-N16R8-muma-DeepSeek.html" rel="noopener noreferrer"&gt;Little MuMa&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;for touch version (&lt;code&gt;MUMA&lt;/code&gt;), it's touch screen is configured as a &lt;code&gt;TOUCH SCREEN&lt;/code&gt; for "trigger" input&lt;/li&gt;
&lt;li&gt;for non-touch version (&lt;code&gt;MUMA_NT&lt;/code&gt;), it's "boot" button on the back right side is configured as a &lt;code&gt;BUTTON&lt;/code&gt; for "trigger" input&lt;/li&gt;
&lt;li&gt;it also has the &lt;code&gt;ES8311&lt;/code&gt; audio codec module, which is used to generate alarm sound&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Platformio env &lt;code&gt;PICOW_GP&lt;/code&gt; -- a Raspberry Pi PicoW attached to a &lt;a href="https://spotpear.cn/wiki/Raspberry-Pi-Pico-RP2040-1.54inch-LCD-display-Screen-Game-ST7789.html" rel="noopener noreferrer"&gt;gamepad-like LCD&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;the gamepad's "start" button is configured as a &lt;code&gt;BUTTON&lt;/code&gt; for "trigger" input&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;PICOW&lt;/code&gt; -- the Raspberry Pi PicoW wiring as mentioned in &lt;a href="https://www.instructables.com/Simple-Arduino-Framework-Raspberry-Pi-Pico-ESP32-T/" rel="noopener noreferrer"&gt;Simple Arduino Framework Raspberry Pi Pico / ESP32 TFT LCD Photo Frame Implementation With Photos Downloaded From the Internet Via DumbDisplay&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;a ST7789 2.8 inch 240x320 SPI TFT LCD module is used, with a FT6336U capacitive touch layer&lt;/li&gt;
&lt;li&gt;it's touch screen is configured as a &lt;code&gt;TOUCH SCREEN&lt;/code&gt; for "trigger" input &lt;/li&gt;
&lt;li&gt;a speaker is attached to it; the speaker is used to generate alarm sound &lt;/li&gt;
&lt;li&gt;for more details, please refer to the later section Customizations for New Hardware PicoW Example
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PlatformIO env &lt;code&gt;ESP32&lt;/code&gt; -- an ESP32 customized hardware 

&lt;ul&gt;
&lt;li&gt;a ST7789V 1.3 inch 240x240 SPI TFT LCD module (ST7789) is used&lt;/li&gt;
&lt;li&gt;a button is attached to it, as &lt;code&gt;BUTTON&lt;/code&gt; for "trigger" input &lt;/li&gt;
&lt;li&gt;a speaker is attached to it; the speaker is used to generate alarm sound &lt;/li&gt;
&lt;li&gt;for more details, please refer to the later section Customizations for New Hardware ESP32 Example
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How many slides can be stored in the MCU?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E.g., for &lt;code&gt;PYCLOCK&lt;/code&gt; which is an ESP32-C3 with 4M of flash memory, I can store 25 to 30 photos to the flash of the MCU; note that for &lt;code&gt;PYCLOCK&lt;/code&gt;, it is configured to use &lt;code&gt;no_ota.csv&lt;/code&gt; partitions in &lt;code&gt;platformio.ini&lt;/code&gt; like
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  board_build.partitions = no_ota.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;E.g., for Raspberry Pi Pico, I can also store 25 to 30 photos to the flash of the MCU as well; note that for Raspberry Pi Pico, &lt;code&gt;littlefs&lt;/code&gt; is configured in &lt;code&gt;platformio.ini&lt;/code&gt; like
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  board_build.filesystem = littlefs
  board_build.filesystem_size = 1m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Additional notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For TWatch (&lt;code&gt;TW3&lt;/code&gt;): If when compile you see some "include font" error, it might be that the project's folder path is too long.
In such a case, try move the project to somewhere closer to the "root" of your filesystem &lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Showing of IP and Connecting DumbDisplay Android App
&lt;/h1&gt;

&lt;p&gt;With &lt;code&gt;BUTTON&lt;/code&gt; or &lt;code&gt;TOUCH SCREEN&lt;/code&gt;, you can trigger &lt;code&gt;AWeatherClock&lt;/code&gt; to show the IP of your MCU to connect to with Android DumbDisplay app&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Be the "trigger" mechanism &lt;code&gt;BUTTON&lt;/code&gt; or &lt;code&gt;TOUCH SCREEN&lt;/code&gt;, you "double click" to trigger showing of IP&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8atamui17kk14kwky8y.jpg" width="800" height="669"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You will need this IP to make connection between &lt;code&gt;AWeatherClock&lt;/code&gt; and your Android phone's Arduino DumbDisplay app for the remote UI, as will be described in more details next&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xiyre2u0bo67s00sd1g.gif" width="864" height="1920"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BTW. You might see that DumbDisplay Android app requires "LOCATION" permission to make use of your phone's GPS service. You can grant the permission with the &lt;code&gt;Settings&lt;/code&gt; menu option of DumbDisplay Android app&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fdd-permission-00.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3lh7ed2a8wdh9vtv2jq.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h1&gt;
  
  
  Settings UI
&lt;/h1&gt;

&lt;p&gt;As mentioned previously, most of the &lt;code&gt;AWeatherClock&lt;/code&gt; settings can be modified with the UI remotely rendered on your Android phone with DumbDisplay Android app&lt;/p&gt;
&lt;h1&gt;
  
  
  Settings UI -- General
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With the &lt;code&gt;General&lt;/code&gt; tab, you can modify the general settings / options&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjf50sukab4djodyeas5.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;🌤️&lt;/code&gt; -- you click the &lt;code&gt;🌤️&lt;/code&gt; button to [manually] trigger refresh of the current weather info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12 Hour&lt;/code&gt; / &lt;code&gt;24 Hour&lt;/code&gt; -- you select whether the digital clock display should be in 12-hour or 24-hour format&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;📡&lt;/code&gt; -- you select whether to sync weather location with the GPS location of your phone&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Slide Show Idle&lt;/code&gt; -- you select the idle time (in minutes) before starting slideshow; to disable idle slideshow, select &lt;code&gt;🚫&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Slide Duration&lt;/code&gt; -- you select the duration (in seconds) each slide should be kept shown, before switching to another one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Update Weather&lt;/code&gt; -- you select the gap (in minutes) between each auto update of the current weather info&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Settings UI -- Alarms
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With the &lt;code&gt;Alarms&lt;/code&gt; tab, you can set up the alarms of &lt;code&gt;AWeatherClock&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1obgs5j7wm6wf5h1vc0.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;🕰️&lt;/code&gt; icon next to an alarm indicates that the alarm is ON; below that icon, the time of the alarm is shown, like &lt;code&gt;00:00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You can select any one of the alarms to edit. The details of the alarm being edited are shown on the right-side

&lt;ul&gt;
&lt;li&gt;you can turn the alarm ON / OFF by selecting the &lt;code&gt;⏰&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;you select the &lt;code&gt;🔄&lt;/code&gt; button to set that the alarm is to be repeated daily; and the week days for the repeat are indicated by the
&lt;code&gt;Su&lt;/code&gt; / &lt;code&gt;Mo&lt;/code&gt; / &lt;code&gt;Tu&lt;/code&gt; / &lt;code&gt;We&lt;/code&gt; / &lt;code&gt;Th&lt;/code&gt; / &lt;code&gt;Fr&lt;/code&gt; / &lt;code&gt;Sa&lt;/code&gt; below the &lt;code&gt;🔄&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;The time of the alarm is clearly shown further down below. You can press the &lt;code&gt;🔼&lt;/code&gt; / &lt;code&gt;🔽&lt;/code&gt; to have the alarm hour / minute changed. Alternatively, you can double-press 
on the hour / minute to have a pop-up dialog for you to enter the hour / minute of the alarm.
Note that if you enter a value bigger than &lt;code&gt;99&lt;/code&gt;, like 1230, it will be interpreted as the time &lt;code&gt;12:30&lt;/code&gt;;
if you want to enter the time, say, &lt;code&gt;00:01&lt;/code&gt;, enter &lt;code&gt;2401&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Settings UI -- Slides
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Ftab_slides_00.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Ftab_slides_01.jpg" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuewk3v9h6c4cbv73ueu6.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the &lt;code&gt;Slides&lt;/code&gt; tab, you can add / remove photos for the idle slideshow&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;⬅️&lt;/code&gt; / &lt;code&gt;➡️&lt;/code&gt; -- you use the &lt;code&gt;⬅️&lt;/code&gt; / &lt;code&gt;➡️&lt;/code&gt; buttons to review the slideshow photos

&lt;ul&gt;
&lt;li&gt;you select the photo to be deleted from the slideshow&lt;/li&gt;
&lt;li&gt;newly uploaded photo can be saved to the slideshow, positioned after the photo selected&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;💾&lt;/code&gt; / &lt;code&gt;🗑️&lt;/code&gt; -- you delete the photo shown by double-pressing &lt;code&gt;🗑️&lt;/code&gt;; the &lt;code&gt;💾&lt;/code&gt; is for you to add the uploaded photo to the slideshow&lt;/li&gt;
&lt;li&gt;Acquire photo to upload:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;🌍&lt;/code&gt; / &lt;code&gt;💦&lt;/code&gt; -- you trigger download of a random photo from the Internet by pressing &lt;code&gt;🌍&lt;/code&gt;; &lt;code&gt;💦&lt;/code&gt; is specifically for downloading a random photo from Unsplash
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;📱&lt;/code&gt; / &lt;code&gt;📷&lt;/code&gt; -- you pick a photo from your phone by pressing &lt;code&gt;📱&lt;/code&gt;; you take a photo with your phone's camera by pressing &lt;code&gt;📷&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In any case, the uploaded photo will be considered "unsaved"; you press &lt;code&gt;💾&lt;/code&gt; to add the photo to the slideshow&lt;/li&gt;
&lt;li&gt;Also notice for photo that might not fit &lt;code&gt;AWeatherClock&lt;/code&gt; screen, a &lt;a href="https://github.com/Yalantis/uCrop" rel="noopener noreferrer"&gt;crop UI&lt;/a&gt; will be invoked for you to crop the photo in order to fit the screen &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Alarm Sound
&lt;/h1&gt;

&lt;p&gt;As mentioned previously, in addition to flashing of the screen, in case of audible alarm sound can be generated with buzzer / speaker / audio module (ES8311 for ESP32), alarm-specific selection will be available&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Beep&lt;/code&gt; sound -- with buzzer / speaker / audio module &lt;/li&gt;
&lt;li&gt;&lt;p&gt;Melody &lt;code&gt;Amazing Grace&lt;/code&gt; (for ESP32) -- the same melody encoding as mentioned in the YouTube video &lt;a href="https://www.youtube.com/watch?v=l-HrsJXIwBY" rel="noopener noreferrer"&gt;Raspberry Pi Pico playing song melody tones, with DumbDisplay control and keyboard input&lt;/a&gt;&lt;br&gt;
and in the post &lt;a href="https://www.instructables.com/Respberry-Pi-Pico-W-Generating-Tones-With-Programm/" rel="noopener noreferrer"&gt;Respberry Pi Pico W Generating Tones With Programmable I/O (PIO) Using MicroPython&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Melody &lt;code&gt;Birthday Song&lt;/code&gt; (for ESP32) -- similar to above melody &lt;code&gt;Amazing Grace&lt;/code&gt;, but musical note encoding was generated with LLM&lt;br&gt;
&lt;code&gt;happy_birthday_melody.h&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  I asked LLM to generate this file's "happy birthday" melody.

  Prompt:

  At the end are the data structures for the melody "amazing grace". Can you figure out the data for the melody "happy birthday"?

  IMPORTANT notes:
  * Each musical note MUST be composed of exactly two characters, no more and no less.
  * Hence, the data "nodenames" / "octaves" / "beats" for each musical note MUST be composed with two characters, and therefore might be padded with space " "
  * For "nodenames" -- e.g "C " for C; "C#" for C sharp; and "Cb" for C flat
  * For "octaves" -- e.g. "0 " for octave 0; "1 " for octave 1; "2 " for octave 2; note that it can be negative like "-1" (still TWO chars), for lower octaves

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

  const char* amazing_grace_nodenames = "G C E C E D C A G G C E C E D G E G E G E C G A C C A G G C E C E D C ";
  const char* amazing_grace_octaves   = "0 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 0 0 0 1 1 1 1 1 1 ";
  const char* amazing_grace_beats     = "2 4 1 1 4 2 4 2 4 2 4 1 1 4 2 8 2 1 1 1 1 4 2 4 1 1 1 4 2 4 1 1 4 2 8 ";

  const int amazing_grace_beatSpeed = 300;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Music [sample] &lt;code&gt;Star Wars&lt;/code&gt; (for ESP32 with audio module ES8311) -- music played with &lt;a href="https://github.com/pschatzmann/arduino-audio-tools" rel="noopener noreferrer"&gt;Arduino Audio Tools&lt;/a&gt;, with data file &lt;code&gt;star_wars_music.h&lt;/code&gt;, which is actually the same data file used by the &lt;a href="https://github.com/pschatzmann/arduino-audio-tools/tree/main/examples/examples-stream/streams-memory_raw-i2s" rel="noopener noreferrer"&gt;&lt;code&gt;streams-memory_raw-i2s&lt;/code&gt; example&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that this per-alarm sound selection is only available for ESP32 line of MCU. Moreover, if your hardware has the audio module ES8311, you will have more&lt;br&gt;
|  |  |&lt;br&gt;
|--|--|&lt;br&gt;
|Again, with the &lt;code&gt;Alarms&lt;/code&gt; tab, you can select the per-alarm sound to alert you when the alarm is due|&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft98hfgrsnza5mhm4xa13.jpg" width="800" height="1777"&gt;|&lt;br&gt;
Notes:

&lt;ul&gt;
&lt;li&gt;The sound choice &lt;code&gt;Star Wars&lt;/code&gt; is only available for the audio module ES8311&lt;/li&gt;
&lt;li&gt;Additionally, slider next to &lt;code&gt;🔊&lt;/code&gt; allows you to change the ES8311 audio module volume&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Basic Hardcoded Configurations -- &lt;code&gt;config.h&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;config.h&lt;/code&gt; for secrets like &lt;code&gt;WIFI_SSID&lt;/code&gt;, &lt;code&gt;WIFI_PASSWORD&lt;/code&gt; and &lt;code&gt;OPEN_WEATHER_MAP_APP_ID&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
// #####
// # you will need to either
// # . enable and complete the following "secret" block
// # . or create and complete the "secret" block in the file `_secret.h`
// #####
#if false

  // ----------------------
  // !!! "secret" block !!!
  // ----------------------
  //
  // *****
  // * you can setup WIFI_SSID / WIFI_PASSWORD; for ESP32, if WIFI_SSID not defined, will use WiFiManager to get WiFi SSID and password
  // * you will need to setup OPEN_WEATHER_MAP_APP_ID
  // * you can optionally setup UNSPLASH_CLIENT_ID
  // *****

  #define WIFI_SSID               "&amp;lt;wifi ssid&amp;gt;"
  #define WIFI_PASSWORD           "&amp;lt;wifi password&amp;gt;"

  // you MUST get APP_ID from https://home.openweathermap.org/users/sign_up
  #define OPEN_WEATHER_MAP_APP_ID "&amp;lt;app id&amp;gt;"

  // optionally, sign up and create an app to get Access Key from https://unsplash.com/developers
  // comment out UNSPLASH_CLIENT_ID if you do not want to use unsplash.com
  #define UNSPLASH_CLIENT_ID      "&amp;lt;client id&amp;gt;"

#else
  #include "_secret.h"
#endif  
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For ESP32 line of MCU, it is not a must to define &lt;code&gt;WIFI_SSID&lt;/code&gt; / &lt;code&gt;WIFI_PASSWORD&lt;/code&gt;. In case not defined, &lt;a href="https://github.com/tzapu/WiFiManager" rel="noopener noreferrer"&gt;WiFiManager&lt;/a&gt; will be used to acquire WiFi credential. Say, you connect to the AP set up by WiFiManager running on your MCU, with AP name &lt;code&gt;AWClock&lt;/code&gt;, as defined by &lt;code&gt;AUTOCONNECT_AP_NAME&lt;/code&gt; in &lt;code&gt;config.h&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;However, you &lt;strong&gt;&lt;em&gt;MUST&lt;/em&gt;&lt;/strong&gt; set your own &lt;code&gt;OPEN_WEATHER_MAP_APP_ID&lt;/code&gt; for &lt;strong&gt;&lt;em&gt;version 2.5&lt;/em&gt;&lt;/strong&gt; APIs which you can apply for from &lt;a href="https://home.openweathermap.org/users/sign_up" rel="noopener noreferrer"&gt;OpenWeather&lt;/a&gt;,
say with a &lt;a href="https://openweathermap.org/full-price#onecall" rel="noopener noreferrer"&gt;free account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Optionally, you can sign up and create an app to get Access Key from &lt;a href="https://unsplash.com/developers" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;; with &lt;code&gt;UNSPLASH_CLIENT_ID&lt;/code&gt; defined,
your are ready to download photos from Unsplash and upload them to your MCU for slideshow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;config.h&lt;/code&gt; for other basic hardcoded configurations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
// TIMEZONE (in hours); note that NTP timezone will be gotten from weather api, hence, this is just the initial TIMEZONE
#define INIT_TIMEZONE                       8

// In order to properly setup the openweathermap.org the endpoint
// * please head to http://api.openweathermap.org to create an account and get an APP ID (for version 2.5) 
// * the country (location) for which to retrieve whether is defined with OPEN_WEATHER_API_LOCATION
//   - please refer to https://openweathermap.org/api/geocoding-api
//   - Please use ISO 3166 country codes -- https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
// * below DEF_SYNC_WEATHER_LOCATION_WITH_GPS ==&amp;gt; got location from GPS when connected to DD
#define DEF_OPEN_WEATHER_API_LOCATION       "Hong Kong"

#define DEF_SYNC_WEATHER_LOCATION_WITH_GPS  true /* got location from GPS when connected to DD */
#define DEF_SLIDE_SHOW_IDLE_DELAY_MINS      2    /* &amp;lt;= 0 means slide show not enabled */
#define DEF_SLIDE_DURATION_SECS             5
#define DEF_UPDATE_WEATHER_INTERVAL_MINS    30

#define NUM_ALARMS                          5
#define AUTO_ACK_ALARM_MINUTES              10
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;INIT_TIMEZONE&lt;/code&gt; -- the initial timezone; as mentioned previously; your MCU's timezone will eventually be synchronized with that returned from 'get current weather' API &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEF_OPEN_WEATHER_API_LOCATION&lt;/code&gt; -- the location (see &lt;a href="https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes" rel="noopener noreferrer"&gt;ISO 3166 country codes&lt;/a&gt;)
for the initial current weather info; note that when connected to DumbDisplay Android app, your phone's GPS location can be the
location for getting current weather info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEF_SYNC_WEATHER_LOCATION_WITH_GPS&lt;/code&gt; -- The default setting whether "get current weather" should be based on GPS location got from your phone,
(rather than based on &lt;code&gt;DEF_OPEN_WEATHER_API_LOCATION&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEF_SLIDE_SHOW_IDLE_DELAY_MINS&lt;/code&gt; -- The default setting for how many idle minutes to start slideshow
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEF_SLIDE_DURATION_SECS&lt;/code&gt; -- The default setting for how many seconds for each slideshow phone should stay displayed before switching to another one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEF_UPDATE_WEATHER_INTERVAL_MINS&lt;/code&gt; -- The default setting for the interval (minutes) between each update of current weather info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NUM_ALARMS&lt;/code&gt; -- The fixed number of alarms &lt;code&gt;AWeatherClock&lt;/code&gt; can set&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTO_ACK_ALARM_MINUTES&lt;/code&gt; -- The number of minutes before due alarm is automatically acknowledged (stopped)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  System Hardcoded Configurations -- &lt;code&gt;sys_config.h&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;The system hardcoded configuration file &lt;code&gt;sys_config.h&lt;/code&gt; not only contains most hardware pin mappings (as will be mentioned in the next section &lt;br&gt;
Highlight for Customization for New Hardware);&lt;br&gt;
specifically, &lt;code&gt;sys_config.h&lt;/code&gt; contains some values that will be useful during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
// comment out to DEBUG use of WIFI MANAGER for WiFi SSID / password
//#define TEST_WIFI_MANAGER

// comment out if you want the program to delay startup for 10 seconds for debugging (examine the serial monitor output)
//#define DELAY_INITIALIZE_FOR_SECONDS 10

// suggested to set the following EEPROM_HEADER to the date you want to reset the saved program settings *** INCLUDING saved slides ***
const int32_t EEPROM_HEADER = 20250505;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Customizations for New Hardware Highlights
&lt;/h1&gt;

&lt;p&gt;There are several areas to consider for customizing &lt;code&gt;AWeatherClock&lt;/code&gt; for new hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;platformio.ini&lt;/code&gt; for configuring PlatformIO for your MCU&lt;/li&gt;
&lt;li&gt;TFT LCD screen. The out-of-the-box configured TFT LCD screens are

&lt;ul&gt;
&lt;li&gt;ST7789 with &lt;a href="https://github.com/adafruit/Adafruit-ST7735-Library.git" rel="noopener noreferrer"&gt;Adafruit ST7735 Library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ILI9341 with &lt;a href="https://github.com/adafruit/Adafruit_ILI9341" rel="noopener noreferrer"&gt;Adafruit_ILI9341&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LCD Screen of TWatch (&lt;code&gt;TW3&lt;/code&gt;) -- &lt;a href="https://github.com/Xinyuan-LilyGO/TTGO_TWatch_Library" rel="noopener noreferrer"&gt;TTGO_TWatch_Library&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Button or touch screen. For touch screen, the out-of-the-box configured touch screens are

&lt;ul&gt;
&lt;li&gt;CST816T with &lt;a href="https://github.com/koendv/cst816t" rel="noopener noreferrer"&gt;cst816t&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FT6236 with &lt;a href="https://github.com/DustinWatts/FT6236" rel="noopener noreferrer"&gt;FT6236&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FT6336U with &lt;a href="https://github.com/aselectroworks/Arduino-FT6336U" rel="noopener noreferrer"&gt;Arduino FT6336U&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Buzzer or audio module. For audio module, the out-of-the-box configured audio module is

&lt;ul&gt;
&lt;li&gt;ES8311 with &lt;a href="https://github.com/pschatzmann/arduino-audio-driver" rel="noopener noreferrer"&gt;arduino audio driver&lt;/a&gt; and &lt;a href="https://github.com/pschatzmann/arduino-audio-tools" rel="noopener noreferrer"&gt;arduino audio tools&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For configuring PlatformIO for MCU. For example, for &lt;code&gt;PYCLOCK&lt;/code&gt;, which is has an ESP32C3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[env:PYCLOCK]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
board_build.partitions = no_ota.csv
lib_deps =
    https://github.com/adafruit/Adafruit-ST7735-Library.git
    https://github.com/adafruit/Adafruit-GFX-Library
    https://github.com/Bodmer/TJpg_Decoder.git
    https://github.com/bblanchon/ArduinoJson
    https://github.com/bitbank2/PNGdec#1.1.0
    https://github.com/tzapu/WiFiManager
    https://github.com/trevorwslee/Arduino-DumbDisplay
build_flags =
    -D ARDUINO_USB_MODE=1
    -D ARDUINO_USB_CDC_ON_BOOT=1
    -D FOR_PYCLOCK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/adafruit/Adafruit-ST7735-Library" rel="noopener noreferrer"&gt;&lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt;&lt;/a&gt; and
&lt;a href="https://github.com/adafruit/Adafruit-GFX-Library" rel="noopener noreferrer"&gt;&lt;code&gt;Adafruit-GFX-Library&lt;/code&gt;&lt;/a&gt; for the ST7789 2.8 inch 240x320 SPI TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Bodmer/TJpg_Decoder" rel="noopener noreferrer"&gt;&lt;code&gt;TJpg_Decoder&lt;/code&gt;&lt;/a&gt; for decoding JPEG data (slides)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bblanchon/ArduinoJson" rel="noopener noreferrer"&gt;&lt;code&gt;ArduinoJson&lt;/code&gt;&lt;/a&gt; for parsing the JSON got from OpenWeather&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bitbank2/PNGdec#1.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;PNGdec&lt;/code&gt;&lt;/a&gt; for decoding the weather condition icon (PNG) retrieved from OpenWether&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;&lt;code&gt;Arduino-DumbDisplay&lt;/code&gt;&lt;/a&gt; for driving DumbDisplay Android app for the UI remotely rendered with your Android phone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As for TFT LCD pin mappings (and others), they are mostly defined in &lt;code&gt;sys_config.h&lt;/code&gt;.&lt;br&gt;
For example, for &lt;code&gt;PYCLOCK&lt;/code&gt;, which has a button&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#if defined(FOR_PYCLOCK)
  #define TFT_CS          5
  #define TFT_DC          4
  #define TFT_SCLK        6
  #define TFT_MOSI        7
  #define TFT_RST         8
  #define TFT_X_OFF       0
  #define BUTTON_PIN      9
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TFT_xxx&lt;/code&gt; -- the pin mappings for the TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TFT_X_OFF&lt;/code&gt; -- the x offset to start the 240x240 area; note that the TFT screen can actually be wider than 240&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BUTTON_PIN&lt;/code&gt; -- the pin number of the button; assume it is &lt;code&gt;INPUT_PULLUP&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case of CST816T touch screen, like that for &lt;code&gt;AST_WATCH&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#elif defined(FOR_AST_WATCH)
  #define TFT_CS          3
  #define TFT_DC          2
  #define TFT_SCLK        5
  #define TFT_MOSI        6
  #define TFT_RST         8
  #define TFT_X_OFF       20
  #define CST_TP_BUS_NUM  0
  #define CST_TP_SCL      7
  #define CST_TP_SDA      11
  #define CST_TP_RST      10
  #define CST_TP_INT      9
  #define BUZZER_PIN      1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TFT_xxx&lt;/code&gt; -- the pin mappings for the TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TFT_X_OFF&lt;/code&gt; -- x offset is to 20, since the TFT LCD screen is 280x240&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CST_TP_xxx&lt;/code&gt; -- the pin mappings for the CST816T touch layer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BUZZER_PIN&lt;/code&gt; -- the pin number of the buzzer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nevertheless, especially for TFT LCD screen, simply define the pin mapping normally not good enough.&lt;br&gt;
Indeed, you will need other customizations to the code, like initialization of &lt;code&gt;Adafruit_ST7789&lt;/code&gt; object in &lt;code&gt;screen_helpers.cpp&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#if defined(FOR_PYCLOCK)
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  SPIClass spi(FSPI);
  Adafruit_ST7789 tft(&amp;amp;spi, TFT_CS, TFT_DC, TFT_RST);
#elif defined(FOR_AST_WATCH)
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  SPIClass spi(FSPI);
  Adafruit_ST7789 tft(&amp;amp;spi, TFT_CS, TFT_DC, TFT_RST);
#elif defined(FOR_PICOW_GP)
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  Adafruit_ST7789 tft(&amp;amp;SPI1, TFT_CS, TFT_DC, TFT_RST);
#elif defined(FOR_PICOW)
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  Adafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_RST);
...
...
void screenSetup() {
#if defined(TFT_BL)
  pinMode(TFT_BL, OUTPUT);
  #if defined(TFT_BL_LOW)
  digitalWrite(TFT_BL, 0);  // light it up (LOW)
  #else
  digitalWrite(TFT_BL, 1);  // light it up
  #endif
#endif

#if defined(FOR_PYCLOCK)
  spi.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
  tft.init(240, 240, SPI_MODE0);
  tft.invertDisplay(true);
  tft.setRotation(2);
  tft.setSPISpeed(40000000);
#elif defined(FOR_AST_WATCH)  
  spi.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
  tft.init(240, 280, SPI_MODE0);
  tft.setRotation(3);
#elif defined(FOR_PICOW_GP)  
  SPI1.setSCK(TFT_SCLK);
  SPI1.setMOSI(TFT_MOSI);
  tft.init(240, 240, SPI_MODE0);
  tft.setRotation(3);
#elif defined(FOR_PICOW)  
  tft.init(240, 320, SPI_MODE0);
  tft.setRotation(1);
  tft.setSPISpeed(40000000);
  ...
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, customizing for &lt;code&gt;BUTTON&lt;/code&gt; or &lt;code&gt;TOUCH SCREEN&lt;/code&gt; code modifications should be easier, like in &lt;code&gt;trigger_helpers.cpp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#if defined(CST_TP_BUS_NUM)
  #include &amp;lt;Wire.h&amp;gt;
  #include "cst816t.h"
  TwoWire tpWire(CST_TP_BUS_NUM);
  cst816t touchpad(tpWire, CST_TP_RST, CST_TP_INT);
#elif defined(FT6336_INT)
  #include &amp;lt;Wire.h&amp;gt;
  #include "FT6336U.h"
  #if defined(ESP32)
    FT6336U touchpad(FT6336_SDA, FT6336_SCL, FT6336_RST, FT6336_INT);
  #else
    FT6336U touchpad(FT6336_RST, FT6336_INT);
  #endif 
#elif defined(GT911_TP_SCL)  
  #include "TAMC_GT911.h"
  TAMC_GT911 touchpad = TAMC_GT911(GT911_TP_SDA, GT911_TP_SCL, GT911_TP_INT, GT911_TP_RST, GT911_TP_WIDTH, GT911_TP_HEIGHT);
...
void triggerSetup() {
#ifdef BUTTON_PIN
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // assume INPUT_PULLUP
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), _triggered, FALLING);
#elif defined(TOUCH_PIN)
  touchAttachInterrupt(TOUCH_PIN, _triggered, TOUCH_THRESHOLD);
#elif defined(CST_TP_BUS_NUM)
  tpWire.begin(CST_TP_SDA, CST_TP_SCL);
  touchpad.begin(mode_motion);
#elif defined(FT6336_INT)
  //pinMode(FT_TP_INT, INPUT_PULLUP);
  #if !defined(ESP32)
    Wire.setSDA(FT6336_SDA);  // FT6336U will use Wire
    Wire.setSCL(FT6336_SCL);
  #endif  
  touchpad.begin();
  attachInterrupt(digitalPinToInterrupt(FT6336_INT), _triggered, CHANGE);
   ...
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As hinted above with &lt;code&gt;AST_WATCH&lt;/code&gt;, to configure a buzzer / speaker is as easy as defining the pin assignment &lt;code&gt;BUZZER_PIN&lt;/code&gt;.&lt;br&gt;
However, there are much more pins for the ES8311 audio module, as in &lt;code&gt;ESP_SPARKBOT&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #define ES8311_PA             46 
  #define ES8311_I2C_SCL        5
  #define ES8311_I2C_SDA        4
  #define ES8311_I2S_PORT       35
  #define ES8311_I2S_MCLK       45
  #define ES8311_I2S_BCK        39  
  #define ES8311_I2S_WS         41
  #define ES8311_I2S_DOUT       42
  #define ES8311_I2S_DIN        40
  #define DEF_AUDIO_VOLUME         60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DEF_AUDIO_VOLUME&lt;/code&gt; sets the default volume of the audio module; set it if you want something different from the audio module default &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Customizations for New Hardware PicoW Example
&lt;/h1&gt;

&lt;p&gt;Take the above-mentioned &lt;code&gt;PICOW&lt;/code&gt; as an example -- the Raspberry Pi PicoW wiring as mentioned in &lt;a href="https://www.instructables.com/Simple-Arduino-Framework-Raspberry-Pi-Pico-ESP32-T/" rel="noopener noreferrer"&gt;Simple Arduino Framework Raspberry Pi Pico / ESP32 TFT LCD Photo Frame Implementation With Photos Downloaded From the Internet Via DumbDisplay&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A ST7789 2.8 inch 240x320 SPI TFT LCD module is used, with a FT6336U capacitive touch layer&lt;/li&gt;
&lt;li&gt;It's touch screen is configured as a &lt;code&gt;TOUCH SCREEN&lt;/code&gt; for "trigger" input &lt;/li&gt;
&lt;li&gt;A speaker is attached to it -- one end of the speaker is connected to &lt;code&gt;GND&lt;/code&gt; and the other end is connected to &lt;code&gt;GP15&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;|Raspberry Pi Pico|SPI TFT LCD |&lt;br&gt;
  |-----------------|------------|&lt;br&gt;
  | 3V3             | VCC        |&lt;br&gt;
  | GND             | GND        |&lt;br&gt;
  | GP21            | BL         |&lt;br&gt;
  | GP17            | CS         |&lt;br&gt;
  | GP16            | RS / DC    |&lt;br&gt;
  | GP18            | CLK / SCLK |&lt;br&gt;
  | GP19            | SDA / MOSI |&lt;br&gt;
  | GP20            | RST        |&lt;br&gt;
  | GP5             | TP_SCL     |&lt;br&gt;
  | GP4             | TP_SDA     |&lt;br&gt;
  | GP6             | TP_INT     |&lt;br&gt;
  | GP7             | TP_RST     |&lt;br&gt;
  | GP15            | SPEAKER    |&lt;br&gt;
  | GND             | SPEAKER    |&lt;/p&gt;

&lt;p&gt;&lt;code&gt;platformio.ini&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[env:PICOW]  ; ensure long file name support ... git config --system core.longpaths true
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = rpipicow
framework = arduino
board_build.core = earlephilhower
board_build.filesystem = littlefs
board_build.filesystem_size = 1m
lib_deps =
    https://github.com/adafruit/Adafruit-ST7735-Library.git#1.11.0
    https://github.com/adafruit/Adafruit-GFX-Library#1.12.1
    https://github.com/Bodmer/TJpg_Decoder.git#V1.1.0 
    https://github.com/aselectroworks/Arduino-FT6336U
    https://github.com/bblanchon/ArduinoJson#v7.4.1
    https://github.com/bitbank2/PNGdec#1.1.0
    https://github.com/trevorwslee/Arduino-DumbDisplay#v0.9.9-r51
build_flags =
    -D FOR_PICOW
...    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;it appears that if using &lt;code&gt;earlephilhower&lt;/code&gt; core in Windows, need to ensure long file name support ...&lt;br&gt;
&lt;br&gt;
&lt;code&gt;git config --system core.longpaths true&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;there are quite a few libraries needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/adafruit/Adafruit-ST7735-Library" rel="noopener noreferrer"&gt;&lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt;&lt;/a&gt; and
&lt;a href="https://github.com/adafruit/Adafruit-GFX-Library" rel="noopener noreferrer"&gt;&lt;code&gt;Adafruit-GFX-Library&lt;/code&gt;&lt;/a&gt; for the ST7789 2.8 inch 240x320 SPI TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Bodmer/TJpg_Decoder" rel="noopener noreferrer"&gt;&lt;code&gt;TJpg_Decoder&lt;/code&gt;&lt;/a&gt; for decoding JPEG data (slides)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aselectroworks/Arduino-FT6336U" rel="noopener noreferrer"&gt;&lt;code&gt;Arduino-FT6336U&lt;/code&gt;&lt;/a&gt; for the FT6336U touch layer &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bblanchon/ArduinoJson" rel="noopener noreferrer"&gt;&lt;code&gt;ArduinoJson&lt;/code&gt;&lt;/a&gt; for parsing the JSON got from OpenWeather&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bitbank2/PNGdec" rel="noopener noreferrer"&gt;&lt;code&gt;PNGdec&lt;/code&gt;&lt;/a&gt; for decoding the weather condition icon (PNG) retrieved from OpenWether&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;&lt;code&gt;Arduino-DumbDisplay&lt;/code&gt;&lt;/a&gt; for driving DumbDisplay Android app for the UI remotely rendered with your Android phone&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;sys_config.h&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#elif defined(FOR_PICOW)
  #define TFT_BL      21
  #define TFT_CS      17
  #define TFT_DC      16
  #define TFT_SCLK    18
  #define TFT_MOSI    19
  #define TFT_RST     20
  #define TFT_X_OFF   40
  #define TFT_UN_INVERTED
  #define FT_TP_SCL   5 
  #define FT_TP_SDA   4
  #define FT_TP_INT   6
  #define FT_TP_RST   7
  #define BUZZER_PIN  15
...  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TFT_xxx&lt;/code&gt; are the pin mappings of the ST7789 TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TFT_X_OFF&lt;/code&gt; is set to 40 since the screen is 320 wide and therefore need to offset the horizontal start by 40&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TFT_UN_INVERTED&lt;/code&gt; defines that the TFT LCD normally un-inverted; this macro tells the normal state of the TFT LCD when flashing the TFT LCD for due alarm&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FT_TP_xxx&lt;/code&gt; are the pin mappings of the FT6336U touch layer (normally part of the TFT LCD module)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BUZZER_PIN&lt;/code&gt; is the pin connected to one end of the speaker attached to the PicoW; the other end can connect to &lt;code&gt;GND&lt;/code&gt; of the PicoW&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;screen_helpers.cpp&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#elif defined(FOR_PICOW)
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  Adafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_RST);
...
void screenSetup() {
#if defined(TFT_BL)
  pinMode(TFT_BL, OUTPUT);
  #if defined(TFT_BL_LOW)
    digitalWrite(TFT_BL, 0);  // light it up (LOW)
  #else
    digitalWrite(TFT_BL, 1);  // light it up
  #endif
#endif
  ...
#elif defined(FOR_PICOW)  
  tft.init(240, 320, SPI_MODE0);
  tft.invertDisplay(false);
  tft.setRotation(1);
  tft.setSPISpeed(40000000);
  ...
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the first &lt;code&gt;defined(FOR_PICOW)&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;include the needed header file for the TFT LCD screen &lt;code&gt;&amp;lt;Adafruit_ST7789.h&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;instantiate the global variable &lt;code&gt;tft&lt;/code&gt; (as the TFT LCD screen object)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;the second &lt;code&gt;defined(FOR_PICOW)&lt;/code&gt;, which is inside the &lt;code&gt;void screenSetup()&lt;/code&gt; block

&lt;ul&gt;
&lt;li&gt;run the necessary code to setup the TFT LCD screen -- &lt;code&gt;tft&lt;/code&gt; declared previously&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Customizations for New Hardware ESP32 Example
&lt;/h1&gt;

&lt;p&gt;Take the above-mentioned &lt;code&gt;ESP32&lt;/code&gt; as another example&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A ST7789 1.3 inch 240x240 SPI TFT LCD module is used&lt;/li&gt;
&lt;li&gt;A button is attached to it, which act as a &lt;code&gt;BUTTON&lt;/code&gt; for "trigger" input -- one end of the button is connected to &lt;code&gt;GND&lt;/code&gt; and the other end is connected to &lt;code&gt;GIPO26&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;A speaker is attached to it -- one end of the speaker is connected to &lt;code&gt;GND&lt;/code&gt; and the other end is connected to &lt;code&gt;GIPO23&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;|ESP32              |SPI TFT LCD |&lt;br&gt;
  |-------------------|------------|&lt;br&gt;
  | 3V3               | VCC        |&lt;br&gt;
  | GND               | GND        |&lt;br&gt;
  | GPIO18            | BL         |&lt;br&gt;
  | GPIO15            | CS         |&lt;br&gt;
  | GPIO2             | DC         |&lt;br&gt;
  | GPIO14            | SCLK       |&lt;br&gt;
  | GPIO13            | MOSI       |&lt;br&gt;
  | GPIO4             | RST        |&lt;br&gt;
  | GPIO26            | BUTTON     |&lt;br&gt;
  | GND               | BUTTON     |&lt;br&gt;
  | GPIO23            | SPEAKER    |&lt;br&gt;
  | GND               | SPEAKER    |&lt;/p&gt;

&lt;p&gt;&lt;code&gt;platformio.ini&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[env:ESP32]
platform = espressif32
board = esp32dev
framework = arduino
;board_build.partitions = huge_app.csv
;monitor_filters = esp32_exception_decoder
lib_deps =
    https://github.com/adafruit/Adafruit-ST7735-Library.git#1.11.0
    https://github.com/adafruit/Adafruit-GFX-Library#1.12.1
    https://github.com/Bodmer/TJpg_Decoder.git#V1.1.0 
    https://github.com/bblanchon/ArduinoJson#v7.4.1
    https://github.com/bitbank2/PNGdec#1.1.0
    https://github.com/tzapu/WiFiManager#v2.0.17
    https://github.com/trevorwslee/Arduino-DumbDisplay#v0.9.9-r51
build_flags =
    -D FOR_ESP32
...    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;it appears that if using &lt;code&gt;earlephilhower&lt;/code&gt; core in Windows, need to ensure long file name support ...&lt;br&gt;
&lt;br&gt;
&lt;code&gt;git config --system core.longpaths true&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;there are quite a few libraries needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/adafruit/Adafruit-ST7735-Library" rel="noopener noreferrer"&gt;&lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt;&lt;/a&gt; and
&lt;a href="https://github.com/adafruit/Adafruit-GFX-Library" rel="noopener noreferrer"&gt;&lt;code&gt;Adafruit-GFX-Library&lt;/code&gt;&lt;/a&gt; for the ST7789 1.3 inch 240x240 SPI TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Bodmer/TJpg_Decoder" rel="noopener noreferrer"&gt;&lt;code&gt;TJpg_Decoder&lt;/code&gt;&lt;/a&gt; for decoding JPEG data (slides)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bblanchon/ArduinoJson" rel="noopener noreferrer"&gt;&lt;code&gt;ArduinoJson&lt;/code&gt;&lt;/a&gt; for parsing the JSON got from OpenWeather&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bitbank2/PNGdec" rel="noopener noreferrer"&gt;&lt;code&gt;PNGdec&lt;/code&gt;&lt;/a&gt; for decoding the weather condition icon (PNG) retrieved from OpenWether&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tzapu/WiFiManager" rel="noopener noreferrer"&gt;&lt;code&gt;WiFiManager&lt;/code&gt;&lt;/a&gt; for getting WiFi credential by setting up WiFi hotspot, when WiFi SSID / password not hard-coded&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;&lt;code&gt;Arduino-DumbDisplay&lt;/code&gt;&lt;/a&gt; for driving DumbDisplay Android app for the UI remotely rendered with your Android phone
&lt;code&gt;sys_config.h&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#elif defined(FOR_ESP32)
  #define TFT_BL      18     
  #define TFT_CS      15
  #define TFT_DC      2
  #define TFT_SCLK    14
  #define TFT_MOSI    13
  #define TFT_RST     4
  #define TFT_X_OFF   0
  #define BUTTON_PIN  26    
  #define BUZZER_PIN  23
...  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TFT_xxx&lt;/code&gt; are the pin mappings of the ST7789 TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TFT_X_OFF&lt;/code&gt; is set to 0 since the screen is 240 wide and therefore need no horizontal offset&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BUTTON_PIN&lt;/code&gt; is the pin connected to one end of the button attached to the ESP32; the other end can connect to &lt;code&gt;GND&lt;/code&gt; of the ESP32&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BUZZER_PIN&lt;/code&gt; is the pin connected to one end of the speaker attached to the ESP32; the other end can connect to &lt;code&gt;GND&lt;/code&gt; of the ESP32&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;screen_helpers.cpp&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#elif defined(FOR_ESP32)
  #include &amp;lt;Adafruit_GFX.h&amp;gt;
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  #include &amp;lt;SPI.h&amp;gt;   
  SPIClass spi = SPIClass(HSPI);
  Adafruit_ST7789 tft(&amp;amp;spi, TFT_CS, TFT_DC, TFT_RST);
...
void screenSetup() {
#if defined(TFT_BL)
  pinMode(TFT_BL, OUTPUT);
  #if defined(TFT_BL_LOW)
    digitalWrite(TFT_BL, 0);  // light it up (LOW)
  #else
    digitalWrite(TFT_BL, 1);  // light it up
  #endif
#endif
  ...
#elif defined(FOR_ESP32)  
  spi.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
  tft.setSPISpeed(40000000);
  tft.init(240, 240, SPI_MODE0);
  tft.setRotation(3);
  ...
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the first &lt;code&gt;defined(FOR_ESP32)&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;include the needed header file for the TFT LCD screen &lt;code&gt;&amp;lt;Adafruit_ST7789.h&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;instantiate the global variable &lt;code&gt;tft&lt;/code&gt; (as the TFT LCD screen object)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;the second &lt;code&gt;defined(FOR_ESP32)&lt;/code&gt;, which is inside the &lt;code&gt;void screenSetup()&lt;/code&gt; block

&lt;ul&gt;
&lt;li&gt;run the necessary code to setup the TFT LCD screen -- &lt;code&gt;tft&lt;/code&gt; declared previously&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Have fun with &lt;code&gt;AWeatherClock&lt;/code&gt;!&lt;/p&gt;

&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>esp32</category>
      <category>raspberrypipico</category>
      <category>clock</category>
      <category>arduino</category>
    </item>
    <item>
      <title>ESP-IDF with Arduino Examples</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Sun, 02 Mar 2025 12:29:11 +0000</pubDate>
      <link>https://forem.com/trevorwslee/esp-idf-with-arduino-examples-3dhe</link>
      <guid>https://forem.com/trevorwslee/esp-idf-with-arduino-examples-3dhe</guid>
      <description>&lt;h1&gt;
  
  
  ESP-IDF with Arduino Examples
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk5ynbyysj7rqk1mg1be.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk5ynbyysj7rqk1mg1be.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ESP32 microcontroller program development with Arduino framework is fun.&lt;br&gt;
Nevertheless, in order to be more flexible, it is often easier to go the route of ESP32 program development with ESP-IDF.&lt;/p&gt;

&lt;p&gt;Say,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With microphone module installed, it is possible to develop "wake-word" recognition; however, it is not clear / easy to do so in Arduino framework. E.g. &lt;a href="https://github.com/espressif/esp-box" rel="noopener noreferrer"&gt;ESP-Box&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;With camera module installed, it is possible to develop face recognition (not just face detection); however, it is not clear / easy to do so in Arduino framework. E.g. &lt;a href="https://github.com/espressif/esp-who" rel="noopener noreferrer"&gt;ESP-Eye&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consequently, one might then choose to develop ESP32 program directly and completely with the well-supported native ESP-IDF framework.&lt;/p&gt;

&lt;p&gt;Nevertheless, it is possible to do it the other way around, by incorporating Arduino framework style in ESP-IDF programming.&lt;br&gt;
Will this make ESP32 programming looks easier? Moreover, it might be possible to use libraries written using the Arduino framework. At the very least, this way might be helpful in transition from the easier non-native Arduino framework for ESP32 to the native ESP-IDF. &lt;/p&gt;

&lt;p&gt;As stated in &lt;a href="https://github.com/espressif/arduino-esp32/blob/master/docs/en/esp-idf_component.rst" rel="noopener noreferrer"&gt;Arduino as an ESP-IDF component&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use the Arduino framework as an ESP-IDF component. This allows you to use the Arduino framework in your ESP-IDF projects with the full flexibility of the ESP-IDF.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is yet to see how compatible is Arduino code with ESP-IDF code; regardless, let's have some fun trying some ESP-IDF with Arduino examples, as will be described next.&lt;/p&gt;

&lt;p&gt;Here&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VSCode will be used as the development IDE, certainly, together with ESP-IDF VSCode extension&lt;/li&gt;
&lt;li&gt;The target &lt;a href="https://github.com/espressif/esp-idf" rel="noopener noreferrer"&gt;ESP-IDF&lt;/a&gt; version is v5.3.2&lt;/li&gt;
&lt;li&gt;The target &lt;a href="https://github.com/espressif/arduino-esp32" rel="noopener noreferrer"&gt;Arduino core for ESP32&lt;/a&gt; is v3.0.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Examples
&lt;/h1&gt;

&lt;p&gt;The examples that we will try out are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="///examples/idf_blink/README.md"&gt;&lt;code&gt;idf-blink&lt;/code&gt;&lt;/a&gt;: A simple ESP-IDF blink program that is essentially generated by ESP-IDF without modification; it blinks the built-in LED (&lt;strong&gt;&lt;em&gt;pin 2&lt;/em&gt;&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="///examples/idf_with_a_blink/README.md"&gt;&lt;code&gt;idf_with_a_blink&lt;/code&gt;&lt;/a&gt;: From an "empty" ESP-IDF program, modified to incorporate with Arduino to blink the built-in LED, the same as &lt;code&gt;idf-blink&lt;/code&gt; but coded in Arduino style&lt;/li&gt;
&lt;li&gt;
&lt;a href="///examples/s3_wb2812_blink/README.md"&gt;&lt;code&gt;s3_wb2812_blink&lt;/code&gt;&lt;/a&gt;: Again, started from an "empty" ESP-IDF program, modified to incorporate with Arduino, in order to be able to use the Arduino library &lt;a href="https://github.com/Freenove/Freenove_WS2812_Lib_for_ESP32" rel="noopener noreferrer"&gt;Freenove_WS2812_Lib_for_ESP32&lt;/a&gt; for "blinking" the NeoPixel on an ESP32S3 board (&lt;strong&gt;&lt;em&gt;pin 48&lt;/em&gt;&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="///examples/wifi_dd_blink/README.md"&gt;&lt;code&gt;wifi_dd_blink&lt;/code&gt;&lt;/a&gt;: Modified to incorporate with Arduino, in order to use the Arduino library &lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;Arduino-DumbDisplay&lt;/a&gt;.
With DumbDisplay, you can realize simple UI on your Android phone. You may want to refer to the post &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt; for a brief introduction to DumbDisplay. Note that this example &lt;code&gt;wifi_dd_blink&lt;/code&gt; uses WiFi connectivity, rather than OTG as described in the post.&lt;/li&gt;
&lt;li&gt;
&lt;a href="///examples/le_dd_blink/README.md"&gt;&lt;code&gt;le_dd_blink&lt;/code&gt;&lt;/a&gt;: Like &lt;code&gt;wifi_dd_blink&lt;/code&gt;, but is a bit more extensive in using DumbDisplay's features, and is using Bluetooth Low Energy connectivity. Moreover, &lt;code&gt;le_dd_blink&lt;/code&gt; deliberately uses some ESP-IDF constructs like &lt;code&gt;xTaskCreate()&lt;/code&gt; / &lt;code&gt;vTaskDelay()&lt;/code&gt; / &lt;code&gt;printf()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Highlights
&lt;/h1&gt;

&lt;p&gt;The steps I worked out to add Android to ESP-IDF is based on the above mentioned [official] &lt;a href="https://github.com/espressif/arduino-esp32/blob/master/docs/en/esp-idf_component.rst" rel="noopener noreferrer"&gt;Arduino as an ESP-IDF component&lt;/a&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add IDF component to &lt;code&gt;idf_component.yml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  dependencies:
    idf:
      version: '&amp;gt;=4.1.0'
    espressif/arduino-esp32: ^3.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The default of &lt;code&gt;CONFIG_FREERTOS_HZ&lt;/code&gt; in &lt;code&gt;sdkconfig&lt;/code&gt; is 100, change it to 1000&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The C entry point &lt;code&gt;app_main()&lt;/code&gt; should be declared in a C++ file like&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #include "Arduino.h"
  extern "C" void app_main(void) {
      initArduino();
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The inclusion of the header file &lt;code&gt;Arduino.h&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The initialization routine &lt;code&gt;initArduino()&lt;/code&gt; should be called the first thing in the entry point &lt;code&gt;app_main()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In order to make it more like programming with Arduino framework, &lt;code&gt;setup()&lt;/code&gt; and &lt;code&gt;loop()&lt;/code&gt; can be added like
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #include "Arduino.h"
  void setup() {}
  void loop() {}
  extern "C" void app_main(void) {
    initArduino();
    setup();
    while (1) {
      loop();
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;A way&lt;/em&gt;&lt;/strong&gt; to use Arduino library is to download the Arduino library source code and compile the library code as if it is part of the main program. Hence&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in &lt;code&gt;main&lt;/code&gt;, create the folder &lt;code&gt;arduino_libraries&lt;/code&gt; for the the downloaded / cloned Arduino library source code&lt;/li&gt;
&lt;li&gt;modify &lt;code&gt;CMakeLists.txt&lt;/code&gt; in &lt;code&gt;main&lt;/code&gt; to compile the library source, like
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idf_component_register(
    SRC_DIRS "." "arduino_libraries/Freenove_WS2812_Lib_for_ESP32/src"
    INCLUDE_DIRS "arduino_libraries/Freenove_WS2812_Lib_for_ESP32/src"
)
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idf_component_register(
    SRC_DIRS "." "arduino_libraries/Arduino-DumbDisplay"
    INCLUDE_DIRS "arduino_libraries/Arduino-DumbDisplay"
)
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Demonstration
&lt;/h1&gt;

&lt;p&gt;You may be interested in a demonstration of steps leading to the examples as recorded in the video &lt;a href="https://www.youtube.com/watch?v=RX5YHPoaXaY" rel="noopener noreferrer"&gt;ESP-IDF with Arduino Examples; from IDF-Blink to Virtual Blink using WiFi / BLE&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;p&gt;Hope that you will have fun with the examples!&lt;/p&gt;

&lt;p&gt;Do share anything you find interesting / insightful concerning ESP-IDF with Arduino development.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>espidf</category>
      <category>arduino</category>
      <category>esp32</category>
      <category>esp32s3</category>
    </item>
    <item>
      <title>Sliding Puzzle Next Move Suggesting Simple DL Model with ESP32 TensorFlow Lite</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Sun, 08 Dec 2024 14:17:00 +0000</pubDate>
      <link>https://forem.com/trevorwslee/sliding-puzzle-next-move-suggesting-simple-dl-model-with-esp32-tensorflow-lite-3k22</link>
      <guid>https://forem.com/trevorwslee/sliding-puzzle-next-move-suggesting-simple-dl-model-with-esp32-tensorflow-lite-3k22</guid>
      <description>&lt;h1&gt;
  
  
  Sliding Puzzle 'Next Move' Suggesting Simple DL Model with ESP32 TensorFlow Lite
&lt;/h1&gt;

&lt;p&gt;This &lt;a href="https://github.com/trevorwslee/ESP32SlidingPuzzle" rel="noopener noreferrer"&gt;project&lt;/a&gt; takes the game &lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay/blob/master/examples/sliding_puzzle_w_suggest/sliding_puzzle_w_suggest.ino" rel="noopener noreferrer"&gt;Sliding Puzzle&lt;/a&gt;&lt;br&gt;
(with simple 'next move' suggesting 'search-directed heuristic' option),&lt;br&gt;
adding to it the capability of suggesting 'next move' with a &lt;strong&gt;&lt;em&gt;simple and naive&lt;/em&gt;&lt;/strong&gt; DL model realized with ESP32 TensorFow Lite support.&lt;br&gt;
The Sliding Puzzle game is implemented for Arduino framework compatible microcontroller boards with aids from &lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;DumbDisplay&lt;/a&gt;&lt;br&gt;
to render the game remotely on your Android mobile phone.&lt;br&gt;
Specifically, ESP32 / ESP32S3 is the targe microcontroller board for this experiment, since it not only supports Arduino framework, it also supports TensorFlow Lite.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faebn5njxayh7w4o6mk0y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faebn5njxayh7w4o6mk0y.gif" width="216" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DL model of this experiment is implemented with TensorFlow Lite that I worked out by referencing to two of my previous experiments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.instructables.com/Trying-Out-TensorFlow-Lite-Hello-World-Model-With-/" rel="noopener noreferrer"&gt;Trying Out TensorFlow Lite Hello World Model With ESP32 and DumbDisplay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.instructables.com/Mnist-Dataset-From-Training-to-Running-With-ESP32-/" rel="noopener noreferrer"&gt;Mnist Dataset -- From Training to Running With ESP32 / ESP32S3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to an ESP32 / ESP32S3 microcontroller board, a few tools are assumed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;VSCode and PlatformIO -- reference on how they are used: &lt;a href="https://www.instructables.com/A-Way-to-Run-Arduino-Sketch-With-VSCode-PlatformIO/" rel="noopener noreferrer"&gt;A Way to Run Arduino Sketch With VSCode PlatformIO Directly&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;DumbDisplay Android app -- reference: &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Building DL Model with VSCode
&lt;/h1&gt;

&lt;p&gt;First, clone this project's source from the project's &lt;a href="https://github.com/trevorwslee/ESP32SlidingPuzzle" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/trevorwslee/ESP32SlidingPuzzle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the cloned directory &lt;code&gt;ESP32SlidingPuzzle&lt;/code&gt; with VSCode, then open &lt;code&gt;train_model.ipynb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5iuut8k6u9f488ftm0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5iuut8k6u9f488ftm0k.png" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run all cells of the Jupyter Notebook &lt;code&gt;train_model.ipynb&lt;/code&gt;.&lt;br&gt;
If environment is not setup already, it should first prompt you to create / select an Python environment. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8sa0q0ynipjlqt111cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8sa0q0ynipjlqt111cy.png" width="776" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a virtual Python environment for the project&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs875ybc27cmw74cmh1r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs875ybc27cmw74cmh1r0.png" width="761" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qc40k0hrlytf6ugj60d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qc40k0hrlytf6ugj60d.png" width="762" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3iuczyjs246kdavtx2ts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3iuczyjs246kdavtx2ts.png" width="762" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the last step, when asked to install dependencies, &lt;strong&gt;&lt;em&gt;make sure&lt;/em&gt;&lt;/strong&gt; to select &lt;code&gt;requirement.txt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihjr2nl27hctkv10emlg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihjr2nl27hctkv10emlg.png" width="759" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This should create the Python virtual environment &lt;code&gt;.venv&lt;/code&gt; for the project with the needed dependencies installed.&lt;/p&gt;

&lt;p&gt;If all the cells completed successfully, the model C header file &lt;code&gt;src/esp32_sliding_puzzle/sp_model_4.h&lt;/code&gt; will be generated, overwriting the one already there.&lt;br&gt;
Yes, there is one already included with this project, and hence it is not necessary for you to train the model again, unless you would like to tune the DL model.&lt;/p&gt;

&lt;p&gt;The DL model is &lt;strong&gt;&lt;em&gt;naively&lt;/em&gt;&lt;/strong&gt; constructed with Keras like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tile_count = 4
batch_size = 128
epochs = 100
...
model = keras.models.Sequential()
model.add(keras.layers.Dense(256, activation='relu', input_shape=(tile_count * tile_count,)))
model.add(keras.layers.Dropout(0.3))
model.add(keras.layers.Dense(256, activation='relu'))
model.add(keras.layers.Dense(4, activation='softmax'))
...
model.compile(loss='categorical_crossentropy', optimizer=keras.optimizers.RMSprop(), metrics=['accuracy'])
history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_validate, y_validate))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With accuracy 0.868, the DL model appears not very good, but should be acceptable&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvrpar6hyn744rvuvjxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvrpar6hyn744rvuvjxx.png" width="719" height="582"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj65cao4qfyovokptxsy4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj65cao4qfyovokptxsy4.png" width="730" height="583"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Building and Uploading the Sketch
&lt;/h1&gt;

&lt;p&gt;The steps to build and upload the sketch is via PlatformIO (an extension of VSCode).&lt;/p&gt;

&lt;p&gt;The configurations for developing and building of the sketch are basically written down in the &lt;code&gt;platformio.ini&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[env]
monitor_speed = 115200

[env:ESP32]
platform = espressif32
board = esp32dev
framework = arduino
board_build.partitions = huge_app.csv
monitor_filters = esp32_exception_decoder
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
    tanakamasayuki/TensorFlowLite_ESP32@^1.0.0
build_flags =
    -D FOR_ESP32

[env:ESP32S3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
    tanakamasayuki/TensorFlowLite_ESP32@^1.0.0
build_flags =
    -D FOR_ESP32S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that you have choices of PlatformIO environments -- &lt;strong&gt;&lt;em&gt;ESP32&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;ESP32S3&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please choose the correct one according to the microcontroller board that you have.&lt;/p&gt;

&lt;p&gt;In either case, the program entry point is &lt;code&gt;src/main.cpp&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;Arduino.h&amp;gt;

#if defined(CONFIG_IDF_TARGET_ESP32S3)
  #include "_secret.h"
#else
  #define BLUETOOTH "ESP32Sliding"
#endif

#include "esp32_sliding_puzzle/esp32_sliding_puzzle.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For ESP32S3, WiFi connection to DumbDisplay Android app is used. In such a case, you will need to provide necessary WiFi login credentials in a header file &lt;code&gt;src/_secret.h&lt;/code&gt; that you need to create with content like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #define WIFI_SSID           "wifi-ssid"
  #define WIFI_PASSWORD       "wifi-password"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Otherwise, for ESP32, Bluetooth connection to DumbDisplay Android app is used, with the Bluetooth device name &lt;code&gt;ESP32Sliding&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build and upload the sketch with VSCode menu item &lt;strong&gt;&lt;em&gt;View | Command Palette&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3ylhw1q471xleb2zebi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3ylhw1q471xleb2zebi.png" width="771" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d85fxndkswed4zhygh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d85fxndkswed4zhygh5.png" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For ESP32 which is using Bluetooth connectivity, you should see log entries with Serial Monitor (with baud rate 115200) like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa76swh7rq1nd77jxsoxq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa76swh7rq1nd77jxsoxq.png" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For ESP32S3 which is using Wifi connectivity, the log entries will be like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p8iircvrosj8s77qe4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p8iircvrosj8s77qe4w.png" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the IP to connect to the ESP32 board shown by the log entries.&lt;/p&gt;

&lt;p&gt;To connect your Android phone to your ESP32 / ESP32S3 microcontroller board via DumbDisplay Android app, open the DumbDisplay Android app and make connection like&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;with Bluetooth&lt;/th&gt;
&lt;th&gt;add WiFi&lt;/th&gt;
&lt;th&gt;with Wifi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/images%2Fdd-connect-bt.jpg" width="" height=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/images%2Fdd-connect-wifi-add.jpg" width="" height=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmd6zqkneisbtcb5d09h7.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Sliding Puzzle Game UI
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;After connection, you should see the picture of the board drawn.&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2ctb12272yzykgmi7nv.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;To start a game, double click on the board, to divide the board into grid of tiles&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho9yfzky99oog8g0ohq5.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Double clicking on the board will randomize the board by 5 steps (5 random moves).&lt;/p&gt;

&lt;p&gt;During game play, you can click &lt;strong&gt;&lt;em&gt;Suggest&lt;/em&gt;&lt;/strong&gt; for a suggested 'next move'.&lt;br&gt;
If you click &lt;strong&gt;&lt;em&gt;Continuous&lt;/em&gt;&lt;/strong&gt;, suggested 'next moves' are continuously made until either you disabled &lt;strong&gt;&lt;em&gt;Continuous&lt;/em&gt;&lt;/strong&gt; or the puzzle is solved. &lt;/p&gt;

&lt;p&gt;There are three options for the 'next move' suggestion:&lt;br&gt;
1) &lt;strong&gt;&lt;em&gt;AI Only&lt;/em&gt;&lt;/strong&gt; -- use the trained DL model for predicting 'next move' (the highest probability one)&lt;br&gt;
2) &lt;strong&gt;&lt;em&gt;AI+Search&lt;/em&gt;&lt;/strong&gt; -- largely use the trained DL model for predicting 'next move'; however, if the top 'next move' probability is not high, fallback to use original "search* algo &lt;br&gt;
3) &lt;strong&gt;&lt;em&gt;Search Only&lt;/em&gt;&lt;/strong&gt; -- use original "search" algo only&lt;/p&gt;

&lt;p&gt;After every solving of the puzzle, 5 more randomize steps are added to randomization making the game a bit harder.&lt;/p&gt;

&lt;p&gt;Any time if you want to reset / restart the game, double click on the &lt;strong&gt;&lt;em&gt;Reset&lt;/em&gt;&lt;/strong&gt; button.&lt;/p&gt;
&lt;h1&gt;
  
  
  Improving the 'Next Move' Suggestion Accuracy
&lt;/h1&gt;

&lt;p&gt;The 'next move' suggestion is certainly not very accurate, specially when the board is randomized for many steps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;&lt;em&gt;naive&lt;/em&gt;&lt;/strong&gt; DL model can certainly be improved.
Notice that in&lt;code&gt;train_model.ipynb&lt;/code&gt;, the training data is randomized in reverse of how a board is randomize.
Hence, it makes sense that the number of randomize steps affects the accuracy of the model specially in case the game is randomized for more steps than the model is trained.
Here are the parameters for training the model:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;round_count&lt;/code&gt; -- the number of rounds of random boards to generate; &lt;code&gt;10000&lt;/code&gt; by default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;random_step_count&lt;/code&gt; -- the number of random moves from "solved orientation" to "randomized orientation"; &lt;code&gt;20&lt;/code&gt; by default
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The "search" algo can be improved&lt;/li&gt;
&lt;li&gt;The DL + "search" can be improved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moreover, the board size can be bigger, like 5x5.&lt;br&gt;
In order to change the board size to 5:&lt;/p&gt;

&lt;p&gt;1) Need to change &lt;code&gt;tile_count&lt;/code&gt; of &lt;code&gt;train_model.ipynb&lt;/code&gt; generate a &lt;code&gt;sp_model_5.h&lt;/code&gt; for the sketch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   # tile_count is the number of horizontal / vertical tiles ... a tile_count of 4 means a 4x4 board
   tile_count = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Need to modify the sketch &lt;code&gt;esp32_sliding_puzzle.ino&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   #define TILE_COUNT 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which controls&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    #if TILE_COUNT == 4
      #include "sp_model_4.h"
    #elif TILE_COUNT == 5
      #include "sp_model_5.h"
    #endif
    const tflite::Model* model = ::tflite::GetModel(sp_model);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Interested friends are encouraged to try out and share how the 'next move' suggestion can be improved.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  A LLM Prompt for the Moves
&lt;/h1&gt;

&lt;p&gt;BTW, here is a challenging LLM prompt for you to try out (just for fun)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if a 4x4 sliding puzzle board game "reference" board layout is represented with

---------------------
|    |  2 |  3 |  4 |
|  5 |  6 |  7 |  8 |
|  9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 |
---------------------

and move of the "missing" cell is represented with a number:
. 0: move to left
. 1: move to right
. 2: move to top
. 3: move to bottom

1:
when move the "missing" cell of the "reference" board layout with move 1, what is the board layout after the move?

2:
from the "reference" board layout, what is the board layout after the moves: 1, 1, 3, 3

3:
if given the board layout
---------------------
|  2 |  6 |  3 |  4 |
|  5 | 10 |  7 |  8 |
|    |  9 | 11 | 12 |
| 13 | 14 | 15 | 16 |
---------------------
what are the moves in order to bring the board back to the "reference" board layout?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From my experience, not all LLM models I tried can provide satisfactory response for the above prompt.&lt;br&gt;
One reason might be that the prompt is not well written; another might be that the answer to question 3 above can be "endless".&lt;br&gt;
GPT4o seems to provide reasonable response; but others like&lt;br&gt;
&lt;strong&gt;&lt;em&gt;GPT4 or even DeepSeek might produce long response that appears never ending; &lt;br&gt;
hence, be prepared to stop the response generation.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;p&gt;Hope that you will have fun with it! Enjoy!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>slidingpuzzle</category>
      <category>esp32</category>
      <category>tensorflowlite</category>
    </item>
    <item>
      <title>Turn ESP32-CAM into a Snapshot Taker, for Selfies and Time-Lapse Pictures</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Wed, 11 Sep 2024 13:42:03 +0000</pubDate>
      <link>https://forem.com/trevorwslee/turn-esp32-cam-into-a-snapshot-taker-for-selfies-and-time-lapse-pictures-2125</link>
      <guid>https://forem.com/trevorwslee/turn-esp32-cam-into-a-snapshot-taker-for-selfies-and-time-lapse-pictures-2125</guid>
      <description>&lt;h1&gt;
  
  
  Turn ESP32-CAM into a Snapshot Taker, for Selfies and Time Lapse Pictures
&lt;/h1&gt;

&lt;p&gt;This project is an attempt to turn ESP32-CAM (or similar ones like LILYGO T-Camera / T-Camera Plus) microcontroller board into an Android phone-managed snapshot taker, for snapshots like selfies and time-lapse pictures ... maybe ... just for fun!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With your Android phone, connect to the ESP32-CAM, make your post and smile.&lt;br&gt;
 Then go back to your phone to select the pictures you like best and save to your phone directly.&lt;/p&gt;

&lt;p&gt;With your Android phone, connect to the ESP32-CAM, set how frequently a snapshot is to be taken.&lt;br&gt;
 Then disconnect from the ESP32-CAM and let it do its job of taking time-lapse pictures.&lt;br&gt;
 Then reconnect to the ESP32-CAM to transfer the taken pictures to your phone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faikiw0szlt3ygsvvo92s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faikiw0szlt3ygsvvo92s.png" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The microcontroller program (sketch) is developed with Arduino framework using VS Code and PlatformIO, in the similar fashion as described by the post -- &lt;a href="https://www.instructables.com/A-Way-to-Run-Arduino-Sketch-With-VSCode-PlatformIO/" rel="noopener noreferrer"&gt;A Way to Run Arduino Sketch With VSCode PlatformIO Directly&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The remote UI -- control panel -- is realized with the help of the DumbDisplay Android app. You have a choice of using Bluetooth (classic) or WiFi for the connection. For a brief description of DumbDisplay, please refer to the post -- &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note that the UI is driven by the sketch; i.e., the flow of the UI is programmed in the sketch. Other than installing the DumbDisplay Android app, no building of a separate mobile app is necessary.&lt;/p&gt;

&lt;h1&gt;
  
  
  The UI -- Ways of taking Snapshots
&lt;/h1&gt;

&lt;p&gt;There are 3 ways snapshots can be captured and saved to your phone&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With your phone connected to the ESP32-CAM -- certainly through the DumbDisplay Android app with Bluetooth or WiFi -- you click the &lt;strong&gt;&lt;em&gt;💾Save&lt;/em&gt;&lt;/strong&gt; button of the UI to save the image being shown. You can turn on / off the auto-save feature by clicking &lt;strong&gt;&lt;em&gt;Auto❎&lt;/em&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;em&gt;Auto☑️&lt;/em&gt;&lt;/strong&gt;. With auto-save enabled, whenever a snapshot is captured from the ESP32-CAM, it will be saved to your phone automatically.&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femp2xcd7r8kmqbc8mdqx.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Assuming you have connected your phone with the ESP32-CAM, clicking on the image canvas will pause showing snapshots, and you will be provided with a slider to slide back in time to select the previously captured snapshots to save to your phone. You will have 20 such snapshots that you can slide back in time to. Once you see the snapshots you like, click &lt;strong&gt;&lt;em&gt;💾Save&lt;/em&gt;&lt;/strong&gt; to save it. When you are done, you can go back by clicking &lt;strong&gt;&lt;em&gt;❌Cancel&lt;/em&gt;&lt;/strong&gt; (or double click on the image canvas).&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k4n7mg5erhg4oolev4i.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Select &lt;strong&gt;&lt;em&gt;Offline📴&lt;/em&gt;&lt;/strong&gt; to enable "offline" capturing and saving of snapshots to the ESP32-CAM. With "offline" enabled, when you disconnect (i.e. offline), the ESP32-CAM will start capturing "offline" snapshots saving them to its flash memory (or SD card). Whenever you reconnect, you will be asked if you want to transfer the saved "offline" snapshots to your phone. Better yet, in SD card case, you can physically transfer the "offline" snapshot &lt;strong&gt;&lt;em&gt;JPEG&lt;/em&gt;&lt;/strong&gt; files saved the usual way -- insert the SD card to your computer, copy and delete the &lt;strong&gt;&lt;em&gt;JPEG&lt;/em&gt;&lt;/strong&gt; files on your SD card. &lt;em&gt;One point about using the SD card slot of ESP32-CAM -- since the SD card module of ESP32-CAM shares the same pin 4 used by the flashlight, whenever the SD card is accessed, the flashlight will light up bright (hence it is not a feature, but it is how it is)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;img src="/imgs/snapper-ss-02.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vpdgdodwhzmb511q1lu.jpg" width="800" height="1777"&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The snapshots will be saved in &lt;strong&gt;&lt;em&gt;JPEG&lt;/em&gt;&lt;/strong&gt; format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snapshots saved to your phone will be saved to the private folder of DumbDisplay app - &lt;code&gt;/&amp;lt;main-storage&amp;gt;/Android/data/nobody.trevorlee.dumbdisplay/files/Pictures/DumbDisplay/snaps/&lt;/code&gt; -- with file name like &lt;code&gt;20240904_223249_001.jpg&lt;/code&gt; (&lt;code&gt;YYYYMMDD-hhmmss_seq.jpg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Offline snapshots transferred to your phone will be saved to a subfolder of the above-mentioned private folder of DumbDisplay app. The name of the subfolder depends on the time you do the snapshots transfer, and is like &lt;code&gt;20240904_231329_OFF&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Offline snapshots are saved to your ESP32-CAM's flash memory / SD card with name like &lt;code&gt;off_001.jpg&lt;/code&gt;. Note that the "offline" &lt;strong&gt;&lt;em&gt;JPEG&lt;/em&gt;&lt;/strong&gt; files will only be properly time-stamped if there was no reset / reboot of ESP32-CAM after connecting to your phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;By default, the storage for DumbDisplay app is a private folder, which DumbDisplay app needs to initialize. You can do this By selecting the &lt;strong&gt;&lt;em&gt;Settings&lt;/em&gt;&lt;/strong&gt; menu item of DumbDisplay app, and clicking on the &lt;strong&gt;&lt;em&gt;Media Storage&lt;/em&gt;&lt;/strong&gt; button&lt;/td&gt;
&lt;td&gt;&lt;img src="/imgs/dd-settings-menu.jpg"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcqoox9t2adt2dsdz1dk.jpg" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;And you can use a folder manager app, like &lt;a href="https://play.google.com/store/apps/details?id=com.marc.files&amp;amp;hl=en" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;Files&lt;/em&gt;&lt;/strong&gt; by &lt;strong&gt;&lt;em&gt;Marc apps &amp;amp; software&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; to browse to that folder.&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1ua729zylptqrgt36lc.gif" width="800" height="1777"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  The UI -- Frequency of Capturing Snapshots
&lt;/h1&gt;

&lt;p&gt;Actually, snapshot capturing is continuous (as fast as possible), but captured snapshot shipment to your phone is not as smooth; in fact there is a setting on how frequently a snapshot is shipped to your phone. Depending on the resolution / quality of the snapshot and the connection method / condition, it can be as frequent as 5 snapshots per second. Treat this frequency control as a feature that allows taking of time-lapse pictures at a desired frequency 😁&lt;/p&gt;

&lt;p&gt;There are 4 quick selections of frequency / frame rate -- &lt;strong&gt;&lt;em&gt;5 PS&lt;/em&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;em&gt;2 PS&lt;/em&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;em&gt;1 PS&lt;/em&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;em&gt;30 PM&lt;/em&gt;&lt;/strong&gt; corresponding to &lt;em&gt;5 frames per second&lt;/em&gt; / &lt;em&gt;2 frames per second&lt;/em&gt; / &lt;em&gt;1 frame per second&lt;/em&gt; / &lt;em&gt;30 frames per minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And there is a custom per-hour frame rate you can set and select -- &lt;strong&gt;&lt;em&gt;720 PH&lt;/em&gt;&lt;/strong&gt; (720 is the default). Once you click on the &lt;strong&gt;&lt;em&gt;720 PH&lt;/em&gt;&lt;/strong&gt; selection, your phone's virtual keyword will pop up allowing you to enter the value (1 - 3600) you want (an empty value means previously set value). &lt;/p&gt;

&lt;h1&gt;
  
  
  The UI -- Snapshots Quality Adjustments
&lt;/h1&gt;

&lt;p&gt;There are several camera adjustments that will affect the quality of the captured snapshots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snapshot resolution / size selections:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;QVGA&lt;/em&gt;&lt;/strong&gt; (320x240)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;VGA&lt;/em&gt;&lt;/strong&gt; (640x480)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;SVGA&lt;/em&gt;&lt;/strong&gt; (800x600)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;XGA&lt;/em&gt;&lt;/strong&gt; (1024x768)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;HD&lt;/em&gt;&lt;/strong&gt; (1280x720)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;SXGA&lt;/em&gt;&lt;/strong&gt; (1280x1024)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;UXGA&lt;/em&gt;&lt;/strong&gt; (1600x1200)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;em&gt;JPEG&lt;/em&gt;&lt;/strong&gt; compression quality slider 🖼️ -- from 5 (high quality) to 60 (low quality)&lt;/li&gt;

&lt;li&gt;Brightness slider ☀️ -- from -2 (dim) to 2 (bright)&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Note that the higher the snapshots quality / resolution, the more data needs be shipped from the ESP32-CAM to your phone, making the UI less responsive, especially when WiFi (not Bluetooth) since WiFi is not full-duplex.&lt;/p&gt;

&lt;h1&gt;
  
  
  The UI -- ESP32 Idle Sleep
&lt;/h1&gt;

&lt;p&gt;Another feature is that when "offline" snapshot capturing is enabled, after a while (60 seconds) of being idle (i.e. not connected), the ESP32-CAM will be put to sleep mode in order to save power. Of course, since "offline" capturing is enabled, the ESP32-CAM will wake up in due time to take a snapshot. However if "offline" frequency is too high, higher than 12 frames per minute, the ESP32-CAM will stay on.&lt;/p&gt;

&lt;p&gt;In any case, &lt;strong&gt;&lt;em&gt;if you want to connect to the ESP32-CAM when it is in sleep mode, you will need to reset / reboot the ESP32-CAM first&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Developing and Building
&lt;/h1&gt;

&lt;p&gt;As mentioned previously, the sketch will be developed using VS Code and PlatformIO.&lt;br&gt;
Please clone the PlatformIO project &lt;a href="https://github.com/trevorwslee/ESP32CamSnapper" rel="noopener noreferrer"&gt;ESP32CamSnapper&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;The configurations for developing and building of the sketch are basically written down in the &lt;code&gt;platformio.ini&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[env]
monitor_speed = 115200

[env:ESP32CAM]
platform = espressif32
board = esp32cam
framework = arduino
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
build_flags =
    -D FOR_ESP32CAM

[env:TCAMERA]  ; v7
platform = espressif32
board = esp-wrover-kit
framework = arduino
board_build.partitions = huge_app.csv
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
build_flags =
    -D BOARD_HAS_PSRAM
    -D FOR_TCAMERA

[env:TCAMERAPLUS]
platform = espressif32
board = esp32dev
framework = arduino
board_build.partitions = huge_app.csv
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
build_flags =
    -D BOARD_HAS_PSRAM
    -D FOR_TCAMERAPLUS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Please make sure you select the correct PlatformIO project environment&lt;/em&gt;&lt;/strong&gt; -- &lt;code&gt;ESP32CAM&lt;/code&gt; / &lt;code&gt;TCAMERA&lt;/code&gt; / &lt;code&gt;TCAMERAPLUS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The program entry point is &lt;code&gt;src/main.cpp&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// *** use Bluetooth with the following device name
#define BLUETOOTH "ESP32CamSnapper"
#include "esp32camsnapper/esp32camsnapper.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above &lt;code&gt;main.cpp&lt;/code&gt; assumes that you prefer to use Bluetooth (classic), and defines the Bluetooth device name to be &lt;strong&gt;&lt;em&gt;ESP32CamSnapper&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define BLUETOOTH "ESP32CamSnapper"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that, for the purposes of this sketch, Bluetooth (classic) is the suggested way of connecting with DumbDisplay app.&lt;/p&gt;

&lt;p&gt;However, if you prefer to use WiFi, you will need to define &lt;code&gt;WIFI_SSID&lt;/code&gt; and &lt;code&gt;WIFI_PASSWORD&lt;/code&gt; rather than &lt;code&gt;BLUETOOTH&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//#define BLUETOOTH "ESP32CamSnapper"
#define WIFI_SSID           "your-wifi-ssid"
#define WIFI_PASSWORD       "your-wifi-password"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case of WiFi, you will need to find out the IP of your ESP32-CAM in order to make connect to it. This can be done easily by attaching it to a Serial monitor (set to baud-rate 115200). There, when your ESP32-CAM tries to connect to DumbDisplay app, log lines like below will be printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;binded WIFI TrevorWireless
listening on 192.168.0.155:10201 ...
listening on 192.168.0.155:10201 ...
listening on 192.168.0.155:10201 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That IP address is the IP address of your ESP32-CAM to make connection to.&lt;/p&gt;

&lt;p&gt;If you have SD card installed for ESP32-CAM (or LILYGO T-Camera Plus), you can use it to store "offline" snapshots, rather than the limited flash memory of the microcontroller board.&lt;/p&gt;

&lt;p&gt;To configure the sketch for such setup, you will need to define the macro &lt;code&gt;OFFLINE_USE_SD&lt;/code&gt;, like by uncomment the corresponding line of the sketch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// *** uncomment the following if offline snaps are to be stored to SD (ESP32CAM / TCameraPlus), rather than LittleFS
#define OFFLINE_USE_SD 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or put the &lt;code&gt;#define&lt;/code&gt; line in &lt;code&gt;main.cpp&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define OFFLINE_USE_SD 
#define BLUETOOTH "ESP32CamSnapper"
#include "esp32camsnapper/esp32camsnapper.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The Sketch
&lt;/h1&gt;

&lt;p&gt;The sketch of the project is &lt;code&gt;esp32camsnapper/esp32camsnapper.ino&lt;/code&gt;. There are several customizations you may want to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By default, you have 20 snapshots you can go back in time to. This number is controlled by the macro &lt;code&gt;STREAM_KEEP_IMAGE_COUNT&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // *** maximum number of images to keep cached (on app side) when streaming snaps to DD app; this number affects how much you can go back to select which image to save
  #define STREAM_KEEP_IMAGE_COUNT             20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;By default, you can enable "offline" snapshot capturing. If you don't want "offline" at all, you can comment out the corresponding &lt;code&gt;#define&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // *** support offline snap taking; comment out if not desired
  #define SUPPORT_OFFLINE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;By default, the ESP32-CAM will be put to sleep after 60 seconds being idle (i.e. not connected). You can change it to other value by changing the macro &lt;code&gt;IDLE_SLEEP_SECS&lt;/code&gt;. If this 'idle put to sleep' behavior is not desirable, you can comment out that &lt;code&gt;#define&lt;/code&gt; to disable such behavior
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // *** number of seconds to put ESP to sleep when idle (not connected); if ESP went to sleep, will need to reset it in order to connect; comment out if not desired
  #define IDLE_SLEEP_SECS                     60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Most settings you change in the UI will be persisted to the EEPROM of the ESP32-CAM. These settings will be effective even you re-upload the program built. In case you want to reset those settings to their defaults, change the &lt;code&gt;HEADER&lt;/code&gt; to some other value, build and re-upload
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // *** if want to reset settings / offline snap metadata saved in EEPROM, set the following to something else
  const int32_t HEADER = 20240910;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Sketch Highlight -- EEPROM
&lt;/h1&gt;

&lt;p&gt;Firstly, in order to use the EEPROM, you will first need to reserve some number of bytes for it, like:&lt;br&gt;
void setup() {&lt;br&gt;
  Serial.begin(115200);&lt;br&gt;
  EEPROM.begin(32);&lt;br&gt;
  ...&lt;br&gt;
}&lt;br&gt;
Here, 32 bytes are reserved for the purpose.&lt;/p&gt;

&lt;p&gt;Most settings are persisted to the EEPROM of ESP32-CAM like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  EEPROM.writeLong(0, HEADER);
  EEPROM.writeChar(4, cameraBrightness);
  EEPROM.writeBool(5, cameraVFlip);
  EEPROM.writeBool(6, cameraHMirror);
  EEPROM.writeChar(7, currentCachePMFrameRateIndex);
  EEPROM.writeChar(8, currentFrameSizeIndex);
  EEPROM.writeBool(9, enableOffline);
  EEPROM.writeShort(10, customCachePHFrameRate);
  EEPROM.writeBool(12, autoSave);
  EEPROM.writeChar(13, cameraQuality);
  EEPROM.commit();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long&lt;/strong&gt; is 4 bytes in size, and can be used for &lt;code&gt;int32_t&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Char&lt;/strong&gt; is 1 byte in size, and can be used for &lt;code&gt;int8_t&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bool&lt;/strong&gt; is also 1 byte in size, and can be used for &lt;code&gt;bool&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Short&lt;/strong&gt; is 2 bytes in size, and can be used for &lt;code&gt;int16_t&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;And these settings are read back when ESP32-CAM starts up like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  int32_t header = EEPROM.readLong(0);
  if (header != HEADER) {
    dumbdisplay.logToSerial("EEPROM: not the expected header ... " + String(header) + " vs " + String(HEADER));
    return;
  }
  cameraBrightness = EEPROM.readChar(4);
  cameraVFlip = EEPROM.readBool(5);
  cameraHMirror = EEPROM.readBool(6);
  currentCachePMFrameRateIndex = EEPROM.readChar(7);
  currentFrameSizeIndex = EEPROM.readChar(8);
  enableOffline = EEPROM.readBool(9);
  customCachePHFrameRate = EEPROM.readShort(10);
  autoSave = EEPROM.readBool(12);
  cameraQuality = EEPROM.readChar(13);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Sketch Highlight -- ESP32 Sleep Mode
&lt;/h1&gt;

&lt;p&gt;ESP32 can be put to sleep like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;esp_sleep_enable_timer_wakeup(sleepTimeoutMillis * 1000);  // in micro second
esp_deep_sleep_start(); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it wakes up, the sketch / program will be just like freshly started, except for variables like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTC_DATA_ATTR int32_t tzMins = 0;
RTC_DATA_ATTR int64_t wakeupOfflineSnapMillis = -1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the variable prefixed with &lt;code&gt;RTC_DATA_ATTR&lt;/code&gt; will keep its value through sleep.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sketch Highlight -- Camera
&lt;/h1&gt;

&lt;p&gt;The ESP32-CAM Camera is initialized with &lt;code&gt;initializeCamera&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool initializeCamera(framesize_t frameSize, int jpegQuality) {
  esp_camera_deinit();     // just in case, disable camera first
  delay(50);
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  ...
  config.pixel_format = PIXFORMAT_JPEG; 
  config.frame_size = frameSize;                
  config.jpeg_quality = jpegQuality;
  config.fb_count = 1;
  ...
  esp_err_t camerr = esp_camera_init(&amp;amp;config);  // initialize the camera
  if (camerr != ESP_OK) {
    dumbdisplay.logToSerial("ERROR: Camera init failed with error -- " + String(camerr));
  }
  resetCameraImageSettings();
  return (camerr == ESP_OK);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;It is important that &lt;code&gt;pixel_format&lt;/code&gt; be set to &lt;code&gt;PIXFORMAT_JPEG&lt;/code&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Also notice that at the end of &lt;code&gt;initializeCamera&lt;/code&gt;, &lt;code&gt;resetCameraImageSettings&lt;/code&gt; is called to set the various settings of the Camera, which is also called when you change camera settings with the UI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool resetCameraImageSettings() {
  sensor_t *s = esp_camera_sensor_get();
  if (s == NULL) {
    dumbdisplay.logToSerial("Error: problem reading camera sensor settings");
    return 0;
  }
  ...
  s-&amp;gt;set_brightness(s, cameraBrightness);  // (-2 to 2) - set brightness
  s-&amp;gt;set_quality(s, cameraQuality);        // set JPEG quality (0 to 63)
  ...
  return 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concerning capturing snapshots, here is how an "offline" snapshot is taken when ESP32-CAM wakes up from sleep&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void setup() {
  Serial.begin(115200);
  EEPROM.begin(32);
  initRestoreSettings();
  initializeStorage();
  ...
  if (wakeupOfflineSnapMillis != -1) {  // wakeupOfflineSnapMillis will be -1 if ESP is reset
    String localTime;
    long now = getTimeNow(&amp;amp;localTime);
    Serial.println("*** woke up for offline snap ... millis=" + localTime + " ***");
    framesize_t frameSize = cameraFrameSizes[currentFrameSizeIndex];
    if (initializeCamera(frameSize, cameraQuality)) { 
      delay(SLEEP_WAKEUP_TAKE_SNAP_DELAY_MS);
      if (true) {
        // it appears that the snap taken will look better if the following "empty" steps are taken        
        camera_fb_t* camera_fb = esp_camera_fb_get();
        esp_camera_fb_return(camera_fb);
      }
      camera_fb_t* camera_fb = esp_camera_fb_get();
      saveOfflineSnap(camera_fb-&amp;gt;buf, camera_fb-&amp;gt;len);
      esp_camera_fb_return(camera_fb);
    } else {
      Serial.println("failed to initialize camera for offline snapping");
    }
    Serial.println("*** going back to sleep ... timeout=" + String(wakeupOfflineSnapMillis / 1000.0) + "s ***");
    Serial.flush();
    esp_sleep_enable_timer_wakeup(wakeupOfflineSnapMillis * 1000);  // in micro second
    esp_deep_sleep_start();
    // !!! the above call will not return !!!
  }    
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the value of &lt;code&gt;wakeupOfflineSnapMillis&lt;/code&gt; is used to determine whether the program is started due to normal boot up of wake up; it is set to the value of "how many milliseconds to sleep for next "offline" snap&lt;/li&gt;
&lt;li&gt;certain, &lt;code&gt;initializeCamera&lt;/code&gt; will be called to initialize the camera&lt;/li&gt;
&lt;li&gt;after initializing the camera, the camera is given some time (2 seconds) to be ready&lt;/li&gt;
&lt;li&gt;then the camera's buffer is retrieved by calling &lt;code&gt;esp_camera_fb_get&lt;/code&gt;, which contains the bytes (&lt;strong&gt;&lt;em&gt;JPEG&lt;/em&gt;&lt;/strong&gt; bytes) to be saved by calling &lt;code&gt;saveOfflineSnap&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;after saving the bytes, the buffered is returned by calling &lt;code&gt;esp_camera_fb_return&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;at last, it goes back to sleep again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will find that the tutorial &lt;a href="https://randomnerdtutorials.com/esp32-cam-ov2640-camera-settings/" rel="noopener noreferrer"&gt;Change ESP32-CAM OV2640 Camera Settings: Brightness, Resolution, Quality, Contrast, and More&lt;/a&gt; by &lt;strong&gt;Random Nerd Tutorials&lt;/strong&gt; gives more details on ESP32-CAM.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sketch Highlight -- DumbDisplay
&lt;/h1&gt;

&lt;p&gt;Like all other use cases of using DumbDisplay, you first declare a global &lt;code&gt;DumbDisplay&lt;/code&gt; object &lt;code&gt;dumbdisplay&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#if defined(BLUETOOTH)
  #include "esp32dumbdisplay.h"
  DumbDisplay dumbdisplay(new DDBluetoothSerialIO(BLUETOOTH));
#elif defined(WIFI_SSID)
  #include "wifidumbdisplay.h"
  DumbDisplay dumbdisplay(new DDWiFiServerIO(WIFI_SSID, WIFI_PASSWORD), DD_DEF_SEND_BUFFER_SIZE, 2 * DD_DEF_IDLE_TIMEOUT);
#else
  #include "dumbdisplay.h"
  DumbDisplay dumbdisplay(new DDInputOutput());
#endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In WiFi case, normally, you will use the defaults for &lt;code&gt;sendBufferSize&lt;/code&gt; (2nd parameter) and &lt;code&gt;idleTimeout&lt;/code&gt; (3rd parameter). However for this program, since a large amount of data might be shipped to DumbDisplay app, the &lt;code&gt;idleTimeout&lt;/code&gt; better be longer than the default. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, several global helper objects / pointers are declared&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DDMasterResetPassiveConnectionHelper pdd(dumbdisplay, true);
GraphicalDDLayer* imageLayer;
JoystickDDLayer* frameSliderLayer;
SelectionDDLayer* frameSizeSelectionLayer;
...
BasicDDTunnel* generalTunnel;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The construction of &lt;code&gt;DDMasterResetPassiveConnectionHelper&lt;/code&gt; accepts two parameters&lt;br&gt;
1) The DumbDisplay object&lt;br&gt;
2) Whether to call &lt;code&gt;DumbDisplay::recordLayerCommands()&lt;/code&gt; / &lt;code&gt;DumbDisplay::playbackLayerCommands()&lt;/code&gt; before and after calling "initialize" lambda expression. The effect of calling &lt;code&gt;DumbDisplay::recordLayerCommands()&lt;/code&gt; / &lt;code&gt;DumbDisplay::playbackLayerCommands()&lt;/code&gt; is so the UI construction of the various layers is more smooth. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The life-cycle of the above DumbDisplay layers and "tunnels" are managed by the global &lt;code&gt;pdd&lt;/code&gt; object, which will monitor connection and disconnection of&lt;br&gt;
DumbDisplay app, calling appropriate C++ lambda expressions in the appropriate time. It is cooperatively given "time slices" in the &lt;code&gt;loop()&lt;/code&gt; block like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void loop() {
  pdd.loop([]() {
    initializeDD();
  }, []() {
    if (updateDD(!pdd.firstUpdated())) {
       saveSettings();
    }
  }, []() {
    deinitializeDD();
  });
  if (pdd.isIdle()) {
    handleIdle(pdd.justBecameIdle());
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pdd.loop()&lt;/code&gt; accepts 3 C++ lambda expressions -- &lt;code&gt;[]() { ... }&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;the 1st lambda expression is called when DumbDisplay app is connected, and DumbDisplay components need be initialized. Here &lt;code&gt;initializeDD()&lt;/code&gt; is called for the purpose.&lt;/li&gt;
&lt;li&gt;the 2nd lambda expression is called during DumbDisplay app is connected to update the DumbDisplay components. Here, &lt;code&gt;updatedDD()&lt;/code&gt; for the purpose. Note that &lt;code&gt;pdd.firstUpdated()&lt;/code&gt; tells if this 2nd lambda expression was previously called after DumbDisplay component initialization (hence not of that means first call)&lt;/li&gt;
&lt;li&gt;the 3rd optional lambda expression is called when DumbDisplay app is disconnected. Here, &lt;code&gt;deinitializeDD()&lt;/code&gt; is called for the purpose.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;After &lt;code&gt;pdd.loop()&lt;/code&gt; is the code that will be run whether DumbDisplay app is connected or not.&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;pdd.isIdle()&lt;/code&gt; tells if DumbDisplay is not connected (i.e. idle). In idle case, the function &lt;code&gt;handleIdle()&lt;/code&gt; is called. Note that &lt;code&gt;pdd.justBecameIdle()&lt;/code&gt; tells if DumbDisplay just disconnected making it idle.
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The first time when the ESP32-CAM is connected, ESP32-CAM's clock is synchronized with that of your phone via &lt;code&gt;generalTunnel&lt;/code&gt; -- a "general tunnel"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void initializeDD() {
  ...
  generalTunnel = dumbdisplay.createGeneralServiceTunnel();
  generalTunnel-&amp;gt;reconnectTo(DD_CONNECT_FOR_GET_DATE_TIME);  // ask DumbDisplay app for current time, in order to set ESP's clock
  ...
}
...
bool updateDD(bool isFirstUpdate) {
  ...
  if (generalTunnel-&amp;gt;pending()) {
    String response;
    if (generalTunnel-&amp;gt;readLine(response)) {
      const String&amp;amp; endPoint = generalTunnel-&amp;gt;getEndpoint();
      if (endPoint == DD_CONNECT_FOR_GET_DATE_TIME) {
        // got current time "feedback" from DumbDisplay app (initially requested)
        dumbdisplay.log("- got sync time " + response);
        DDDateTime dateTime;
        DDParseGetDataTimeResponse(response, dateTime, &amp;amp;tzMins);
        dumbdisplay.log("  . tz_mins=" + String(tzMins));
        Esp32SetDateTime(dateTime);
        ...
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that syncing and setting of the ESP32-CAM's clock is only done once when the ESP32-CAM connects with your phone. Also note that the ESP32-CAM's clock will keep running during sleep.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Other than the main scene / state, the UI has other scenes, like for selecting snapshots to save, and for transferring "offline" snapshots. When switching between the different scenes, the UI layers will be re-auto-pined by calling &lt;code&gt;pinLayers&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void pinLayers(State forState) {
  if (forState == STREAMING) {
    dumbdisplay.configAutoPin(DDAutoPinConfig('V')
      .beginGroup('H')
        .addLayer(frameSizeSelectionLayer)
        ...
        .addLayer(saveButtonLayer)
      .endGroup()  
      .build(), true);
  } else if (forState == CONFIRM_SAVE_SNAP) {
    dumbdisplay.configAutoPin(DDAutoPinConfig('V')
      .beginGroup('H')
        .addLayer(imageLayer)
        ...
      .endGroup()
      .build(), true);
  } else if (forState == TRANSFER_OFFLINE_SNAPS) {
    dumbdisplay.configAutoPin(DDAutoPinConfig('V')
      .addLayer(imageLayer)
      .addLayer(progressLayer)
      .addLayer(cancelButtonLayer)
      .build(), true);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The last parameter of &lt;code&gt;configAutoPin&lt;/code&gt; is &lt;code&gt;autoShowHideLayers&lt;/code&gt;.  Which if &lt;code&gt;true&lt;/code&gt;, auto-pinning will automatically hide all other not-involved layers. This relieves the need to explicitly hide all layers not involved in different scenes of the UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Selection and prompting for custom frame rate might be worth highlighting here. It is accomplished with the &lt;code&gt;customFrameRateSelectionLayer&lt;/code&gt; layer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    fb = customFrameRateSelectionLayer-&amp;gt;getFeedback();
    if (fb != NULL) {
      if (fb-&amp;gt;type == CUSTOM) {
        int phFrameRate = fb-&amp;gt;text.isEmpty() ? customCachePHFrameRate : fb-&amp;gt;text.toInt();
        if (phFrameRate &amp;gt;= 1 &amp;amp;&amp;amp; phFrameRate &amp;lt;= 3600) {
          customCachePHFrameRate = phFrameRate;
          ...
        } else {
          dumbdisplay.log("invalid custom frame rate!", true);
        }
      } else {
        customFrameRateSelectionLayer-&amp;gt;explicitFeedback(0, 0, "'per-hour' frame rate; e.g. " + String(customCachePHFrameRate) , CUSTOM, "numkeys");
      }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;normally the type of "feedback" (&lt;code&gt;fb-&amp;gt;type&lt;/code&gt;) from the UI will not be &lt;code&gt;CUSTOM&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;in such a non-&lt;code&gt;CUSTOM&lt;/code&gt; case (i.e. "feedback" is because of clicking), call &lt;code&gt;customFrameRateSelectionLayer-&amp;gt;explicitFeedback&lt;/code&gt; like above

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;explicitFeedback&lt;/code&gt; will explicitly fake a "feedback" to the layer&lt;/li&gt;
&lt;li&gt;but this time, the type of "feedback" is &lt;code&gt;CUSTOM&lt;/code&gt; -- the 4th parameter&lt;/li&gt;
&lt;li&gt;the first 3 parameters are for &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;text&lt;/code&gt; of the "feedback"&lt;/li&gt;
&lt;li&gt;the last parameters -- option to &lt;code&gt;explicitFeedback&lt;/code&gt; &lt;code&gt;numkeys&lt;/code&gt; -- means that during the "feedback" routing, you will be prompted for numeric value with your phone's virtual keyboard, which will then replace the text of the "feedback"&lt;/li&gt;
&lt;li&gt;note that the initial &lt;code&gt;text&lt;/code&gt; passed in to &lt;code&gt;explicitFeedback&lt;/code&gt; is used as the "hint" on the virtual keyboard&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;in &lt;code&gt;CUSTOM&lt;/code&gt; case, &lt;code&gt;fb-&amp;gt;text&lt;/code&gt; will be the value you entered &lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Another opportunity of prompting is confirmation for transferring "offline" snapshots to your phone. It is accomplished via &lt;code&gt;generalTunnel&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool updateDD(bool isFirstUpdate) {
  ...
  if (generalTunnel-&amp;gt;pending()) {
    String response;
    if (generalTunnel-&amp;gt;readLine(response)) {
      const String&amp;amp; endPoint = generalTunnel-&amp;gt;getEndpoint();
      if (endPoint == DD_CONNECT_FOR_GET_DATE_TIME) {
        // got current time "feedback" from DumbDisplay app (initially requested)
        ...
        if (state == STREAMING &amp;amp;&amp;amp; offlineSnapCount &amp;gt; 0) {
          generalTunnel-&amp;gt;reconnectTo("confirm?title=Transfer Snaps&amp;amp;message=There are " + String(offlineSnapCount) + " offline snaps. Transfer them%3F&amp;amp;ok=Yes&amp;amp;cancel=No");
        }
#endif
      } else {
        if (state == STREAMING &amp;amp;&amp;amp; response == "Yes") {
          setStateToTransferOfflineSnaps();  // assume the response if from the "confirm" dialog [for transferring offline snaps]
        }
        ...
      }
    }
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Right after gotten the "sync time" from DumbDisplay app, and when need to prompt for "yes/no" confirmation for transferring "offline" snaps, &lt;code&gt;generalTunnel-&amp;gt;reconnectTo&lt;/code&gt; is called to handle the "confirm" request.&lt;/li&gt;
&lt;li&gt;The "Yes" or "No" response will come back through &lt;code&gt;generalTunnel&lt;/code&gt; again (the same way as &lt;code&gt;DD_CONNECT_FOR_GET_DATE_TIME&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the "offline" transfer, an "alert" is popped up indicating the transfer is done. This is done by calling &lt;code&gt;dumbdispaly.alert&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool updateDD(bool isFirstUpdate) {
  ...
  if (state == TRANSFER_OFFLINE_SNAPS) {
    if (offlineSnapCount == 0 || clickedCancelButton) {
      ...
      dumbdisplay.alert("Transferred " + String(startTransferOfflineSnapCount - offlineSnapCount) + " offline snaps!");
      ...
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Build and Upload
&lt;/h1&gt;

&lt;p&gt;Build, upload the sketch and try it out! &lt;/p&gt;

&lt;p&gt;If it interests you, you may use a Serial monitor (set to baud-rate 115200) to observe when things are happening&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initialize offline snap storage ...
... offlineSnapCount=3 ...
... existing STORAGE ...
    $ total: 896 KB
    $ used: 180 KB
    $ free: 716 KB (79.91%)
... offline snap storage ...
    - startOfflineSnapIdx=0
    - offlineSnapCount=3
... offline snap storage initialized
bluetooth address: 84:0D:8E:D2:90:EE
*** just became idle ... millis=1067 ***
bluetooth address: 84:0D:8E:D2:90:EE
bluetooth address: 84:0D:8E:D2:90:EE
bluetooth address: 84:0D:8E:D2:90:EE
...
**********
* _SendBufferSize=128
**********
*** just connected ... millis=7133 ***
- got sync time 2024-09-07-10-17-41-+0800
  . tz_mins=480
Initializing camera ...
... initialized camera!
...
*** just became idle ... millis=93898(Saturday, September 07 2024 10:19:07+0800) ***
bluetooth address: 84:0D:8E:D2:90:EE
! written offline snap to [/off_000.jpg] ... size=48.39KB
bluetooth address: 84:0D:8E:D2:90:EE
bluetooth address: 84:0D:8E:D2:90:EE
...
*** going to sleep ... millis=153898(Saturday, September 07 2024 10:20:07+0800) ***
...
Initialize offline snap storage ...
... offlineSnapCount=2 ...
... existing STORAGE ...
    $ total: 896 KB
    $ used: 112 KB
    $ free: 784 KB (87.50%)
... offline snap storage ...
    - startOfflineSnapIdx=0
    - offlineSnapCount=2
... offline snap storage initialized
*** woke up for offline snap ... millis=542(Saturday, September 07 2024 10:20:33+0800) ***
! written offline snap to [/off_002.jpg] ... size=78.91KB
*** going back to sleep ... timeout=27.20s ***
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;p&gt;Hope that you will have fun with it! Enjoy!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=pHbXvHb8zV0" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6hcubigvi6ibxilazlc.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>esp32cam</category>
      <category>snapshots</category>
    </item>
    <item>
      <title>Simple Arduino Framework Photo Frame Implementation with Photos Downloaded from the Internet via DumbDisplay</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Mon, 08 Jul 2024 10:06:04 +0000</pubDate>
      <link>https://forem.com/trevorwslee/simple-arduino-framework-photo-frame-implementation-with-photos-downloaded-from-the-internet-via-dumbdisplay-170o</link>
      <guid>https://forem.com/trevorwslee/simple-arduino-framework-photo-frame-implementation-with-photos-downloaded-from-the-internet-via-dumbdisplay-170o</guid>
      <description>&lt;h1&gt;
  
  
  Simple Arduino Framework Raspberry Pi Pico / ESP32 TFT LCD Photo Frame Implementation with Photos Downloaded from the Internet via DumbDisplay
&lt;/h1&gt;

&lt;p&gt;The target of this &lt;a href="https://github.com/trevorwslee/TFTImageShow" rel="noopener noreferrer"&gt;project&lt;/a&gt; is to implement, mostly the software part, a simple Arduino framework photos / images showing "photo frame" using Raspberry Pi Pico or ESP32 with photos / images downloaded from the Internet via DumbDisplay -- an Android app running on your Android phone.&lt;/p&gt;

&lt;p&gt;The microcontroller program here is developed in Arduino framework using VS Code and PlatformIO, in the similar fashion as described by the post -- &lt;a href="https://www.instructables.com/A-Way-to-Run-Arduino-Sketch-With-VSCode-PlatformIO/" rel="noopener noreferrer"&gt;A Way to Run Arduino Sketch With VSCode PlatformIO Directly&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simple remote UI for downloading photos / images from the Internet is realized with the help of the DumbDisplay Android app. For a brief description of DumbDisplay, you may want to refer to the post -- &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note that the UI is driven by the microcontroller program; i.e., the control flow of the UI is programmed in the sketch. Additionally, please note that the downloaded image, be it in &lt;strong&gt;Jpeg&lt;/strong&gt; or &lt;strong&gt;PNG&lt;/strong&gt; format, will be transferred to the microcontroller board in &lt;strong&gt;Jpeg&lt;/strong&gt; format, scaled to site inside the TFT LCD screen.&lt;/p&gt;

&lt;p&gt;For Raspberry Pi Pico board (WiFi), a ST7789 2.8 inch 240x320 SPI TFT LCD screen is attached to a Raspberry Pi Pico board.&lt;br&gt;
The TFT LCD module library used is the &lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt; Arduino library.&lt;/p&gt;

&lt;p&gt;For ESP32, LiLyGo TDisplay / TCamera Plus board is used. &lt;br&gt;
The TFT LCD module library use is the &lt;code&gt;bodmer/TFT_eSPI&lt;/code&gt; Arduino library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FTFTImageShow%2Fmain%2Fimgs%2Ftft_image_show.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FTFTImageShow%2Fmain%2Fimgs%2Ftft_image_show.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In all cases, the &lt;strong&gt;Jpeg&lt;/strong&gt; library used is the &lt;code&gt;bodmer/TJpg_Decoder&lt;/code&gt; Arduino library.&lt;/p&gt;

&lt;p&gt;A simple flash-based &lt;strong&gt;LittleFS&lt;/strong&gt; file-system is allocated for storing the saved &lt;strong&gt;Jpeg&lt;/strong&gt; images.&lt;/p&gt;

&lt;p&gt;The microcontroller board has two running modes:&lt;/p&gt;

&lt;p&gt;1) When connected to the DumbDisplay Android app (using WiFi), a simple UI is provided for downloading images from some predefined sites,&lt;br&gt;
   as well as for transferring the downloaded image in &lt;strong&gt;Jpeg&lt;/strong&gt; format to the microcontroller board.&lt;br&gt;
   Note that the predefined sites is hardcoded in the sketch that you can conveniently change as desired by changing the sketch.&lt;br&gt;
2) When not connected to the DumbDisplay Android app, the microcontroller cycles through the saved &lt;strong&gt;Jpeg&lt;/strong&gt; images displaying them to the TFT LCD &lt;br&gt;
   screen one by one like a simple "photo frame". Note that since the images are stored in &lt;strong&gt;LittleFS&lt;/strong&gt;, they will survive even after reboot of the&lt;br&gt;
   microcontroller board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Connect for the UI; disconnect to enjoy "photo frame" slide show.&lt;/em&gt;&lt;/strong&gt;    &lt;/p&gt;
&lt;h1&gt;
  
  
  The UI
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2FUI_00.png"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FTFTImageShow%2Fmain%2Fimgs%2FUI_01.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first time connected, an image download will be initiated automatically.&lt;br&gt;
After downloading an image the image will be transferred to the microcontroller board in &lt;strong&gt;Jpeg&lt;/strong&gt; format and be displayed to the TFT LCD screen.&lt;/p&gt;

&lt;p&gt;You can choose to save the transferred image by clicking the &lt;strong&gt;&lt;em&gt;💾Save&lt;/em&gt;&lt;/strong&gt; button.&lt;br&gt;
Notice that the [7-segment] &lt;em&gt;number&lt;/em&gt; displayed next to the &lt;strong&gt;&lt;em&gt;Saved🗂️&lt;/em&gt;&lt;/strong&gt; label will be bumped up after saving.&lt;br&gt;
The &lt;em&gt;number&lt;/em&gt; indicates the number of images saved to the microcontroller's &lt;strong&gt;LittleFS&lt;/strong&gt; storage.&lt;/p&gt;

&lt;p&gt;If you so desired, you can turn on auto-save by clicking the &lt;strong&gt;&lt;em&gt;Auto&lt;/em&gt;&lt;/strong&gt; save button. (You turn off auto-save by clicking the button again.)&lt;/p&gt;

&lt;p&gt;If you want to initiate another download of image, click on the canvas that shows the downloaded image.&lt;/p&gt;

&lt;p&gt;If you want to delete all the saved images, double-click on the [7-segment] &lt;em&gt;number&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After you are done with downloading and saving images, disconnect the microcontroller.&lt;br&gt;
After disconnection, the "photo frame" slide show begins on the microcontroller side.&lt;/p&gt;

&lt;p&gt;Anytime you want to change the saved images, reconnect to DumbDisplay Android app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Connect for the UI; disconnect to enjoy "photo frame" slide show.&lt;/em&gt;&lt;/strong&gt;    &lt;/p&gt;
&lt;h1&gt;
  
  
  Wiring TFT LCD Module
&lt;/h1&gt;

&lt;p&gt;For LiLyGo TDisplay / TCamera Plus board, the TFT LCD screen is in-built the microcontroller board; hence, no need for additional wiring.&lt;/p&gt;

&lt;p&gt;For Raspberry Pi Pico board, as mentioned previously, a ST7789 2.8 inch 240x320 SPI TFT LCD module is used; hence, some wiring is necessary&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FTFTImageShow%2Fmain%2Fimgs%2FTFT_WIRE.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FTFTImageShow%2Fmain%2Fimgs%2FTFT_WIRE.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Raspberry Pi Pico&lt;/th&gt;
&lt;th&gt;SPI TFT LCD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3V3&lt;/td&gt;
&lt;td&gt;VCC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP21&lt;/td&gt;
&lt;td&gt;BL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP17&lt;/td&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP16&lt;/td&gt;
&lt;td&gt;RS / DC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP18&lt;/td&gt;
&lt;td&gt;CLK / SCLK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP19&lt;/td&gt;
&lt;td&gt;SDA / MOSI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP20&lt;/td&gt;
&lt;td&gt;RST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h1&gt;
  
  
  Developing and Building
&lt;/h1&gt;

&lt;p&gt;As mentioned previously, the sketch will be developed using VS Code and PlatformIO.&lt;br&gt;
Please clone the PlatformIO project &lt;a href="https://github.com/trevorwslee/TFTImageShow" rel="noopener noreferrer"&gt;TFTImageShow&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;The configurations for developing and building of the sketch in basically captured in the &lt;code&gt;platformio.ini&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[env]
monitor_speed = 115200

[env:PICOW]  ; ensure long file name support ... git config --system core.longpaths true
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = rpipicow
framework = arduino
board_build.core = earlephilhower
board_build.filesystem = littlefs
board_build.filesystem_size = 1m
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
    https://github.com/adafruit/Adafruit-ST7735-Library.git
    https://github.com/adafruit/Adafruit-GFX-Library
    https://github.com/Bodmer/TJpg_Decoder.git 
    Wire
    SPI
    https://github.com/adafruit/Adafruit_BusIO 
build_flags =
    -D FOR_PICOW

[env:TDISPLAY]
platform = espressif32
board = esp32dev
framework = arduino
board_build.filesystem = littlefs
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
    bodmer/TFT_eSPI     ; Setup25_TTGO_T_Display
    bodmer/TJpg_Decoder
    LittleFS
build_flags =
    -D FOR_TDISPLAY

[env:TCAMERAPLUS]
platform = espressif32
board = esp32dev
framework = arduino
board_build.filesystem = littlefs
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
    bodmer/TFT_eSPI      ; modify User_Setup_Select.h ... Setup44_TTGO_CameraPlus
    bodmer/TJpg_Decoder
    LittleFS
    Wire
    SPI
    SPIFFS
build_flags =
    -D FOR_TCAMERAPLUS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Please make sure you select the correct PlatformIO project environment&lt;/em&gt;&lt;/strong&gt; -- &lt;code&gt;PICOW&lt;/code&gt; / &lt;code&gt;TDISPLAY&lt;/code&gt; / &lt;code&gt;TCAMERAPLUS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;PICOW&lt;/code&gt;, the platform core is download from &lt;code&gt;https://github.com/maxgerhardt/platform-raspberrypi.git&lt;/code&gt;.&lt;br&gt;
(As far as I know, this is the only PlatformIO platform core that supports the use of Raspberry Pi PicoW WiFi capability.)&lt;/p&gt;

&lt;p&gt;It might take a long time for PlatformIO to download and install it.&lt;br&gt;
If PlatformIO fails to download and install the platform core, it might be that your system doesn't have long "file name" enabled, in such a case, try&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git config --system core.longpaths true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;TDISPLAY&lt;/code&gt; that uses &lt;code&gt;bodmer/TFT_eSPI&lt;/code&gt;, you will need to modify the installed &lt;code&gt;.pio/libdeps/TDISPLAY/TFT_eSPI/User_Set_Select.h&lt;/code&gt;&lt;br&gt;
to use &lt;code&gt;User_Setups/Setup25_TTGO_T_Display.h&lt;/code&gt; rather than the default &lt;code&gt;User_Setup.h&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
//#include &amp;lt;User_Setup.h&amp;gt;           // Default setup is root library folder
...
#include &amp;lt;User_Setups/Setup25_TTGO_T_Display.h&amp;gt;    // Setup file for ESP32 and TTGO T-Display ST7789V SPI bus TFT
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;TCAMERAPLUS&lt;/code&gt; which also uses &lt;code&gt;bodmer/TFT_eSPI&lt;/code&gt;, modify &lt;code&gt;User_Set_Select.h&lt;/code&gt; similarly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
//#include &amp;lt;User_Setup.h&amp;gt;           // Default setup is root library folder
...
#include &amp;lt;User_Setups/Setup44_TTGO_CameraPlus.h&amp;gt;   // Setup file for ESP32 and TTGO T-CameraPlus ST7789 SPI bus TFT    240x240
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program entry point is &lt;code&gt;src/main.cpp&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ***
// the below _secret.h just define macros like:
// #define WIFI_SSID           "your-wifi-ssid"
// #define WIFI_PASSWORD       "your-wifi-password"
// ***
#include "_secret.h"

#include "tft_image_show/tft_image_show.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there are two &lt;strong&gt;included&lt;/strong&gt; files -- &lt;code&gt;_secret.h&lt;/code&gt; and &lt;code&gt;tft_image_show/tft_image_show.ino&lt;/code&gt; -- in the &lt;code&gt;src&lt;/code&gt; directory&lt;/p&gt;

&lt;p&gt;You will need to create the &lt;code&gt;_secret.h&lt;/code&gt; with content like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define WIFI_SSID           "your-wifi-ssid"
#define WIFI_PASSWORD       "your-wifi-password"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these macros for accessing your WiFi, the microcontroller board will connect to DumbDisplay Android app using WiFi.&lt;br&gt;
If you do not want to use WiFi, simply don't provide them.&lt;br&gt;
In such a case, connection to DumbDisplay Android app is assumed to be using serial UART (slower) via an OTG adapter.&lt;br&gt;
Please refer to the above mentioned post -- &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  The Sketch
&lt;/h1&gt;

&lt;p&gt;The sketch of the project is &lt;code&gt;tft_image_show/tft_image_show.ino&lt;/code&gt;. You can [easily] customize some aspects of the sketch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
// NEXT_S defines the delay (in seconds) to show next saved image
#define NEXT_S              5
...
// MAX_IMAGE_COUNT define that maximum number of images that can be saved
// set MAX_IMAGE_COUNT to 0 to force reformat the storage
#define MAX_IMAGE_COUNT     10
...
// getDownloadImageURL() returns a URL to download an image; add / remove sites as needed
// download image bigger than needed (on purpose)
const String urls[] = {
  String("https://loremflickr.com/") + String(2 * TFT_WIDTH) + String("/") + String(2 * TFT_HEIGHT),
  String("https://picsum.photos/") + String(2 * TFT_WIDTH) + String("/") + String(2 * TFT_HEIGHT),
};
const char* getDownloadImageURL() {
  int idx = random(2);
  return urls[idx].c_str();
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The slide show delay is defined by the macro &lt;code&gt;NEXT_S&lt;/code&gt;, which default to 5 seconds&lt;/li&gt;
&lt;li&gt;The maximum number of saved images is defined by the macro &lt;code&gt;MAX_IMAGE_COUNT&lt;/code&gt;, which default to 10.
Note that if you set &lt;code&gt;MAX_IMAGE_COUNT&lt;/code&gt; to 0, flash and run the sketch, the &lt;strong&gt;LittleFS&lt;/strong&gt; storage will be reformatted.
For normal running, &lt;code&gt;MAX_IMAGE_COUNT&lt;/code&gt; should be at lease 1.&lt;/li&gt;
&lt;li&gt;You can modify &lt;code&gt;urls&lt;/code&gt; / &lt;code&gt;getDownloadImageURL()&lt;/code&gt; to add / remove Internet sites for downloading images.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Sketch Highlight -- TFT LCD Library &lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Here is how &lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt; used in the sketch.&lt;/p&gt;

&lt;p&gt;First a global &lt;code&gt;tft&lt;/code&gt; object is defined like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #define A_TFT_BL    21
  #define A_TFT_CS    17
  #define A_TFT_DC    16
  #define A_TFT_SCLK  18
  #define A_TFT_MOSI  19
  #define A_TFT_RST   20
  #define TFT_WIDTH   320
  #define TFT_HEIGHT  240
  #include &amp;lt;Adafruit_ST7789.h&amp;gt;
  Adafruit_ST7789 tft(A_TFT_CS, A_TFT_DC, A_TFT_RST);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the pin assignments exactly match the wiring described previously.&lt;/p&gt;

&lt;p&gt;Then in &lt;code&gt;setup()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  pinMode(A_TFT_BL, OUTPUT);
  digitalWrite(A_TFT_BL, 1);  // light it up
  tft.init(240, 320, SPI_MODE0);
  tft.invertDisplay(false);
  tft.setRotation(1);
  tft.setSPISpeed(40000000);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The back-light part is obvious.&lt;/li&gt;
&lt;li&gt;The TFT LCD screen size is 240x320.&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;SPI_MODE0&lt;/code&gt; and other settings? Simply, they work for me. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Sketch Highlight -- TFT LCD Library &lt;code&gt;bodmer/TFT_eSPI&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Here is how &lt;code&gt;bodmer/TFT_eSPI&lt;/code&gt; used in the sketch.&lt;/p&gt;

&lt;p&gt;First, a global &lt;code&gt;tft&lt;/code&gt; object is defined like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  #include &amp;lt;TFT_eSPI.h&amp;gt;
  TFT_eSPI tft = TFT_eSPI();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;setup()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  tft.init();
  tft.setRotation(0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Sketch Highlight -- Jpeg Library &lt;code&gt;bodmer/TJpg_Decoder&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;You might be wondering why use &lt;strong&gt;Jpeg&lt;/strong&gt; but not RGB565 directly. Simply because of the very high data compression ratio of &lt;strong&gt;Jpeg&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Anyway, here is how &lt;code&gt;bodmer/TJpg_Decoder&lt;/code&gt; used in the sketch.&lt;/p&gt;

&lt;p&gt;Include the needed headers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;TJpg_Decoder.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;setup()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#if defined(TFT_ESPI_VERSION)
  TJpgDec.setSwapBytes(true);
#endif
  TJpgDec.setCallback(tft_output);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why &lt;code&gt;setSwapBytes(true)&lt;/code&gt;? Since it seems to work that way.&lt;/p&gt;

&lt;p&gt;And here is the &lt;em&gt;callback&lt;/em&gt; &lt;code&gt;tft_output&lt;/code&gt;, which is mostly copied from an example of &lt;code&gt;bodmer/TJpg_Decoder&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) {
  // Stop further decoding as image is running off bottom of screen
  if ( y &amp;gt;= tft.height() ) return 0;
  // This function will clip the image block rendering automatically at the TFT boundaries
#if defined(TFT_ESPI_VERSION)
  tft.pushRect(x, y, w, h, bitmap);
#else
  tft.drawRGBBitmap(x, y, bitmap, w, h);
#endif
  // Return 1 to decode next block
  return 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that in case of using &lt;code&gt;bodmer/TFT_eSPI&lt;/code&gt;, the way to draw the decoded &lt;strong&gt;Jpeg&lt;/strong&gt; chunk is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  tft.pushRect(x, y, w, h, bitmap);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;Adafruit-ST7735-Library&lt;/code&gt; is use, the way to draw the decoded &lt;strong&gt;Jpeg&lt;/strong&gt; chunk is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  tft.drawRGBBitmap(x, y, bitmap, w, h);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After setting &lt;code&gt;TJpgDec&lt;/code&gt; up, a &lt;strong&gt;Jpeg&lt;/strong&gt; image can be drawn like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  TJpgDec.drawJpg(x, y, jpegImage.bytes, jpegImage.byteCount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Sketch Highlight -- &lt;strong&gt;LittleFS&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Include the needed headers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;FS.h&amp;gt;
#include &amp;lt;LittleFS.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;setup()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; LittleFS.begin();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to format the &lt;strong&gt;LittleFS&lt;/strong&gt;, call &lt;code&gt;format()&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  LittleFS.format()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Jpeg&lt;/strong&gt; image can be saved like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  File f = LittleFS.open(fileName, "w");
  if (f) {
    f.println(currentJpegImage.width);
    f.println(currentJpegImage.height);
    f.println(currentJpegImage.byteCount);
    f.write(currentJpegImage.bytes, currentJpegImage.byteCount);
    f.close();
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that not only the &lt;strong&gt;Jpeg&lt;/strong&gt; image bytes got written to the file, the various metadata are saved first. (I.e. the file is not a normal &lt;strong&gt;JPG&lt;/strong&gt; file, but a customized one.)&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;Jpeg&lt;/strong&gt; image can be read back like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  File f = LittleFS.open(fileName, "r");
  if (f) {
    int width = f.readStringUntil('\n').toInt();
    int height = f.readStringUntil('\n').toInt();
    int byteCount = f.readStringUntil('\n').toInt();
    uint8_t* bytes = new uint8_t[byteCount];
    f.readBytes((char*) bytes, byteCount);
    f.close();
    tempImage.width = width;
    tempImage.height = height;
    tempImage.byteCount = byteCount;
    tempImage.bytes = bytes;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Sketch Highlight -- DumbDisplay
&lt;/h1&gt;

&lt;p&gt;Like all other use cases of using DumbDisplay, you first declare a global &lt;code&gt;DumbDisplay&lt;/code&gt; object &lt;code&gt;dumbdisplay&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#if defined(WIFI_SSID)
  #include "wifidumbdisplay.h"
  DumbDisplay dumbdisplay(new DDWiFiServerIO(WIFI_SSID, WIFI_PASSWORD));
#else
  #include "dumbdisplay.h"
  DumbDisplay dumbdisplay(new DDInputOutput());
#endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, the new configured &lt;code&gt;DDWiFiServerIO&lt;/code&gt; is one of the few ways to establish connection with DumbDisplay Android app.&lt;br&gt;
Like in "no WiFi" case, Serial UART &lt;code&gt;DDInputOutput&lt;/code&gt; is used.&lt;/p&gt;

&lt;p&gt;Then, several global helper objects / pointers are declared&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DDMasterResetPassiveConnectionHelper pdd(dumbdisplay);
GraphicalDDLayer* imageLayer;
LcdDDLayer* saveButtonLayer;
LcdDDLayer* autoSaveOptionLayer;
LcdDDLayer* savedCountLabelLayer;
SevenSegmentRowDDLayer* savedCountLayer;
SimpleToolDDTunnel* webImageTunnel;
ImageRetrieverDDTunnel* imageRetrieverTunnel = NULL;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;DDMasterResetPassiveConnectionHelper&lt;/code&gt; global object &lt;code&gt;pdd&lt;/code&gt; is a helper for managing connect and reconnection with DumbDisplay app.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;GraphicalDDLayer&lt;/code&gt; pointer &lt;code&gt;imageLayer&lt;/code&gt; is the &lt;code&gt;canvas&lt;/code&gt; to which downloaded image is drawn to.
Like other layers, they the layer is created later, in this case, when DumbDisplay app is created.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;LcdDDLayer&lt;/code&gt; pointers &lt;code&gt;saveButtonLayer&lt;/code&gt;, &lt;code&gt;autoSaveOptionLayer&lt;/code&gt; and &lt;code&gt;savedCountLabelLayer&lt;/code&gt; are for &lt;strong&gt;save&lt;/strong&gt; button, &lt;em&gt;auto-save&lt;/em&gt; button, and &lt;em&gt;saved-count&lt;/em&gt; label respectively.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;SevenSegmentRowDDLayer&lt;/code&gt; pointer &lt;code&gt;savedCountLayer&lt;/code&gt; is for show the saved image count.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;SimpleToolDDTunnel&lt;/code&gt; pointer &lt;code&gt;webImageTunnel&lt;/code&gt; is for download image to DumbDisplay Android app purpose. It will be created together with other layers and &lt;code&gt;tunnels&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ImageRetrieverDDTunnel&lt;/code&gt; pointer &lt;code&gt;imageRetrieverTunnel&lt;/code&gt; is for retrieving the data of the downloaded image, in &lt;strong&gt;Jpeg&lt;/strong&gt; format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The life-cycle of the above DumbDisplay layers and "tunnels" are managed by the global &lt;code&gt;pdd&lt;/code&gt; object, which will monitor connection and disconnection of&lt;br&gt;
DumbDisplay app, calling appropriate user-defined functions as well as DumbDisplay functions in the appropriate time. It is cooperatively given "time slices" in the &lt;code&gt;loop()&lt;/code&gt; block like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  void loop() {
    ...
    pdd.loop(initializeDD, updateDD);
    ...
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;initializeDD&lt;/code&gt; is the function defined in the sketch that is supposed to create the various layer and tunnel objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void initializeDD() {
  tft.fillScreen(COLOR_BG);
  // create a graphical layer for drawing the downloaded web image to
  imageLayer = dumbdisplay.createGraphicalLayer(2 * TFT_WIDTH, 2 * TFT_HEIGHT);
  ...
  // create a LCD layer for the save button
  saveButtonLayer = dumbdisplay.createLcdLayer(6, 2);
  ...
  // create a LCD layer for the auto save option
  autoSaveOptionLayer = dumbdisplay.createLcdLayer(6, 1);
  ...
  // create a LCD layer as the label for the number of saved images
  savedCountLabelLayer = dumbdisplay.createLcdLayer(8, 1);
  ...
  // create a 7-segment layer for showing the number of saved images
  savedCountLayer = dumbdisplay.create7SegmentRowLayer(2);
  ...
  // create a tunnel for downloading web image ... initially, no URL yet ... downloaded.png is the name of the image to save
  webImageTunnel = dumbdisplay.createImageDownloadTunnel("", "downloaded.png");
  ...
  // create a tunnel for retrieving JPEG image data from DumbDisplay app storage
  imageRetrieverTunnel = dumbdisplay.createImageRetrieverTunnel();
  // auto pin the layers
  dumbdisplay.configAutoPin(DDAutoPinConfig('V')
    .addLayer(imageLayer)
    .beginGroup('H')
      ...
    .endGroup()
    .build());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;updateDD&lt;/code&gt; is the function define in the sketch that is supposed to receive "time slices" to update / act-on the layer and tunnel objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  bool isFirstUpdate = !pdd.firstUpdated();
  bool updateUI = isFirstUpdate;
  if (autoSaveOptionLayer-&amp;gt;getFeedback() != NULL) {
    // toggle auto save
    autoSave = !autoSave;
    updateUI = true;
  }
  if (updateUI) {
    if (autoSave) {
      autoSaveOptionLayer-&amp;gt;writeLine("Auto✅️"); 
    } else {
      autoSaveOptionLayer-&amp;gt;writeLine("Auto⛔");
    }
  }
  ...
  if (isFirstUpdate || state == NOTHING) {
    if (isFirstUpdate || imageLayer-&amp;gt;getFeedback() != NULL) {
      // trigger download image
      saveButtonLayer-&amp;gt;disabled(true);
      imageLayer-&amp;gt;noBackgroundColor();
      state = DOWNLOADING_FOR_IMAGE;
    }
    return;
  }
  if (state == DOWNLOADING_FOR_IMAGE) {
    // set the URL to download web image 
    currentJpegImage.release();
    String url = getDownloadImageURL();
    webImageTunnel-&amp;gt;reconnectTo(url);
    imageLayer-&amp;gt;clear();
    imageLayer-&amp;gt;write("downloading image ...");
    state = WAITING_FOR_IMAGE_DOWNLOADED;
    return;
  }
  if (state == WAITING_FOR_IMAGE_DOWNLOADED) {
    int result = webImageTunnel-&amp;gt;checkResult();
    if (result == 1) {
      // web image downloaded ... retrieve JPEG data of the image
      imageRetrieverTunnel-&amp;gt;reconnectForJpegImage("downloaded.png", TFT_WIDTH, TFT_HEIGHT);
      imageLayer-&amp;gt;clear();
      imageLayer-&amp;gt;drawImageFileFit("downloaded.png");
      state = RETRIEVING_IMAGE;
      retrieveStartMillis = millis();
    } else if (result == -1) {
      // failed to download the image
      imageLayer-&amp;gt;clear();
      imageLayer-&amp;gt;write("!!! failed to download image !!!");
      dumbdisplay.writeComment("XXX failed to download XXX");
      state = NOTHING;
    }
    return;
  }
  if (state == RETRIEVING_IMAGE) {
    // read the retrieve image (if it is available)
    DDJpegImage jpegImage;
    bool retrievedImage = imageRetrieverTunnel-&amp;gt;readJpegImage(jpegImage);
    if (retrievedImage) {
      unsigned long retrieveTakenMillis = millis() - retrieveStartMillis;
      dumbdisplay.writeComment(String("* ") + jpegImage.width + "x" + jpegImage.height + " (" + String(jpegImage.byteCount / 1024.0) + " KB) in " + String(retrieveTakenMillis / 1000.0) + "s");
      if (jpegImage.isValid()) {
        ...
      } else {
        ...
      }
      ...
      state = NOTHING;
    }   
    return
  }  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how layer "feedback" (e.g. clicking) is received using &lt;code&gt;getFeedback()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;how the &lt;code&gt;webImageTunnel&lt;/code&gt; "tunnel" is used to download image with call to &lt;code&gt;reconnectTo()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;how the &lt;code&gt;imageRetrieverTunnel&lt;/code&gt; "tunnel" is used to initiate retrieving of download image data with call to &lt;code&gt;reconnectForJpegImage()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;how the image data (&lt;code&gt;DDJpegImage&lt;/code&gt;) is received via the tunnel &lt;code&gt;imageRetrieverTunnel&lt;/code&gt; with call to &lt;code&gt;readJpegImage()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole &lt;code&gt;updateDD&lt;/code&gt; basically is a "state-machine" that handles the different states (&lt;code&gt;state&lt;/code&gt;) of the UI processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NOTHING&lt;/code&gt; -- just started, or finished download / saving of image; will wait for &lt;code&gt;imageLayer&lt;/code&gt; being clicked to initiate an image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DOWNLOADING_FOR_IMAGE&lt;/code&gt; -- this state could have been merged with previous stage; anyway, it reconnects &lt;code&gt;webImageTunnel&lt;/code&gt; to activate an image download&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WAITING_FOR_IMAGE_DOWNLOADED&lt;/code&gt;  -- waiting for download image to complete; then will retrieve the download image to be displayed to the TFT LCD screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RETRIEVING_IMAGE&lt;/code&gt; -- retrieving the download image data to be transferred to the microcontroller; once retrieved, display the image to the TFT LCD screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The slide show is carried out when "idle" (not connected to DumbDisplay app)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void loop() {
  pdd.loop(initializeDD, updateDD);
  if (pdd.isIdle()) {
    if (pdd.justBecameIdle()) {
      // re-start slide show
      ...
    }
    unsigned long now = millis();
    if (now &amp;gt;= nextMillis) {
      if (MAX_IMAGE_COUNT &amp;gt; 0 &amp;amp;&amp;amp; savedImageCount &amp;gt; 0) {
        ...
      } else {
        ...
      }
      ...
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Build and Upload
&lt;/h1&gt;

&lt;p&gt;Build, upload the sketch and try it out! &lt;/p&gt;

&lt;p&gt;For WiFi connectivity, you will need to find out the IP address of the microcontroller board. Simply connect the microcontroller board with a Serial monitor (set to use baud rate 115200), you should see lines like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;binded WIFI TrevorWireless
listening on 192.168.0.218:10201 ...
listening on 192.168.0.218:10201 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See that the IP address of the microcontroller board is printed out. &lt;/p&gt;

&lt;p&gt;On DumbDisplay Android app side&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You will need the microcontroller's IP address to configure DumbDisplay Android app to connect it with your microcontroller&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FTFTImageShow%2Fmain%2Fimgs%2FDD_WIFI.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Start the DumbDisplay app.&lt;/li&gt;
&lt;li&gt;Click on the Establish Connection icon.&lt;/li&gt;
&lt;li&gt;In the "establish connection" dialog, you should see the "add WIFI device" icon at the bottom right of the dialog. Click on it.&lt;/li&gt;
&lt;li&gt;A popup for you to enter WIFI IP will be shown. Enter the IP address of your ESP board as Network Host. Click OK when done.&lt;/li&gt;
&lt;li&gt;Back to the "establish connection" dialog, a new entry will be added, click on it to establish WIFI connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have fun with it!&lt;/p&gt;

&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>raspberrypipico</category>
      <category>esp32</category>
      <category>spitftlcd</category>
    </item>
    <item>
      <title>Implement a Simple Calculator Android App by Reusing Logics in Rust via JavaScript-WASM Interfacing</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Tue, 14 May 2024 16:33:25 +0000</pubDate>
      <link>https://forem.com/trevorwslee/implement-a-simple-calculator-android-app-by-reusing-logics-in-rust-via-javascript-wasm-interfacing-3915</link>
      <guid>https://forem.com/trevorwslee/implement-a-simple-calculator-android-app-by-reusing-logics-in-rust-via-javascript-wasm-interfacing-3915</guid>
      <description>&lt;h1&gt;
  
  
  Implement a Simple Calculator Android App by Reusing Logics in Rust via JavaScript-WASM Interfacing
&lt;/h1&gt;

&lt;p&gt;As a followup of my previous work -- &lt;a href="https://github.com/trevorwslee/wasm_calculator"&gt;Implement a Simple WASM Calculator in Rust Using Leptos, and with DumbCalculator&lt;/a&gt; --&lt;br&gt;
this time, I would like to explore a &lt;em&gt;not-elegant-but-work-for-me&lt;/em&gt; way to reuse the logics implemented in Rust, without going through the trouble rewriting the core using Kotlin.&lt;/p&gt;

&lt;p&gt;The idea is to use JavaScript as the "bridge" between the Android app and the WASM Rust code, which is &lt;br&gt;
largely realized with the help of &lt;code&gt;DumbCalculator&lt;/code&gt; of &lt;a href="https://crates.io/crates/rusty_dumb_tools"&gt;rusty_dumb_tools&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The binding of Rust (WASM) and JavaScript is realized with the help of &lt;code&gt;wasm-bindgen&lt;/code&gt; and &lt;code&gt;wasm-pack&lt;/code&gt; --
&lt;a href="https://github.com/rustwasm/wasm-bindgen/tree/main/examples/without-a-bundler"&gt;https://github.com/rustwasm/wasm-bindgen/tree/main/examples/without-a-bundler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You can refer to &lt;a href="https://rustwasm.github.io/docs/wasm-pack/quickstart.html"&gt;&lt;code&gt;wasm-pack&lt;/code&gt; Quickstart&lt;/a&gt; for instruction on installing &lt;code&gt;wasm-pack&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For Android app to interact with JavaScript (web page), Android's &lt;code&gt;WebView&lt;/code&gt; is the key enabler&lt;/li&gt;
&lt;li&gt;You can refer to &lt;a href="https://developer.android.com/studio/install"&gt;Install Android Studio&lt;/a&gt; for instruction on installing &lt;strong&gt;&lt;em&gt;Android Studio&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Other than &lt;strong&gt;&lt;em&gt;Android Studio&lt;/em&gt;&lt;/strong&gt;, &lt;a href="https://code.visualstudio.com/download"&gt;VSCode&lt;/a&gt; is used for developing the Rust code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Starting the Project
&lt;/h2&gt;

&lt;p&gt;Since it will be an Android App -- &lt;code&gt;ACalculatorApp&lt;/code&gt; -- naturally, we will create an &lt;strong&gt;&lt;em&gt;Android Studio&lt;/em&gt;&lt;/strong&gt; project for it.&lt;br&gt;
Moreover, inside the project, we will be creating a little Rust project for developing the JavaScript "bridge".&lt;/p&gt;

&lt;p&gt;Simply, create an &lt;strong&gt;&lt;em&gt;Android Studio&lt;/em&gt;&lt;/strong&gt; project &lt;code&gt;ACalculatorApp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SW2xrliH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/new_project.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SW2xrliH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/new_project.png" width="771" height="562"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Initialize for the JavaScript-Rust "Bridge"
&lt;/h2&gt;

&lt;p&gt;To start coding for the JavaScript-Rust "bridge":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open the created folder &lt;code&gt;ACalculatorApp&lt;/code&gt; with &lt;strong&gt;&lt;em&gt;VSCode&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the folder &lt;code&gt;rust&lt;/code&gt; (inside &lt;code&gt;ACalculatorApp&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the folder &lt;code&gt;rust&lt;/code&gt;, create &lt;code&gt;Cargo.toml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[package]
name = "dumb_calculator"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.92"
rusty_dumb_tools = "0.1.13"

[dependencies.web-sys]
version = "0.3.4"
    features = [
    'Document',
    'Element',
    'HtmlElement',
    'Node',
    'Window',
]
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Note that in order for &lt;code&gt;wasm-bindgen&lt;/code&gt; to work, the following configurations are required&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;crate-type = ["cdylib"]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[dependencies.web-sys]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wasm-bindgen = "0.2.92"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the folder &lt;code&gt;rust&lt;/code&gt;, create &lt;code&gt;src/lib.rc&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn get_greeting(who: String) -&amp;gt; String {
    format!("Hello, {}!", who)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the folder &lt;code&gt;rust&lt;/code&gt;, create &lt;code&gt;simple.html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script type="module"&amp;gt;
  import init, { get_greeting } from './pkg/dumb_calculator.js';
  async function load() {
      await init();
      window.get_greeting = get_greeting;
  }
  load();
&amp;lt;/script&amp;gt;
&amp;lt;button onclick="document.getElementById('msg').innerText+=get_greeting('World')"&amp;gt;GREET&amp;lt;/button&amp;gt;
&amp;lt;div id="msg"&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Try build the Rust code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open a terminal to &lt;code&gt;rust&lt;/code&gt; and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  wasm-pack build --target web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate the output folders &lt;code&gt;target&lt;/code&gt; and &lt;code&gt;pkg&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start &lt;strong&gt;&lt;em&gt;Live Server&lt;/em&gt;&lt;/strong&gt; VSCode extension

&lt;ul&gt;
&lt;li&gt;visit localhost:5501/rust/simple.html&lt;/li&gt;
&lt;li&gt;click the &lt;code&gt;GREET&lt;/code&gt; button&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TnIE4B2l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/try_bridge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TnIE4B2l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/try_bridge.png" width="800" height="246"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An alternative is to use Python's &lt;code&gt;http.server&lt;/code&gt;; in &lt;code&gt;rust&lt;/code&gt;, open a terminal and run
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  python -m http.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;visit localhost:8000/simple.html&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways of the JavaScript-WASM Bridge
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rust functions are exposed simply by annotating them like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn get_greeting(who: String) -&amp;gt; String {
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cargo.toml&lt;/code&gt; requires some special specifications 

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;crate-type = ["cdylib"]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[dependencies.web-sys]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wasm-bindgen = "0.2.92"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Building not with &lt;code&gt;cargo&lt;/code&gt;, but with &lt;code&gt;wasm-pack&lt;/code&gt; like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wasm-pack build --target web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;HTML page that loads the WASM Rust exposed needs be in "module" like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script type="module"&amp;gt;
  import init, { get_greeting } from './pkg/dumb_calculator.js';
  async function load() {
      await init();
      window.get_greeting = get_greeting;
  }
  load();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;load()&lt;/code&gt; async function, which calls &lt;code&gt;init&lt;/code&gt; generated&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;load()&lt;/code&gt; invoked explicitly as the last thing of the &lt;em&gt;module&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;after &lt;em&gt;load&lt;/em&gt;, assign the exposed -- &lt;code&gt;get_greeting&lt;/code&gt; in this case -- to &lt;code&gt;window&lt;/code&gt; so that it can be accessed outside of the "module"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Android Calling the JavaScript-WASM "Bridge"
&lt;/h2&gt;

&lt;p&gt;The key enabled is an Android &lt;code&gt;WebView&lt;/code&gt;. With Jetpack Compose, it can be programmed like&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;as in &lt;code&gt;simple&lt;/code&gt; package&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val ENDPOINT: String = "http://192.168.0.17:8000/simple.html"
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SimpleBridgeWebView()
        }
    }
}
@Composable
fun SimpleBridgeWebView(modifier: Modifier = Modifier) {
    AndroidView(
        factory = { context -&amp;gt;
            WebView(context).apply {
                this.settings.javaScriptEnabled = true
                this.webViewClient = WebViewClient()
                this.loadUrl(ENDPOINT)
            }
        },
        update = {}
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WebView&lt;/code&gt; is wrapped with an &lt;code&gt;AndroidView&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;javaScriptEnabled&lt;/code&gt;, but currently no JavaScript is involved&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loadUrl&lt;/code&gt; is called to load the "bridge" (web page) when the &lt;code&gt;WebView&lt;/code&gt; is created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Important&lt;/em&gt;&lt;/strong&gt; Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you should change the IP and port in &lt;code&gt;ENDPOINT&lt;/code&gt; to yours&lt;/li&gt;
&lt;li&gt;you may get into "firewall" issue; if so, be suggested to try to use Python's &lt;code&gt;http.server&lt;/code&gt; to serve the "bridge"
since very likely your Python installation already has firewall access setup &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More &lt;strong&gt;&lt;em&gt;importantly&lt;/em&gt;&lt;/strong&gt;, modify Android permission settings in &lt;code&gt;AndroidManifest.xml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow access to the Internet:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;manifest ...&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.INTERNET" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;allow &lt;code&gt;WebView&lt;/code&gt; "clear text" traffic
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;application ...
    android:usesCleartextTraffic="true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reminder: &lt;a href="https://github.com/trevorwslee/ACalculatorApp"&gt;this repository&lt;/a&gt; already includes the above code in the &lt;code&gt;simple&lt;/code&gt; package, you can use that &lt;code&gt;MainActivity&lt;/code&gt; simply by changing &lt;code&gt;AndroidManifest.xml&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...
    &amp;lt;activity
        android:name=".simple.MainActivity"
        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and run the Android app, and see that the "bridge" loads and is working&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IWC11xXw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/an_try_bridge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IWC11xXw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/an_try_bridge.png" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Package the "Bridge" with the App
&lt;/h2&gt;

&lt;p&gt;It is possible to package the "bridge" in the app's PKG. To do so, we will need to put everything of the "bridge" to the &lt;code&gt;assets&lt;/code&gt; folder like&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Android Studio&lt;/th&gt;
&lt;th&gt;VSCode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZTRuaGP5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/v1/imgs/assets.png" width="" height=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VNVa975r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/code_assets.png" width="358" height="472"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To copy the "bridge" over to &lt;code&gt;assets&lt;/code&gt;, after building it by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wasm-pack build --target web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;additionally run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ../app/src/main/assets
mkdir ../app/src/main/assets/bridge
cp bridge.html simple.html ../app/src/main/assets/bridge/
cp -r pkg ../app/src/main/assets/bridge/pkg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to make things easier, create a &lt;code&gt;rust/build.sh&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -ex
wasm-pack build --target web
cp *.html ../app/src/main/assets/bridge/
cp -r pkg ../app/src/main/assets/bridge/pkg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, every time want to build the "bridge", in &lt;code&gt;rust&lt;/code&gt; run &lt;code&gt;build.sh&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;As for the Android app side, somethings need be changed&lt;br&gt;
(&lt;em&gt;as in &lt;code&gt;internal&lt;/code&gt; package&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SimpleInternBridgeWebView()
        }
    }
}
@Composable
fun SimpleInternBridgeWebView(modifier: Modifier = Modifier) {
    AndroidView(
        factory = { context -&amp;gt;
            val assetLoader = WebViewAssetLoader.Builder()
                .addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(context))
                .build()
            WebView(context).apply {
                this.settings.javaScriptEnabled = true
                this.webViewClient = object : WebViewClient() {
                    override fun shouldInterceptRequest(
                        view: WebView,
                        request: WebResourceRequest
                    ): WebResourceResponse? {
                        return assetLoader.shouldInterceptRequest(request.url)
                    }
                }
                this.loadUrl("https://appassets.androidplatform.net/assets/bridge/simple.html")
            }
        },
        update = {}
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;assetLoader&lt;/code&gt; is used to act as a web server that serves web contents from &lt;code&gt;assets&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shouldInterceptRequest&lt;/code&gt; is intercepted to call &lt;code&gt;assetLoader&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the URL is now like &lt;code&gt;https://appassets.androidplatform.net/assets/bridge/simple.html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, build and run the Android app, and see that the "bridge" loads from &lt;code&gt;assets&lt;/code&gt; and is working&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Some Jetpack Compose Code to Call &lt;code&gt;get_greeting&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first thing to realize is that we need to get hold of the &lt;code&gt;WebView&lt;/code&gt; in order to be able to call its special tailored methods&lt;/p&gt;

&lt;p&gt;To achieve this, we need to refactor the code a bit so that the &lt;code&gt;WebView&lt;/code&gt; is created explicitly outside of &lt;code&gt;SimpleInternBridgeWebView&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    setContent {
        val webView = createSimpleInternBridgeWebView(this)
        SimpleInternBridgeWebView(webView)
    }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;WebView&lt;/code&gt; (&lt;code&gt;webView&lt;/code&gt;), we call its &lt;code&gt;evaluateJavascript&lt;/code&gt; method to invoke the "bridge" asynchronously like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    webView.evaluateJavascript("get_greeting('Android')") {
        greeting.value = it
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the code&lt;br&gt;
(&lt;em&gt;as in &lt;code&gt;internal_ui&lt;/code&gt; package&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val webView = createSimpleInternBridgeWebView(this)
            Column() {
                val greeting = remember { mutableStateOf("...") }
                SimpleInternBridgeWebView(webView)
                Text(text = greeting.value)
                Button(onClick = {
                    webView.evaluateJavascript("get_greeting('Android')") {
                        greeting.value = it
                    }
                }) {
                    Text("Get Greeting")
                }
            }
        }
    }
}
@Composable
fun SimpleInternBridgeWebView(webView: WebView, modifier: Modifier = Modifier) {
    AndroidView(
        factory = { context -&amp;gt; webView },
        update = { webView.loadUrl("https://appassets.androidplatform.net/assets/bridge/simple.html") }
    )
}
fun createSimpleInternBridgeWebView(context: Context): WebView {
    val assetLoader = WebViewAssetLoader.Builder()
        .addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(context))
        .build()
    return WebView(context).apply {
        this.settings.javaScriptEnabled = true
        this.webViewClient = object : WebViewClient() {
            override fun shouldInterceptRequest(
                view: WebView,
                request: WebResourceRequest
            ): WebResourceResponse? {
                return assetLoader.shouldInterceptRequest(request.url)
            }
        }
        this.loadUrl("https://appassets.androidplatform.net/assets/bridge/simple.html")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fsNbgf5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/from_an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fsNbgf5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/from_an.png" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lets Bring Some Calculator Code into the Picture
&lt;/h2&gt;

&lt;p&gt;As mentioned previously, the Android App will be a simple calculator with core implementation in Rust with the help of &lt;code&gt;DumbCalculator&lt;/code&gt;. Now, lets bring &lt;code&gt;DumbCalculator&lt;/code&gt; into the picture and make change to the code &lt;code&gt;lib.rs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use wasm_bindgen::prelude::*;
use std::{cell::RefCell, mem::{self, MaybeUninit}, sync::Once};
use rusty_dumb_tools::calculator::DumbCalculator;

#[wasm_bindgen]
pub fn get_greeting(who: String) -&amp;gt; String {
    format!("Hello, {}!", who)
}

#[wasm_bindgen]
struct Calculator {
    display_width: usize,
    calculator: DumbCalculator,
}
#[wasm_bindgen]
impl Calculator {
    pub fn new(display_width: u8) -&amp;gt; Calculator {
        Calculator {
            display_width: display_width as usize,
            calculator: DumbCalculator::new(),
        }
    }
    pub fn push(&amp;amp;mut self, key: &amp;amp;str) {
        self.calculator.push(key).unwrap();
    }
    pub fn get_display(&amp;amp;self) -&amp;gt; String {
        self.calculator.get_display_sized(self.display_width)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now, we have a &lt;code&gt;Calculator&lt;/code&gt; class to expose&lt;/li&gt;
&lt;li&gt;Again, in order to expose, simply annotate the &lt;code&gt;struct&lt;/code&gt; as well as the &lt;code&gt;impl&lt;/code&gt; with &lt;code&gt;#[wasm_bindgen]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we use another "bridge" -- &lt;code&gt;simple_calculator.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script type="module"&amp;gt;
  import init, { Calculator } from './pkg/dumb_calculator.js';
  async function load() {
      await init();
      window.Calculator = Calculator;
  }
  load();
&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
  function new_calc() {
    calc = Calculator.new(5);
    _sync_calc();
  }
  function _sync_calc() {
    let display = calc.get_display();
    let elem = document.getElementById('msg');
    elem.innerText = display;
  }
&amp;lt;/script&amp;gt;
&amp;lt;div id="buttons"&amp;gt;
  &amp;lt;button onclick="new_calc()"&amp;gt;new&amp;lt;/button&amp;gt;
  &amp;lt;button onclick="calc.push('1'); _sync_calc();"&amp;gt;1&amp;lt;/button&amp;gt;
  &amp;lt;button onclick="calc.push('+'); _sync_calc();"&amp;gt;+&amp;lt;/button&amp;gt;
  &amp;lt;button onclick="calc.push('2'); _sync_calc();"&amp;gt;2&amp;lt;/button&amp;gt;
  &amp;lt;button onclick="calc.push('='); _sync_calc();"&amp;gt;=&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id="msg"&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that in another &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block, some JavaScript functions are defined&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;new_calc()&lt;/code&gt; should be called to crate a new &lt;code&gt;Calculator&lt;/code&gt; instance, and assign it to the JavaScript variable &lt;code&gt;calc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;after creating a new &lt;code&gt;Calculator&lt;/code&gt; instance with &lt;code&gt;new_calc()&lt;/code&gt;, can call the methods of &lt;code&gt;Calculator&lt;/code&gt; like &lt;code&gt;calc.push('1')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_sync_calc()&lt;/code&gt; is there to synchronize the &lt;code&gt;Calcuator&lt;/code&gt;'s &lt;em&gt;display&lt;/em&gt; with the "msg" &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4KMytwlk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/try_bridge_calc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4KMytwlk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/try_bridge_calc.png" width="798" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it! Simply change the above &lt;code&gt;simple.html&lt;/code&gt; to &lt;code&gt;simple_calculator.html&lt;/code&gt;, like in &lt;code&gt;ENDPOINT&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val ENDPOINT: String = "http://192.168.0.17:8000/simple_calculator.html"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zZeNnfiQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/an_try_bridge_calc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zZeNnfiQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/an_try_bridge_calc.png" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It Appears that It Will Work!
&lt;/h2&gt;

&lt;p&gt;Nevertheless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Rust code file &lt;code&gt;lib.rs&lt;/code&gt; need be extended in order to expose more functionalities of &lt;code&gt;DumbCalculator&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The "bridge" is also extended, like in &lt;code&gt;bridge.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The complete &lt;code&gt;ACalculatorApp&lt;/code&gt; coding is quite involving, mostly due to UI coding&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Without going into all the details of the implementation, I hope this exploration at this point is already enjoyable.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7fwp_dl1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/ACalculatorApp/master/imgs/completed.png" width="800" height="1778"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Enjoy!
&lt;/h2&gt;

&lt;p&gt;You are more than welcome to clone the &lt;a href="https://github.com/trevorwslee/ACalculatorApp"&gt;GitHub repository&lt;/a&gt; and build and run the complete Android app yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>android</category>
      <category>webassembly</category>
      <category>rust</category>
    </item>
    <item>
      <title>GitHub Repository README to DEV Community Post</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Sat, 06 Apr 2024 02:03:37 +0000</pubDate>
      <link>https://forem.com/trevorwslee/github-repository-readme-to-dev-community-post-432m</link>
      <guid>https://forem.com/trevorwslee/github-repository-readme-to-dev-community-post-432m</guid>
      <description>&lt;h1&gt;
  
  
  GitHub Repository README to DEV Community  Post
&lt;/h1&gt;

&lt;p&gt;The enabler of the capability is a GitHub Action workflow contributed by the &lt;a href="https://github.com/sinedied/publish-devto"&gt;publish-devto&lt;/a&gt; project&lt;/p&gt;

&lt;p&gt;Hence, the picture&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V4YoM_Yp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/cat.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V4YoM_Yp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/cat.jpg" alt="cat.jpg" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;which is the sample that comes with the &lt;code&gt;publish-devto&lt;/code&gt; project. &lt;/p&gt;

&lt;p&gt;Here I will simply show how I setup GitHub repo for automatically posting README (&lt;code&gt;README.md&lt;/code&gt;) to DEV Community (&lt;code&gt;dev.to&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  On Dev Community Side
&lt;/h2&gt;

&lt;p&gt;As expected, you will need some "access token" from &lt;code&gt;dev.to&lt;/code&gt; for the workflow to work.&lt;/p&gt;

&lt;p&gt;Indeed, you need to get an API token from &lt;code&gt;dev.to&lt;/code&gt; -- &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Extension&lt;/code&gt; -&amp;gt; &lt;code&gt;DEV Community API Keys&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9oPf8nHQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/dev_api_key.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9oPf8nHQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/dev_api_key.png" alt="dev_api_key.png" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  On GitHub Side
&lt;/h2&gt;

&lt;p&gt;GitHub also requires some [repository] settings for security measures.&lt;/p&gt;

&lt;p&gt;Firstly, grant "read and write permissions" to "Workflow" -- &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Actions | General&lt;/code&gt; -&amp;gt; &lt;code&gt;Workflow permissions&lt;/code&gt; set to &lt;code&gt;Read and write permissions&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gVgurkrj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/rw_permit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gVgurkrj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/rw_permit.png" alt="rw_permit.png" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Secondly, setup &lt;code&gt;DEVTO_TOKEN&lt;/code&gt; secret (the API token you acquired in previous step) -- &lt;code&gt;Secret and variables | Actions&lt;/code&gt; new &lt;code&gt;Repository secrets&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xXIj6dMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/set_secret.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xXIj6dMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/set_secret.png" alt="set_secret.png" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It appears that the repository needs be a public one.&lt;/em&gt;&lt;/strong&gt; Otherwise, &lt;code&gt;dev.to&lt;/code&gt; will fail to acquire the images.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Repo Source Side
&lt;/h2&gt;

&lt;p&gt;Firstly, you will need &lt;code&gt;.github/workflows/publish.yml&lt;/code&gt; that you can download &lt;a href="https://github.com/trevorwslee/readme_to_devto/blob/main/.github/workflows/publish.yml"&gt;here&lt;/a&gt;, which is one that I tailored from the one that comes with the &lt;code&gt;publish-devto&lt;/code&gt; project&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;&lt;em&gt;importantly&lt;/em&gt;&lt;/strong&gt;, make sure that the &lt;code&gt;branch&lt;/code&gt; specified in &lt;code&gt;publish.yml&lt;/code&gt; is indeed the actual branch you want the action be triggered on&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
name: publish
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
...
        # (Optional) The git branch to use. Default is 'main'.
        branch: main
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secondly, your &lt;code&gt;README.md&lt;/code&gt; will need to have "headers" (starting from 1st line) like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: GitHub Repository README to DEV Community Post
description: Setup GitHub repo for automatically posting README to DEV Community  
tags: 'github, readme, devto'
cover_image: ./assets/cat.jpg
published: false
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes on the &lt;em&gt;headers&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; should be set correctly the first time, and should not be changed (unless you want to start a new post)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; is optional (i.e can be missing)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tags&lt;/code&gt; is optional, which is a list of comma-delimited single-words&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cover_image&lt;/code&gt; is optional&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;published&lt;/code&gt; should be &lt;code&gt;false&lt;/code&gt; until you want to publish the post&lt;/li&gt;
&lt;li&gt;the first successful trigger of the &lt;code&gt;publish&lt;/code&gt; workflow, &lt;code&gt;id&lt;/code&gt; will be added automatically (&lt;code&gt;README.md&lt;/code&gt; modified in the repo, and need be pulled)
without the &lt;code&gt;id&lt;/code&gt;, post will be treated as a new post&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Commit Repo
&lt;/h2&gt;

&lt;p&gt;That is it!&lt;/p&gt;

&lt;p&gt;Commit and push the changes to GitHub, you should see the running of the workflow &lt;code&gt;publish&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oeXA1WAa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/workflow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oeXA1WAa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/trevorwslee/readme_to_devto/main/assets/workflow.png" alt="workflow.png" width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the workflow is successful, a new draft post will be created on Dev Community (&lt;code&gt;dev.to&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Be reminded that the first time successful running of the workflow, &lt;code&gt;id&lt;/code&gt; will be automatically added to the "headers" of &lt;code&gt;README.md&lt;/code&gt;.&lt;/em&gt;&lt;/strong&gt; Pull for the addition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Hope this helps!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>github</category>
      <category>readme</category>
      <category>devto</category>
    </item>
    <item>
      <title>Arduino UNO R4 WiFi Experiments</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Fri, 05 Apr 2024 09:02:01 +0000</pubDate>
      <link>https://forem.com/trevorwslee/arduino-uno-r4-wifi-experiments-4ca9</link>
      <guid>https://forem.com/trevorwslee/arduino-uno-r4-wifi-experiments-4ca9</guid>
      <description>&lt;h1&gt;
  
  
  Arduino UNO R4 WiFi Experiments
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Farduino_uno_r4_wifi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Farduino_uno_r4_wifi.png" alt="arduino_uno_r4_wifi.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this &lt;a href="https://github.com/trevorwslee/UNOR4WiFiExperiments" rel="noopener noreferrer"&gt;project&lt;/a&gt;, I hope to demonstrate my Arduino UNO R4 WiFi experiments with the microcontroller board's LED matrix, starting from simply turning on/off each one of the LEDs, to having a simple remote UI for controlling the LEDs of the matrix, using an Android app -- DumbDisplay -- connected using the microcontroller board's WiFi support. &lt;/p&gt;

&lt;p&gt;The microcontroller programs here are Arduino sketches developed using VSCode with PlatformIO, in the similar fashion as described by the post -- &lt;a href="https://www.instructables.com/A-Way-to-Run-Arduino-Sketch-With-VSCode-PlatformIO/" rel="noopener noreferrer"&gt;A Way to Run Arduino Sketch With VSCode PlatformIO Directly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nevertheless, the sketches should be buildable with Arduino IDE.&lt;/p&gt;

&lt;h1&gt;
  
  
  PlatformIO Project for the Sketches
&lt;/h1&gt;

&lt;p&gt;A single PlatformIO project will be used for all the sketches here.&lt;/p&gt;

&lt;p&gt;Assuming you have created a PlatformIO project &lt;code&gt;UNOR4WiFiExperiments&lt;/code&gt; for the board &lt;code&gt;Arduino UNO R4 Wifi&lt;/code&gt; with the &lt;code&gt;Arduino&lt;/code&gt; framework.&lt;/p&gt;

&lt;p&gt;You should have the file &lt;code&gt;platformio.ini&lt;/code&gt; similar to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[env:UNOR4]
platform = renesas-ra
board = uno_r4_wifi
framework = arduino
monitor_speed = 115200
lib_deps =
    https://github.com/trevorwslee/Arduino-DumbDisplay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UNOR4&lt;/code&gt; is simply the name of the "PlatformIO Project Environment"&lt;/li&gt;
&lt;li&gt;The section &lt;code&gt;monitor_speed&lt;/code&gt; specifies the baud rate for the PlatformIO's Serial Monitor to be &lt;code&gt;115200&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The section &lt;code&gt;lib_deps&lt;/code&gt; specifies that the project will be depending on the &lt;a href="https://github.com/trevorwslee/Arduino-DumbDisplay" rel="noopener noreferrer"&gt;DumbDisplay Arduino Library&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The file &lt;code&gt;src/main.cpp&lt;/code&gt; should simply be the two lines like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;Arduino.h&amp;gt;
#include "INO/matrix_test/matrix_test.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, the first sketch will be &lt;code&gt;src/INO/matrix_test/matrix_test.ino&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Turning LED Matrix On/Off
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;src/INO/matrix_test/matrix_test.ino&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "Arduino_LED_Matrix.h"
ArduinoLEDMatrix matrix;
void setup() {
  Serial.begin(115200);
  matrix.begin();
}
const uint32_t happy[] = {
    0x19819,
    0x80000001,
    0x81f8000
};
const uint32_t heart[] = {
    0x3184a444,
    0x44042081,
    0x100a0040
};
void loop(){
  matrix.loadFrame(happy);
  delay(500);
  matrix.loadFrame(heart);
  delay(500);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is basically directly copied from the web page -- &lt;a href="https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/" rel="noopener noreferrer"&gt;Using the Arduino UNO R4 WiFi LED Matrix&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The basic initialization for using the LED matrix is like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  matrix.begin();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;To manipulate the LEDs of the matrix, a "frame" of 3 &lt;code&gt;uint32_t&lt;/code&gt;s is needed, as in &lt;code&gt;happy&lt;/code&gt; and &lt;code&gt;heart&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You load and display a "frame" like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  matrix.loadFrame(happy);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since each one of the 96 bits of the 3 &lt;code&gt;u_int32_t&lt;/code&gt;s corresponds to a LED of the 12x8 LED matrix, apparently, the LED on/off can be controlled by turning the proper bit on/off, with routine like the following &lt;code&gt;set_bit()&lt;/code&gt; which sets the corresponding bits of &lt;code&gt;frame&lt;/code&gt; on/off&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
uint32_t frame[] = { 0, 0, 0 };  // 3 32-bit unsigned ints can hold 96 bits
void set_bit(size_t bit, bool on) {
  int index = bit / 32;
  int offset = 31 - (bit % 32);
  if (on) {
    frame[index] |= (1 &amp;lt;&amp;lt; offset);
  } else {
    frame[index] &amp;amp;= ~(1 &amp;lt;&amp;lt; offset);
  }
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accordingly, let's try to turn on/off each of the LEDs one by one with sketch &lt;code&gt;src/INO/matrix_obo_test/matrix_obo_test.ino&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Turing the LEDs On/Off One-by-One
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;src/INO/matrix_obo_test/matrix_obo_test.ino&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "Arduino_LED_Matrix.h"
ArduinoLEDMatrix matrix;
uint32_t frame[] = { 0, 0, 0 };  // 3 32-bit unsigned ints can hold 96 bits
void set_bit(size_t bit, bool on) {
  int index = bit / 32;
  int offset = 31 - (bit % 32);
  if (on) {
    frame[index] |= (1 &amp;lt;&amp;lt; offset);
  } else {
    frame[index] &amp;amp;= ~(1 &amp;lt;&amp;lt; offset);
  }
}
void setup() {
  Serial.begin(115200);
  matrix.begin();
}
int bit = -1;  // the bit last turned on
void loop() {
  if (bit != -1) {
    // turn off last bit
    set_bit(bit, false);
  }
  // advance bit
  if (bit == -1) {
    bit = 0;
  } else {
    bit = (bit + 1) % 96;
  }
  // turn on bit
  set_bit(bit, true);
  // load the frame (bits)
  matrix.loadFrame(frame);
  delay(500);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that as it is looping through, the bit (turned on previously) is first turned off&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if (bit != -1) {
    // turn off last bit
    set_bit(bit, false);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the bit is advanced to next bit, and turned on&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // advance bit
  if (bit == -1) {
    bit = 0;
  } else {
    bit = (bit + 1) % 96;
  }
  // turn on bit
  set_bit(bit, true);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the "frame" is loaded and displayed on the LED matrix&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // load the frame (bits)
  matrix.loadFrame(frame);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build and upload the sketch above, you will need to modify &lt;code&gt;src/main.cpp&lt;/code&gt; as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;Arduino.h&amp;gt;
//#include "INO/matrix_test/matrix_test.ino"
#include "INO/matrix_obo_test/matrix_obo_test.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  A Simple Remote Virtual Joystick to Control the LEDs
&lt;/h1&gt;

&lt;p&gt;Let's do something, hopefully, more interesting -- use a Joystick to control the LEDs, but a virtual one, and remotely on your Android phone, with the help of DumbDisplay. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;For some little background on DumbDisplay, you may want to refer to the post &lt;a href="https://www.instructables.com/Blink-Test-With-Virtual-Display-DumbDisplay/" rel="noopener noreferrer"&gt;Blink Test With Virtual Display, DumbDisplay&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Note that the connection between Arduino UNO R4 WiFi and DumbDisplay Android app will be WIFI. Hence, you will first need to define macros for your WIFI credentials&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/INO/dd_joystick_test/secret.h&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define WIFI_SSID           "&amp;lt;WIFI-SSID&amp;gt;"
#define WIFI_PASSWORD       "&amp;lt;WIFI-password&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;src/INO/dd_joystick_test/dd_joystick_test.ino&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "Arduino_LED_Matrix.h"
#include "wifidumbdisplay.h"
#include "secret.h"
ArduinoLEDMatrix matrix;
uint32_t frame[] = { 0, 0, 0 };  // 3 uint32_t can hold 96 bits
void set_bit(size_t bit, bool on) {
  int index = bit / 32;
  int offset = 31 - (bit % 32);
  if (on) {
    frame[index] |= (1 &amp;lt;&amp;lt; offset);
  } else {
    frame[index] &amp;amp;= ~(1 &amp;lt;&amp;lt; offset);
  }
}
DumbDisplay dumbdisplay(new DDWiFiServerIO(WIFI_SSID, WIFI_PASSWORD));
JoystickDDLayer *joystickLayer;
const size_t JOYSTICK_SIZE = 240;
void setup() {
  Serial.begin(115200);
  matrix.begin();
  // create a joystick layer
  joystickLayer = dumbdisplay.createJoystickLayer(JOYSTICK_SIZE - 1);
  joystickLayer-&amp;gt;border(3, "darkblue", "round", 1);
  // turn on bit 0
  set_bit(0, true);
  matrix.loadFrame(frame);
}
int prev_bit = 0;
void loop() {
  const DDFeedback* fb = joystickLayer-&amp;gt;getFeedback();
  if (fb != NULL) {
    // got "feedback" (i.e. joystick moved)
    size_t x = int((fb-&amp;gt;x * 12) / (double) JOYSTICK_SIZE);
    size_t y = int((fb-&amp;gt;y * 8) / (double) JOYSTICK_SIZE);
    size_t bit = x + y * 12;
    if (bit != prev_bit) {
      dumbdisplay.writeComment("x: " + String(x) + " y: " + String(y) + " bit: " + String(bit));
      set_bit(prev_bit, false);
      set_bit(bit, true);
      matrix.loadFrame(frame);
      prev_bit = bit;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build and upload the sketch above, you will need to modify &lt;code&gt;src/main.cpp&lt;/code&gt; as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;Arduino.h&amp;gt;
//#include "INO/matrix_test/matrix_test.ino"
//#include "INO/matrix_obo_test/matrix_obo_test.ino"
#include "INO/dd_joystick_test/dd_joystick_test.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the sketch is running, please turn to the Serial Monitor. There you should see log lines like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listening on 192.168.0.2:10201 ...
listening on 192.168.0.2:10201 ...
listening on 192.168.0.2:10201 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which shows the network IP address (and port) on which your Arduino UNO R4 WiFi is listening.&lt;/p&gt;

&lt;p&gt;Your IP will likely be different than what shown above. Note down the IP address shown. You will need this IP for DumbDisplay WIFI connection. &lt;/p&gt;

&lt;p&gt;On your Android phone side&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start the DumbDisplay app.&lt;/li&gt;
&lt;li&gt;Click on the Establish Connection icon.&lt;/li&gt;
&lt;li&gt;In the "establish connection" dialog, you should see the "add WIFI device" icon at the bottom right of the dialog. Click on it.&lt;/li&gt;
&lt;li&gt;A popup for you to enter WIFI IP will be shown. Enter the IP address of your ESP board as Network Host. Click OK when done.&lt;/li&gt;
&lt;li&gt;Back to the "establish connection" dialog, a new entry will be added, click on it to establish WIFI connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once connected, you should see the virtual joystick displayed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Fdd_joystick.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Fdd_joystick.png" alt="dd_joystick.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving the joystick should move the turned on LED of your Arduino UNO R4 WiFi LED matrix&lt;/p&gt;

&lt;h1&gt;
  
  
  Another Simple Remote UI to Draw on the LED Matrix
&lt;/h1&gt;

&lt;p&gt;The sketch for the captioned UI is a bit longer, and hence will not be listed here. Instead, you can download the sketch &lt;code&gt;src/INO/dd_draw/dd_draw.ino&lt;/code&gt; &lt;a href="https://github.com/trevorwslee/UNOR4WiFiExperiments/blob/main/src/INO/dd_draw/dd_draw.ino" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alternatively, you can clone the complete repo &lt;a href="https://github.com/trevorwslee/UNOR4WiFiExperiments" rel="noopener noreferrer"&gt;UNOR4WiFiExperiments&lt;/a&gt; for all sketches mentioned&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I guess by now, you will realize that all you need to switch the PlatformIO project to target for another sketch is simply to change &lt;code&gt;src/main.cpp&lt;/code&gt;, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;Arduino.h&amp;gt;
//#include "INO/matrix_test/matrix_test.ino"
//#include "INO/matrix_obo_test/matrix_obo_test.ino"
//#include "INO/dd_joystick_test/dd_joystick_test.ino"
#include "INO/dd_draw/dd_draw.ino"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the resulting UI on DumbDisplay app will present you with a canvas for you to draw dots, which of course will be synchronized to your Arduino UNO R4 WiFi LED matrix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Fdd_draw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Fdd_draw.png" alt="dd_draw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLEAR&lt;/strong&gt; -- clear the dots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LOG&lt;/strong&gt; -- show the "frame" on the "terminal" of DumbDisplay app , e.g. &lt;code&gt;// {0x44444444,0x7c44444,0x44444444}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By default, DumbDisplay app will show the "commands" sent to it to the "terminal". You can turn this off with the menu "Show Commands"&lt;/li&gt;
&lt;li&gt;You can share the text on the "terminal" to other apps with the menu "Share Terminal Text"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the UI allows you to draw "frame" by "frame", with some work (maybe some hard work), you can create some basic animation to be displayed on the LED matrix, like the sketch &lt;code&gt;src/INO/frames/frames.ino&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "Arduino_LED_Matrix.h"
ArduinoLEDMatrix matrix;
void setup() {
  Serial.begin(115200);
  matrix.begin();
}
const uint32_t frames[][3] = {
  {0x20020020,0x2002002,0x200200},
  {0x20020020,0x3e02002,0x200200},
  {0x22022022,0x3e02202,0x20220220},
  {0x22422422,0x3e02202,0x20220220},
  {0x22422422,0x3e42242,0x24224224},
};
const size_t num_frames = sizeof(frames) / sizeof(frames[0]);
int frame_index = 0;  
void loop(){
  matrix.loadFrame(frames[frame_index]);
  delay(500);
  frame_index = (frame_index + 1) % num_frames;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Fhi_uno_r4_wifi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2FUNOR4WiFiExperiments%2Fmain%2Fimgs%2Fhi_uno_r4_wifi.gif" alt="hi_uno_r4_wifi.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Enjoy!
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>arduino</category>
      <category>platformio</category>
    </item>
    <item>
      <title>Implement a Simple WASM Calculator in Rust Using Leptos, and with DumbCalculator</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Thu, 04 Apr 2024 11:44:32 +0000</pubDate>
      <link>https://forem.com/trevorwslee/implement-a-simple-wasm-calculator-in-rust-using-leptos-and-with-dumbcalculator-2hd8</link>
      <guid>https://forem.com/trevorwslee/implement-a-simple-wasm-calculator-in-rust-using-leptos-and-with-dumbcalculator-2hd8</guid>
      <description>&lt;h1&gt;
  
  
  Implement a Simple WASM Calculator in Rust Using Leptos, and with DumbCalculator
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2Fwasm_calculator%2Fmaster%2Fwasm_calculator.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftrevorwslee%2Fwasm_calculator%2Fmaster%2Fwasm_calculator.png" alt="WASM Calculator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Environment
&lt;/h2&gt;

&lt;p&gt;Here I will assume program development tools like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Of course, the &lt;a href="https://www.rust-lang.org/tools/install" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; programming language itself.&lt;/li&gt;
&lt;li&gt;The popular &lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt; program development editor / IDE, with the extension 
&lt;a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer" rel="noopener noreferrer"&gt;rust-analyzer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Preferably the popular source control tool &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;GIT&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rust Crates Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://leptos.dev/" rel="noopener noreferrer"&gt;Leptos&lt;/a&gt; -- a Rust framework to develop WASM app in Rust.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.rs/rusty_dumb_tools/0.1.7/rusty_dumb_tools/calculator/struct.DumbCalculator.html" rel="noopener noreferrer"&gt;DumbCalculator&lt;/a&gt; of &lt;a href="https://github.com/trevorwslee/rusty_dumb_tools" rel="noopener noreferrer"&gt;rusty_dumb_tools&lt;/a&gt; -- a simple Rust component that acts like a simple physical calculator. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preparation for WASM Development
&lt;/h2&gt;

&lt;p&gt;WASM development in Rust can be enabled with the &lt;a href="https://trunkrs.dev/" rel="noopener noreferrer"&gt;Trunk&lt;/a&gt; tool.&lt;br&gt;
Indeed, Trunk is used here, and we install Trunk like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo install trunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing Trunk, we will also need to add the Rust target &lt;code&gt;wasm32-unknown-unknown&lt;/code&gt;, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rustup target add wasm32-unknown-unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kick-starting &lt;code&gt;wasm_calculator&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To get kick-started, create a new Rust project &lt;code&gt;wasm_calculator&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo new wasm_calculator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the just created folder &lt;code&gt;wasm_calculator&lt;/code&gt; with VSCode like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd wasm_calculator
code .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In VSCode, open and edit &lt;code&gt;Cargo.toml&lt;/code&gt; adding the necessary dependencies, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[dependencies]
leptos = { version = "0.6.5", features = ["csr"] }
rusty_dumb_tools = "0.1.8"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the Trunk config file &lt;code&gt;Trunk.toml&lt;/code&gt; with content like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[build]
target = "trunk.html"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;trunk.html&lt;/code&gt;, which is sort of the template for our final output &lt;code&gt;index.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;meta charset="UTF-8"&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that without the above mentioned &lt;code&gt;Trunk.toml&lt;/code&gt; config file, Trunk will in fact look for &lt;code&gt;index.html&lt;/code&gt; as the template instead.&lt;br&gt;
(However, we would like to reserve &lt;code&gt;index.html&lt;/code&gt; for other purposes, and hence would use &lt;code&gt;trunk.html&lt;/code&gt; instead.)&lt;/p&gt;

&lt;p&gt;Our WASM code will be "mounted" to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of this &lt;code&gt;trunk.html&lt;/code&gt;, let's see it working&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trunk serve --open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the Trunk server serving the &lt;code&gt;trunk.html&lt;/code&gt; merged with whatever WASM code in &lt;code&gt;main.rs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The server will keep running, and hot update the page whenever &lt;code&gt;trunk.html&lt;/code&gt; or &lt;code&gt;main.rs&lt;/code&gt; get changed&lt;/p&gt;

&lt;p&gt;Say, change the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of &lt;code&gt;trunk.html&lt;/code&gt; to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;&amp;lt;h3&amp;gt;&amp;amp;mdash; WASM Calculator &amp;amp;mdash;&amp;lt;/h3&amp;gt;&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See that the browser page is updated accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basis of &lt;code&gt;wasm_calculator&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The initially generated &lt;code&gt;main.rs&lt;/code&gt; is actually not WASM code to be "mounted" to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;br&gt;
To "mount" some simple WASM code (written in Rust), can change &lt;code&gt;main.rs&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use leptos::*;
fn main() {
    mount_to_body(move || {
        view! {
            &amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, our modification will be hot-deployed, and we should see that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is "mounted" to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, after &lt;code&gt;&amp;lt;h3&amp;gt;&amp;amp;mdash; WASM Calculator &amp;amp;mdash;&amp;lt;/h3&amp;gt;&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;Here is some little insights from the above code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mount_to_body&lt;/code&gt; is the function provided by Leptos to "mount" WASM code (written in Rust) to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mount_to_body&lt;/code&gt; can accept a closure which accepts no argument and returns the result of calling the &lt;code&gt;view!&lt;/code&gt; macro, which is of cause also provided by Leptos.&lt;/li&gt;
&lt;li&gt;Inside &lt;code&gt;view!&lt;/code&gt;, we write "HTML" -- like &lt;code&gt;&amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;&lt;/code&gt; -- which even looks like plain HTML, is in fact "legal" Rust code to be pre-processed by the macro &lt;code&gt;view!&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fact, normally, we will be coding our WASM code, in an &lt;code&gt;App()&lt;/code&gt; function, and "mount" it like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use leptos::*;
fn main() {
    mount_to_body(move || view! { &amp;lt;App/&amp;gt; });
}
fn App() -&amp;gt; impl IntoView {
    view! {
        &amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above, things inside &lt;code&gt;view!&lt;/code&gt; will be pre-processed to be transcribed to Rust code in compile time, hence, we should be able to include regular Rust code inside &lt;code&gt;view!&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
    let color = "red";
    let who = "World"; 
    view! {
        &amp;lt;div style={format!("color:{}", color)}&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown, we can enclose regular Rust code inside &lt;code&gt;{}&lt;/code&gt; as above &lt;code&gt;{format!("color:{}", color)}&lt;/code&gt; and &lt;code&gt;Hello, {who}!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can even "nest" &lt;code&gt;view!&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let color = "red";
  let who = "World"; 
  view! {
    {
      view! {
        &amp;lt;div style={format!("color:{}", color)}&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why "nest" &lt;code&gt;view!&lt;/code&gt;? Hopefully, it will become apparent in later sections. &lt;/p&gt;

&lt;p&gt;Let's extract the styling of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to live else-where. Indeed, we can put our CSS in &lt;code&gt;trunk.html&lt;/code&gt; and use it in &lt;code&gt;App()&lt;/code&gt; like&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trunk.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;style&amp;gt;
  .test-class {
    color: green;
  }
&amp;lt;/style&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.rs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
fn App() -&amp;gt; impl IntoView {
  let who = "World"; 
  view! {
    {
      view! {
        &amp;lt;div class="test-class"&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's add two buttons to it to make it interactive.&lt;/p&gt;

&lt;p&gt;Note that Leptos will generate HTML once only -- like the above &lt;code&gt;App()&lt;/code&gt; will only be called once to generate initial HTML code -- any updates are triggered with "signals", and rendered with closures (more about this later).&lt;/p&gt;

&lt;p&gt;Therefore, to make it interactive, not only we will need to add some interactive HTML elements, like &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, we will also need to make use of "signals" as well.&lt;/p&gt;

&lt;p&gt;First, let's add two &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;s, and be able to log to the browser's console when any of the button is clicked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use leptos::*;
use leptos::logging::log;
use web_sys::MouseEvent;
...
fn App() -&amp;gt; impl IntoView {
  let who = "World"; 
  let on_clicked = |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
  };
  view! {
    {
      view! {
        &amp;lt;div class="test-class"&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;I am 1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;I am 2&amp;lt;/button&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two more dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    use leptos::logging::log;
    use web_sys::MouseEvent;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The closure defined by &lt;code&gt;on_clicked&lt;/code&gt; will be called when any one of the button is clicked
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;button on:click=on_clicked value="1"&amp;gt;I am 1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;I am 2&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the two &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;s are placed in the same level as the "nested" &lt;code&gt;view!&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each button is associated with a value (e.g. &lt;code&gt;value="1"&lt;/code&gt;), and when a button is clicked, the associated value is retrieved and printed out to the browser's console
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's make use of "signal" to trigger update of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let value = clicked_value.get();
          format!("Hello, [{}]!", value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;I am 1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;I am 2&amp;lt;/button&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "signal" is created like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let (clicked_value, set_clicked_value) = create_signal(String::from(""));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;such a "signal" is composed of a "getter" &lt;code&gt;clicked_value&lt;/code&gt; and a "setter" &lt;code&gt;set_clicked_value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the type of the "signal" is &lt;code&gt;String&lt;/code&gt;, and the "signal" is initialized to the empty &lt;code&gt;String&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The "setter" &lt;code&gt;set_clicked_value&lt;/code&gt; is called to set new value in the closure defined by &lt;code&gt;on_click&lt;/code&gt;, which is called when any of the button is clicked
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let on_clicked = move |ev: MouseEvent| {
    ...
    set_clicked_value.set(value);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the closure captures variables, like &lt;code&gt;set_clicked_value&lt;/code&gt;, moved; and this is the requirement of using "signal"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "user" of the "signal" is the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; created by the nested &lt;code&gt;view!&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    move || view! {
      &amp;lt;div class="test-class"&amp;gt; {
        let value = clicked_value.get();
        format!("Hello, [{}]!", value)
      } &amp;lt;/div&amp;gt;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the "nested" &lt;code&gt;view&lt;/code&gt; is now a "moved" closure, since it is using the "signal" to get the new value set&lt;/li&gt;
&lt;li&gt;the value set is returned by calling &lt;code&gt;clicked_value.get()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;by using the "signal", Leptos knows that the closure need be called again to update the content of the nested &lt;code&gt;view!&lt;/code&gt; when the "signal" is updated

&lt;ul&gt;
&lt;li&gt;Here is what will happen when a button is clicked&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;the closure &lt;code&gt;on_click&lt;/code&gt; gets called&lt;/li&gt;

&lt;li&gt;the "signal" gets updated when &lt;code&gt;set_clicked_value&lt;/code&gt; is called&lt;/li&gt;

&lt;li&gt;the closure of the "nested" &lt;code&gt;view!&lt;/code&gt; gets called when the "signal" is updated, which will update the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; generated by the "nested" &lt;code&gt;view!&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Let's change the buttons to ones that simulate the key presses for &lt;code&gt;1 + 2 =&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
  &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
  &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
  &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And also add &lt;code&gt;DumbCalculator&lt;/code&gt; into the picture&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
use std::cell::RefCell;
use rusty_dumb_tools::calculator::*;
...
fn App() -&amp;gt; impl IntoView {
  let calculator_ref = RefCell::new(DumbCalculator::new());
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let mut calculator = calculator_ref.borrow_mut();
          let value = clicked_value.get();
          if !value.is_empty() {
            calculator.push(value.as_str()).unwrap();
          }
          let result_value = calculator.get_display_sized(10);
          format!("[{}]", result_value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An instance of &lt;code&gt;DumbCalculator&lt;/code&gt; is created and stored in a &lt;code&gt;RefCell&lt;/code&gt;, and assigned to &lt;code&gt;calculator_ref&lt;/code&gt;; note that even &lt;code&gt;calculator_ref&lt;/code&gt; is immutable, the instance of &lt;code&gt;DumbCalculator&lt;/code&gt; can be retrieved mutable
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let calculator_ref = RefCell::new(DumbCalculator::new());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that since &lt;code&gt;App()&lt;/code&gt; will only be called one, only a single instance of &lt;code&gt;DumbCalculator&lt;/code&gt; will ever be created, unless the browser page is refreshed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "nested" &lt;code&gt;view!&lt;/code&gt; code for the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is changed to something like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;div class="test-class"&amp;gt; {
      let mut calculator = calculator_ref.borrow_mut();
      let value = clicked_value.get();
      if !value.is_empty() {
        calculator.push(value.as_str()).unwrap();
      }
      let result_value = calculator.get_display_sized(10);
      format!("[{}]", result_value)
    } &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The instance of &lt;code&gt;DumbCalculator&lt;/code&gt; is "borrowed" mutable&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let mut calculator = calculator_ref.borrow_mut();
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The button value, which is supposed to simulate calculator key press, is pushed to the &lt;code&gt;DumbCalculator&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  calculator.push(value.as_str()).unwrap();
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What the calculator display should look like is rendered in the content of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let result_value = calculator.get_display_sized(10);
  format!("[{}]!", result_value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's add two buttons for "AC" (&lt;em&gt;all cancel&lt;/em&gt;) as well as "⬅" (&lt;em&gt;undo&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let calculator_ref = RefCell::new(DumbCalculator::new());
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let mut calculator = calculator_ref.borrow_mut();
          let value = clicked_value.get();
          if value == "ac" {
            calculator.reset();
          } else if value == "&amp;lt;" {
            calculator.undo();
          } else if !value.is_empty() {
            calculator.push(value.as_str()).unwrap();
          }
          let result_value = calculator.get_display_sized(10);
          format!("[{}]", result_value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button on:click=on_clicked value="ac"&amp;gt;AC&amp;lt;/button&amp;gt;
      &amp;lt;button on:click=on_clicked value="&amp;lt;"&amp;gt;{"⬅"}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The two additional buttons are added below the &lt;code&gt;1 + 2 =&lt;/code&gt; buttons like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;div&amp;gt;
      &amp;lt;button on:click=on_clicked value="ac"&amp;gt;AC&amp;lt;/button&amp;gt;
      &amp;lt;button on:click=on_clicked value="&amp;lt;"&amp;gt;{"⬅"}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Clicking of the two buttons are handled like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let value = clicked_value.get();
    if value == "ac" {
      calculator.reset();
    } else if value == "&amp;lt;" {
      calculator.undo();
    } else if !value.is_empty() {
      calculator.push(value.as_str()).unwrap();
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are getting closer and closer. Let's add to the code the capability of showing the history of the calculator.&lt;/p&gt;

&lt;p&gt;For this, we will be using another &lt;code&gt;history&lt;/code&gt; "signal".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let calculator_ref = RefCell::new(DumbCalculator::new());
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let (history, set_history) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let mut calculator = calculator_ref.borrow_mut();
          let value = clicked_value.get();
          if value == "ac" {
            calculator.reset();
          } else if value == "&amp;lt;" {
            calculator.undo();
          } else if !value.is_empty() {
            calculator.push(value.as_str()).unwrap();
          }
          let history = calculator.get_history_string(true);
          match &amp;amp;history {
            Some(hist) =&amp;gt; set_history.set(hist.to_string()),
            None =&amp;gt; set_history.set("".to_string()),  
          }
          let result_value = calculator.get_display_sized(10);
          format!("[{}]", result_value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button on:click=on_clicked value="ac"&amp;gt;AC&amp;lt;/button&amp;gt;
      &amp;lt;button on:click=on_clicked value="&amp;lt;"&amp;gt;{"⬅"}&amp;lt;/button&amp;gt;
      {move || view! {
        &amp;lt;span&amp;gt; { history.get() } &amp;lt;/span&amp;gt;
      }}
    &amp;lt;/div&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;history&lt;/code&gt; "signal" is added like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let (history, set_history) = create_signal(String::from(""));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;history&lt;/code&gt; if set after pushing value to the calculator
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let history = calculator.get_history_string(true);
    match &amp;amp;history {
      Some(hist) =&amp;gt; set_history.set(hist.to_string()),
      None =&amp;gt; set_history.set("".to_string()),  
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The updated &lt;code&gt;history&lt;/code&gt; is displayed next to the "⬅" button
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {move || view! {
      &amp;lt;span&amp;gt; { history.get() } &amp;lt;/span&amp;gt;
    }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;Finally, let's add all the whistles and bells and finish off our calculator ... &lt;/p&gt;

&lt;p&gt;Simply ... please replace the corresponding file as listed here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trunk.html&lt;/code&gt; -- &lt;a href="https://github.com/trevorwslee/wasm_calculator/blob/master/trunk.html" rel="noopener noreferrer"&gt;https://github.com/trevorwslee/wasm_calculator/blob/master/trunk.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main.rs&lt;/code&gt; -- &lt;a href="https://github.com/trevorwslee/wasm_calculator/blob/master/src/main.rs" rel="noopener noreferrer"&gt;https://github.com/trevorwslee/wasm_calculator/blob/master/src/main.rs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or ... simply ... clone the GitHub repo &lt;a href="https://github.com/trevorwslee/wasm_calculator" rel="noopener noreferrer"&gt;https://github.com/trevorwslee/wasm_calculator&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Manually Deploy to GitHub Pages
&lt;/h2&gt;

&lt;p&gt;Now that we have the final result WASM calculator, we may want to deploy it to GitHub Pages.&lt;/p&gt;

&lt;p&gt;Assuming a GitHub account, say like mine -- &lt;code&gt;trevorwslee&lt;/code&gt;, I can easily post some static pages to my GitHub Pages -- &lt;code&gt;https://trevorwslee.github.io/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What I needed is a GitHub repository with the "same" name &lt;code&gt;trevorwslee.github.io&lt;/code&gt; -- &lt;a href="https://github.com/trevorwslee/trevorwslee.github.io" rel="noopener noreferrer"&gt;https://github.com/trevorwslee/trevorwslee.github.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order for GitHub Pages to host our WASM Calculator app&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With Trunk, build &lt;em&gt;release&lt;/em&gt; version specifying the context to use in GitHub Pages, like mine

&lt;ul&gt;
&lt;li&gt;The context is &lt;code&gt;WASMCalculator&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then the URL to the WASM app is &lt;a href="https://trevorwslee.github.io/WASMCalculator/" rel="noopener noreferrer"&gt;https://trevorwslee.github.io/WASMCalculator/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;To build such &lt;em&gt;release&lt;/em&gt;, run Trunk like
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  trunk build --release --public-url "WASMCalculator"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;After building the &lt;em&gt;release&lt;/em&gt; with Trunk, the &lt;code&gt;dist&lt;/code&gt; folder will contain the files

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wasm_calculator-XXX.wasm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasm_calculator-XXX.js&lt;/code&gt;
to be checked in to &lt;code&gt;WASMCalculator&lt;/code&gt; folder of the GitHub Pages repository, like mine -- &lt;a href="https://github.com/trevorwslee/trevorwslee.github.io/tree/main/WASMCalculator" rel="noopener noreferrer"&gt;https://github.com/trevorwslee/trevorwslee.github.io/tree/main/WASMCalculator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;After a few moments, the WASM Calculator should be ready to try out -- &lt;a href="https://trevorwslee.github.io/WASMCalculator/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://trevorwslee.github.io/WASMCalculator/" rel="noopener noreferrer"&gt;https://trevorwslee.github.io/WASMCalculator/&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enjoy!
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webassembly</category>
      <category>rust</category>
      <category>leptos</category>
    </item>
    <item>
      <title>Implement a Simple WASM Calculator in Rust Using Leptos, and with DumbCalculator</title>
      <dc:creator>Trevor Lee</dc:creator>
      <pubDate>Tue, 20 Feb 2024 15:18:56 +0000</pubDate>
      <link>https://forem.com/trevorwslee/implement-a-simple-wasm-calculator-in-rust-using-leptos-and-with-dumbcalculator-42od</link>
      <guid>https://forem.com/trevorwslee/implement-a-simple-wasm-calculator-in-rust-using-leptos-and-with-dumbcalculator-42od</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj37cmkv3eqbvi8tjrch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj37cmkv3eqbvi8tjrch.png" alt="Image description" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Environment
&lt;/h2&gt;

&lt;p&gt;Here I will assume program development tools like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Of cause, the &lt;a href="https://www.rust-lang.org/tools/install"&gt;Rust&lt;/a&gt; programming language itself.&lt;/li&gt;
&lt;li&gt;The popular &lt;a href="https://code.visualstudio.com/download"&gt;VSCode&lt;/a&gt; program development editor / IDE, with the extension 
&lt;a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer"&gt;rust-analyzer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Preferably the popular source control tool &lt;a href="https://git-scm.com/downloads"&gt;GIT&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rust Crates Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://leptos.dev/"&gt;Leptos&lt;/a&gt; -- a Rust framework to develop WASM app in Rust&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.rs/rusty_dumb_tools/0.1.6/rusty_dumb_tools/calculator/struct.DumbCalculator.html"&gt;DumbCalculator&lt;/a&gt; of &lt;a href="https://github.com/trevorwslee/rusty_dumb_tools"&gt;rusty_dumb_tools&lt;/a&gt; -- a simple Rust component that acts like a simple physical calculator. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preparation for WASM Development
&lt;/h2&gt;

&lt;p&gt;WASM development in Rust can be enabled with the &lt;a href="https://trunkrs.dev/"&gt;Trunk&lt;/a&gt; tool.&lt;br&gt;
Indeed, Trunk is used here, and we install Trunk like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo install trunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing Trunk, we will also need to add the Rust target &lt;code&gt;wasm32-unknown-unknown&lt;/code&gt;, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rustup target add wasm32-unknown-unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kick-starting &lt;code&gt;wasm_calculator&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To get kick-started, create a new Rust project &lt;code&gt;wasm_calculator&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo new wasm_calculator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the just created folder &lt;code&gt;wasm_calculator&lt;/code&gt; with VSCode like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd wasm_calculator
code .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In VSCode, open and edit &lt;code&gt;Cargo.toml&lt;/code&gt; adding the necessary dependencies, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[dependencies]
leptos = { version = "0.6.5", features = ["csr"] }
rusty_dumb_tools = "0.1.7"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the Trunk config file &lt;code&gt;Trunk.toml&lt;/code&gt; with content like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[build]
target = "trunk.html"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;trunk.html&lt;/code&gt;, which is sort of the template for our final output &lt;code&gt;index.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;meta charset="UTF-8"&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that without the above mentioned &lt;code&gt;Trunk.toml&lt;/code&gt; config file, Trunk will in fact look for &lt;code&gt;index.html&lt;/code&gt; as the template instead.&lt;/p&gt;

&lt;p&gt;Our WASM code will be "mounted" to  &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of this &lt;code&gt;trunk.html&lt;/code&gt;, let's see it working&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trunk serve --open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the Trunk server serving the &lt;code&gt;trunk.html&lt;/code&gt; merged with whatever WASM code in &lt;code&gt;main.rs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The server will keep running, and hot update the page whenever &lt;code&gt;trunk.html&lt;/code&gt; or &lt;code&gt;main.rs&lt;/code&gt; changed&lt;/p&gt;

&lt;p&gt;Say, change the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of &lt;code&gt;trunk.html&lt;/code&gt; to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;&amp;lt;h3&amp;gt;&amp;amp;mdash; WASM Calculator &amp;amp;mdash;&amp;lt;/h3&amp;gt;&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See that the browser page is changed accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basis of &lt;code&gt;wasm_calculator&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The initially generated &lt;code&gt;main.rs&lt;/code&gt; is actually not WASM code to be "mounted" to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;br&gt;
To "mount" some simple WASM code (written in Rust), can change &lt;code&gt;main.rs&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use leptos::*;
fn main() {
    mount_to_body(move || {
        view! {
            &amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, our modification will be hot-deployed, and we should see that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is "mounted" to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, after &lt;code&gt;&amp;lt;h3&amp;gt;&amp;amp;mdash; WASM Calculator &amp;amp;mdash;&amp;lt;/h3&amp;gt;&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;Here is some little insights from the above code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mount_to_body&lt;/code&gt; is the function provided by Leptos to "mount" WASM code (written in Rust) to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mount_to_body&lt;/code&gt; can accept a closure which accepts no argument and returns the result of calling the &lt;code&gt;view!&lt;/code&gt; macro, which is of cause also provided by Leptos.&lt;/li&gt;
&lt;li&gt;Inside &lt;code&gt;view!&lt;/code&gt;, we write "HTML" -- like &lt;code&gt;&amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;&lt;/code&gt; -- which even looks like plain HTML, is in fact valid Rust code to be pre-processed by the macro &lt;code&gt;view!&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fact, normally, we will be coding our WASM code, in an &lt;code&gt;App()&lt;/code&gt; function, and "mount" it like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use leptos::*;
fn main() {
    mount_to_body(move || view! { &amp;lt;App/&amp;gt; });
}
fn App() -&amp;gt; impl IntoView {
    view! {
        &amp;lt;div style="color:red"&amp;gt;Hello, World!&amp;lt;/div&amp;gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above, things inside &lt;code&gt;view!&lt;/code&gt; will be pre-processed to be transcribed to Rust code in compile time, hence, we should be able to include regular Rust code inside &lt;code&gt;view!&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
    let color = "red";
    let who = "World"; 
    view! {
        &amp;lt;div style={format!("color:{}", color)}&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown, we can enclose regular Rust code inside &lt;code&gt;{}&lt;/code&gt; as above &lt;code&gt;{format!("color:{}", color)}&lt;/code&gt; and &lt;code&gt;Hello, {who}!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can even "nest" &lt;code&gt;view!&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let color = "red";
  let who = "World"; 
  view! {
    {
      view! {
        &amp;lt;div style={format!("color:{}", color)}&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why "nest" &lt;code&gt;view!&lt;/code&gt;? Hopefully, it will become apparent in later sections. &lt;/p&gt;

&lt;p&gt;Let's extract the styling of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to live else-where. Indeed, we can put our CSS in &lt;code&gt;trunk.html&lt;/code&gt; and use it in &lt;code&gt;App()&lt;/code&gt; like&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trunk.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;style&amp;gt;
  .test-class {
    color: green;
  }
&amp;lt;/style&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.rs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
fn App() -&amp;gt; impl IntoView {
  let who = "World"; 
  view! {
    {
      view! {
        &amp;lt;div class="test-class"&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's add two buttons to it to make it interactive.&lt;/p&gt;

&lt;p&gt;Note that Leptos will render HTML once only -- like the above &lt;code&gt;App()&lt;/code&gt; will only be called once to generate initial HTML code -- any updates are triggered with "signals", and rendered with closures (more about this later).&lt;/p&gt;

&lt;p&gt;Therefore, to make it interactive, not only we will need to add some interactive HTML elements, like &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, we will also need to make use of "signals" as well.&lt;/p&gt;

&lt;p&gt;First, let's add two &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;s, and be able to log to the browser's console when any of the button is clicked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use leptos::*;
use leptos::logging::log;
use web_sys::MouseEvent;
...
fn App() -&amp;gt; impl IntoView {
  let who = "World"; 
  let on_clicked = |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
  };
  view! {
    {
      view! {
        &amp;lt;div class="test-class"&amp;gt;Hello, {who}!&amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;I am 1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;I am 2&amp;lt;/button&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two more dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    use leptos::logging::log;
    use web_sys::MouseEvent;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The closure defined by &lt;code&gt;on_clicked&lt;/code&gt; will be called when any one of the button is clicked
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;button on:click=on_clicked value="1"&amp;gt;I am 1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;I am 2&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the two &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;s are placed in the same level as the "nested" &lt;code&gt;view!&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each button is associated with a value (e.g. &lt;code&gt;value="1"&lt;/code&gt;), and when a button is clicked, the associated value is retrieved and printed out to the browser's console
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's make use of "signal" to trigger update of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let value = clicked_value.get();
          format!("Hello, [{}]!", value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;I am 1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;I am 2&amp;lt;/button&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "signal" is created like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let (clicked_value, set_clicked_value) = create_signal(String::from(""));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;such a "signal" is composed of a "getter" &lt;code&gt;clicked_value&lt;/code&gt; and a "setter" &lt;code&gt;set_clicked_value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the type of the "signal" is &lt;code&gt;String&lt;/code&gt;, and the "signal" is initialized to the empty &lt;code&gt;String&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The "setter" &lt;code&gt;set_clicked_value&lt;/code&gt; is called to set new value in the closure defined by &lt;code&gt;on_click&lt;/code&gt;, which is called when any of the button is clicked
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let on_clicked = move |ev: MouseEvent| {
    ...
    set_clicked_value.set(value);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the closure captures variables, like &lt;code&gt;set_clicked_value&lt;/code&gt;,  moved; and this is the requirement of using "signal"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "user" of the "signal" is the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; created by the nested &lt;code&gt;view!&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    move || view! {
      &amp;lt;div class="test-class"&amp;gt; {
        let value = clicked_value.get();
        format!("Hello, [{}]!", value)
      } &amp;lt;/div&amp;gt;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the "nested" &lt;code&gt;view&lt;/code&gt; is now a "moved" closure, since it is using the "signal" to get the new value set&lt;/li&gt;
&lt;li&gt;the value set is returned by calling &lt;code&gt;clicked_value.get()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;by using the "signal", Leptos knows that the closure need be called again to update the content of the nested &lt;code&gt;view!&lt;/code&gt; when the "signal" is updated

&lt;ul&gt;
&lt;li&gt;Here is what will happen when a button is clicked&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;the closure &lt;code&gt;on_click&lt;/code&gt; gets called&lt;/li&gt;
&lt;li&gt;the "signal" gets updated when &lt;code&gt;set_clicked_value&lt;/code&gt; is called&lt;/li&gt;
&lt;li&gt;the closure of the "nested" &lt;code&gt;view!&lt;/code&gt; gets called when the "signal" is updated, which will update the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; generated by the "nested" &lt;code&gt;view!&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's change the buttons to ones that simulate the key presses for &lt;code&gt;1 + 2 =&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
  &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
  &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
  &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And also add &lt;code&gt;DumbCalculator&lt;/code&gt; into the picture&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
use std::cell::RefCell;
use rusty_dumb_tools::calculator::*;
...
fn App() -&amp;gt; impl IntoView {
  let calculator_ref = RefCell::new(DumbCalculator::new());
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let mut calculator = calculator_ref.borrow_mut();
          let value = clicked_value.get();
          if !value.is_empty() {
            calculator.push(value.as_str()).unwrap();
          }
          let result_value = calculator.get_display_sized(10);
          format!("[{}]", result_value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An instance of &lt;code&gt;DumbCalculator&lt;/code&gt; is created and stored in a &lt;code&gt;RefCell&lt;/code&gt;, and assigned to &lt;code&gt;calculator_ref&lt;/code&gt;; note that even &lt;code&gt;calculator_ref&lt;/code&gt; is immutable, the instance of &lt;code&gt;DumbCalculator&lt;/code&gt; can be retrieved mutable
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let calculator_ref = RefCell::new(DumbCalculator::new());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that since &lt;code&gt;App()&lt;/code&gt; will only be called one, only a single instance of &lt;code&gt;DumbCalculator&lt;/code&gt; will ever be created, unless the browser page is refreshed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "nested" &lt;code&gt;view!&lt;/code&gt; code for the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is changed to something like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;div class="test-class"&amp;gt; {
      let mut calculator = calculator_ref.borrow_mut();
      let value = clicked_value.get();
      if !value.is_empty() {
        calculator.push(value.as_str()).unwrap();
      }
      let result_value = calculator.get_display_sized(10);
      format!("[{}]", result_value)
    } &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The instance of &lt;code&gt;DumbCalculator&lt;/code&gt; is "borrowed" mutable&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let mut calculator = calculator_ref.borrow_mut();
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The button value, which is supposed to simulate calculator key press, is pushed to the &lt;code&gt;DumbCalculator&lt;/code&gt; like&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  calculator.push(value.as_str()).unwrap();
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What the calculator display should look like is rendered in the content of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let result_value = calculator.get_display_sized(10);
  format!("[{}]!", result_value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's add two buttons for "AC" (&lt;em&gt;all cancel&lt;/em&gt;) as well as "⬅" (&lt;em&gt;undo&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let calculator_ref = RefCell::new(DumbCalculator::new());
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let mut calculator = calculator_ref.borrow_mut();
          let value = clicked_value.get();
          if value == "ac" {
            calculator.reset();
          } else if value == "&amp;lt;" {
            calculator.undo();
          } else if !value.is_empty() {
            calculator.push(value.as_str()).unwrap();
          }
          let result_value = calculator.get_display_sized(10);
          format!("[{}]", result_value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button on:click=on_clicked value="ac"&amp;gt;AC&amp;lt;/button&amp;gt;
      &amp;lt;button on:click=on_clicked value="&amp;lt;"&amp;gt;{"⬅"}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The two additional buttons are added below the &lt;code&gt;1 + 2 =&lt;/code&gt; buttons like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;div&amp;gt;
      &amp;lt;button on:click=on_clicked value="ac"&amp;gt;AC&amp;lt;/button&amp;gt;
      &amp;lt;button on:click=on_clicked value="&amp;lt;"&amp;gt;{"⬅"}&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Clicking of the two buttons are handled like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let value = clicked_value.get();
    if value == "ac" {
      calculator.reset();
    } else if value == "&amp;lt;" {
      calculator.undo();
    } else if !value.is_empty() {
      calculator.push(value.as_str()).unwrap();
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are getting closer and closer. Let's add to the code the capability of showing the history of the calculator.&lt;/p&gt;

&lt;p&gt;For this, we will be using another &lt;code&gt;history&lt;/code&gt; "signal".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn App() -&amp;gt; impl IntoView {
  let calculator_ref = RefCell::new(DumbCalculator::new());
  let (clicked_value, set_clicked_value) = create_signal(String::from(""));
  let (history, set_history) = create_signal(String::from(""));
  let on_clicked = move |ev: MouseEvent| {
    let value = event_target_value(&amp;amp;ev);
    log!("* clicked value [{}]", value);
    set_clicked_value.set(value);
  };
  view! {
    {
      move || view! {
        &amp;lt;div class="test-class"&amp;gt; {
          let mut calculator = calculator_ref.borrow_mut();
          let value = clicked_value.get();
          if value == "ac" {
            calculator.reset();
          } else if value == "&amp;lt;" {
            calculator.undo();
          } else if !value.is_empty() {
            calculator.push(value.as_str()).unwrap();
          }
          let history = calculator.get_history_string(true);
          match &amp;amp;history {
            Some(hist) =&amp;gt; set_history.set(hist.to_string()),
            None =&amp;gt; set_history.set("".to_string()),  
          }
          let result_value = calculator.get_display_sized(10);
          format!("[{}]", result_value)
        } &amp;lt;/div&amp;gt;
      }
    }
    &amp;lt;button on:click=on_clicked value="1"&amp;gt;1&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="+"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="2"&amp;gt;2&amp;lt;/button&amp;gt;
    &amp;lt;button on:click=on_clicked value="="&amp;gt;=&amp;lt;/button&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button on:click=on_clicked value="ac"&amp;gt;AC&amp;lt;/button&amp;gt;
      &amp;lt;button on:click=on_clicked value="&amp;lt;"&amp;gt;{"⬅"}&amp;lt;/button&amp;gt;
      {move || view! {
        &amp;lt;span&amp;gt; { history.get() } &amp;lt;/span&amp;gt;
      }}
    &amp;lt;/div&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;history&lt;/code&gt; "signal" is added like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let (history, set_history) = create_signal(String::from(""));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;history&lt;/code&gt; if set after pushing value to the calculator
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let history = calculator.get_history_string(true);
    match &amp;amp;history {
      Some(hist) =&amp;gt; set_history.set(hist.to_string()),
      None =&amp;gt; set_history.set("".to_string()),  
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The updated &lt;code&gt;history&lt;/code&gt; is displayed next to the "⬅" button
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {move || view! {
      &amp;lt;span&amp;gt; { history.get() } &amp;lt;/span&amp;gt;
    }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;Finally, let's add all the whistles and bells and finish off our calculator ... &lt;/p&gt;

&lt;p&gt;Simply ... please replace the corresponding file as listed here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trunk.html&lt;/code&gt; -- &lt;a href="https://github.com/trevorwslee/wasm_calculator/blob/master/trunk.html"&gt;https://github.com/trevorwslee/wasm_calculator/blob/master/trunk.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main.rs&lt;/code&gt; -- &lt;a href="https://github.com/trevorwslee/wasm_calculator/blob/master/src/main.rs"&gt;https://github.com/trevorwslee/wasm_calculator/blob/master/src/main.rs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or ... simply ... clone the GitHub repo &lt;a href="https://github.com/trevorwslee/wasm_calculator"&gt;https://github.com/trevorwslee/wasm_calculator&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Manually Deploy to GitHub Page
&lt;/h2&gt;

&lt;p&gt;Now that we have the final result WASM calculator, we may want to deployed it to GitHub Pages.&lt;/p&gt;

&lt;p&gt;Assuming a GitHub account, say like mine -- &lt;code&gt;trevorwslee&lt;/code&gt;, I can easily post some static pages to my GitHub Pages -- &lt;code&gt;https://trevorwslee.github.io/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What I needed is a GitHub repository with the "same" name &lt;code&gt;trevorwslee.github.io&lt;/code&gt; -- &lt;a href="https://github.com/trevorwslee/trevorwslee.github.io"&gt;https://github.com/trevorwslee/trevorwslee.github.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order for GitHub Pages to host our WASM Calculator app&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With Trunk, build &lt;em&gt;release&lt;/em&gt; version specifying the context to use in GitHub Pages, like mine

&lt;ul&gt;
&lt;li&gt;The context is &lt;code&gt;WASMCalculator&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then the URL to the WASM app is &lt;a href="https://trevorwslee.github.io/WASMCalculator/"&gt;https://trevorwslee.github.io/WASMCalculator/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;To build such &lt;em&gt;release&lt;/em&gt;, run Trunk like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  trunk build --release --public-url "WASMCalculator"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;After building the &lt;em&gt;release&lt;/em&gt; with Trunk, the &lt;code&gt;dist&lt;/code&gt; folder will contain the files

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wasm_calculator-XXX.wasm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasm_calculator-XXX.js&lt;/code&gt;
to be checked in to &lt;code&gt;WASMCalculator&lt;/code&gt; folder of the GitHub Pages repository, like mine -- &lt;a href="https://github.com/trevorwslee/trevorwslee.github.io/tree/main/WASMCalculator"&gt;https://github.com/trevorwslee/trevorwslee.github.io/tree/main/WASMCalculator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;After a few moments, the WASM Calculator should be ready to try out -- &lt;a href="https://trevorwslee.github.io/WASMCalculator/"&gt;&lt;/a&gt;&lt;a href="https://trevorwslee.github.io/WASMCalculator/"&gt;https://trevorwslee.github.io/WASMCalculator/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enjoy!
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Peace be with you!&lt;br&gt;
May God bless you!&lt;br&gt;
Jesus loves you!&lt;br&gt;
Amazing Grace!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webassembly</category>
      <category>rust</category>
      <category>leptos</category>
    </item>
  </channel>
</rss>
