<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-03-20T16:33:58+00:00</updated><id>/feed.xml</id><title type="html">Basko Blog</title><subtitle>My daily, strange projects and thoughts.</subtitle><entry><title type="html">Rubik’s Cube Net</title><link href="/rubiks/cube/2026/03/12/rubiks-cube-net.html" rel="alternate" type="text/html" title="Rubik’s Cube Net" /><published>2026-03-12T22:00:00+00:00</published><updated>2026-03-12T22:00:00+00:00</updated><id>/rubiks/cube/2026/03/12/rubiks-cube-net</id><content type="html" xml:base="/rubiks/cube/2026/03/12/rubiks-cube-net.html"><![CDATA[<p>A simple interactive Rubik’s Cube displayed as a net (cross/T shape).
Use the buttons below to perform standard cube rotations.
The log panel on the right records each move. Press <strong>New Line</strong> to start a new log row, and <strong>Reset</strong> to restore the solved state and clear the log.</p>

<style>
  .rcube-app {
    font-family: Arial, sans-serif;
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    align-items: flex-start;
  }

  .rcube-left {
    display: flex;
    flex-direction: column;
    gap: 15px;
  }

  .rcube-net {
    display: grid;
    grid-template-columns: repeat(4, auto);
    grid-template-rows: repeat(3, auto);
    gap: 2px;
    justify-content: start;
  }

  .rcube-face {
    display: grid;
    grid-template-columns: repeat(3, 36px);
    grid-template-rows: repeat(3, 36px);
    gap: 1px;
    border: 2px solid #333;
    padding: 1px;
    background: #333;
  }

  .rcube-face-U { grid-column: 2; grid-row: 1; }
  .rcube-face-L { grid-column: 1; grid-row: 2; }
  .rcube-face-F { grid-column: 2; grid-row: 2; }
  .rcube-face-R { grid-column: 3; grid-row: 2; }
  .rcube-face-B { grid-column: 4; grid-row: 2; }
  .rcube-face-D { grid-column: 2; grid-row: 3; }

  .rcube-cell {
    width: 36px;
    height: 36px;
    border: 1px solid #555;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  .rcube-cell-original {
    font-size: 9px;
    font-weight: bold;
    line-height: 1;
  }

  .rcube-cell-current {
    font-size: 8px;
    line-height: 1;
  }

  .rcube-controls {
    display: grid;
    grid-template-columns: repeat(8, auto);
    grid-template-rows: repeat(3, auto);
    gap: 6px;
    justify-content: start;
  }

  .rcube-controls button {
    padding: 6px 12px;
    font-size: 14px;
    cursor: pointer;
    border: 1px solid #999;
    border-radius: 4px;
    background: #f0f0f0;
    min-width: 42px;
  }

  .rcube-controls button:hover {
    background: #ddd;
  }

  .rcube-reset-section {
    margin-top: 10px;
  }

  .rcube-reset-section button {
    padding: 6px 12px;
    font-size: 14px;
    cursor: pointer;
    border-radius: 4px;
    background: #ff6b6b;
    color: white;
    border-color: #e55;
    font-weight: bold;
    min-width: 42px;
  }

  .rcube-reset-section button:hover {
    background: #e55;
  }

  .rcube-log-panel {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 200px;
  }

  .rcube-log-panel textarea {
    width: 250px;
    height: 350px;
    font-family: monospace;
    font-size: 13px;
    resize: vertical;
    border: 1px solid #999;
    border-radius: 4px;
    padding: 6px;
  }

  .rcube-log-panel button {
    padding: 6px 12px;
    font-size: 14px;
    cursor: pointer;
    border: 1px solid #999;
    border-radius: 4px;
    background: #f0f0f0;
    align-self: flex-start;
  }

  .rcube-log-panel button:hover {
    background: #ddd;
  }
</style>

<div class="rcube-app">
  <div class="rcube-left">
    <div class="rcube-net" id="rcube-net"></div>
    <div class="rcube-controls">
      <button style="grid-column:3; grid-row:1;" onclick="rcubeDoMove('U')">U</button>
      <button style="grid-column:4; grid-row:1;" onclick="rcubeDoMove('Ui')">U'</button>
      <button style="grid-column:1; grid-row:2;" onclick="rcubeDoMove('L')">L</button>
      <button style="grid-column:2; grid-row:2;" onclick="rcubeDoMove('Li')">L'</button>
      <button style="grid-column:3; grid-row:2;" onclick="rcubeDoMove('F')">F</button>
      <button style="grid-column:4; grid-row:2;" onclick="rcubeDoMove('Fi')">F'</button>
      <button style="grid-column:5; grid-row:2;" onclick="rcubeDoMove('R')">R</button>
      <button style="grid-column:6; grid-row:2;" onclick="rcubeDoMove('Ri')">R'</button>
      <button style="grid-column:7; grid-row:2;" onclick="rcubeDoMove('B')">B</button>
      <button style="grid-column:8; grid-row:2;" onclick="rcubeDoMove('Bi')">B'</button>
      <button style="grid-column:3; grid-row:3;" onclick="rcubeDoMove('D')">D</button>
      <button style="grid-column:4; grid-row:3;" onclick="rcubeDoMove('Di')">D'</button>
    </div>
    <div class="rcube-reset-section">
      <button onclick="rcubeRandomMove()">Random Move</button>
      <button onclick="rcubeReset()">Reset</button>
    </div>
  </div>
  <div class="rcube-log-panel">
    <strong>Move Log</strong>
    <textarea id="rcube-log" readonly=""></textarea>
    <button onclick="rcubeNewLogLine()">New Line</button>
  </div>
</div>

<script>
(function() {
  // Face indices
  var U = 0, D = 1, F = 2, B = 3, L = 4, R = 5;

  // Face colors: U=white, D=yellow, F=green, B=blue, L=orange, R=red
  var FACE_COLORS = ['#FFFFFF', '#FFDD00', '#009B48', '#0046AD', '#FF5800', '#B71234'];
  var FACE_NAMES = ['U', 'D', 'F', 'B', 'L', 'R'];

  var cube;
  var currentLogLine = '';

  function initCube() {
    cube = [];
    for (var f = 0; f < 6; f++) {
      cube[f] = [];
      for (var r = 0; r < 3; r++) {
        cube[f][r] = [];
        for (var c = 0; c < 3; c++) {
          cube[f][r][c] = f * 9 + r * 3 + c;
        }
      }
    }
  }

  function rotateFaceCW(faceIdx) {
    var face = cube[faceIdx];
    var tmp = face.map(function(row) { return row.slice(); });
    for (var i = 0; i < 3; i++) {
      for (var j = 0; j < 3; j++) {
        face[j][2 - i] = tmp[i][j];
      }
    }
  }

  function rotateFaceCCW(faceIdx) {
    var face = cube[faceIdx];
    var tmp = face.map(function(row) { return row.slice(); });
    for (var i = 0; i < 3; i++) {
      for (var j = 0; j < 3; j++) {
        face[2 - j][i] = tmp[i][j];
      }
    }
  }

  // === Move implementations ===
  // All edge values are saved before any modification to avoid
  // overwrite issues when reversed indices (2-i) are used.

  function moveR() {
    rotateFaceCW(R);
    var eF = [cube[F][0][2], cube[F][1][2], cube[F][2][2]];
    var eU = [cube[U][0][2], cube[U][1][2], cube[U][2][2]];
    var eB = [cube[B][0][0], cube[B][1][0], cube[B][2][0]];
    var eD = [cube[D][0][2], cube[D][1][2], cube[D][2][2]];
    for (var i = 0; i < 3; i++) {
      cube[U][i][2] = eF[i];
      cube[F][i][2] = eD[i];
      cube[D][i][2] = eB[2 - i];
      cube[B][i][0] = eU[2 - i];
    }
  }

  function moveRi() {
    rotateFaceCCW(R);
    var eF = [cube[F][0][2], cube[F][1][2], cube[F][2][2]];
    var eU = [cube[U][0][2], cube[U][1][2], cube[U][2][2]];
    var eB = [cube[B][0][0], cube[B][1][0], cube[B][2][0]];
    var eD = [cube[D][0][2], cube[D][1][2], cube[D][2][2]];
    for (var i = 0; i < 3; i++) {
      cube[F][i][2] = eU[i];
      cube[U][i][2] = eB[2 - i];
      cube[B][i][0] = eD[2 - i];
      cube[D][i][2] = eF[i];
    }
  }

  function moveL() {
    rotateFaceCW(L);
    var eF = [cube[F][0][0], cube[F][1][0], cube[F][2][0]];
    var eU = [cube[U][0][0], cube[U][1][0], cube[U][2][0]];
    var eB = [cube[B][0][2], cube[B][1][2], cube[B][2][2]];
    var eD = [cube[D][0][0], cube[D][1][0], cube[D][2][0]];
    for (var i = 0; i < 3; i++) {
      cube[F][i][0] = eU[i];
      cube[U][i][0] = eB[2 - i];
      cube[B][i][2] = eD[2 - i];
      cube[D][i][0] = eF[i];
    }
  }

  function moveLi() {
    rotateFaceCCW(L);
    var eF = [cube[F][0][0], cube[F][1][0], cube[F][2][0]];
    var eU = [cube[U][0][0], cube[U][1][0], cube[U][2][0]];
    var eB = [cube[B][0][2], cube[B][1][2], cube[B][2][2]];
    var eD = [cube[D][0][0], cube[D][1][0], cube[D][2][0]];
    for (var i = 0; i < 3; i++) {
      cube[F][i][0] = eD[i];
      cube[D][i][0] = eB[2 - i];
      cube[B][i][2] = eU[2 - i];
      cube[U][i][0] = eF[i];
    }
  }

  function moveU() {
    rotateFaceCW(U);
    var eF = [cube[F][0][0], cube[F][0][1], cube[F][0][2]];
    var eR = [cube[R][0][0], cube[R][0][1], cube[R][0][2]];
    var eB = [cube[B][0][0], cube[B][0][1], cube[B][0][2]];
    var eL = [cube[L][0][0], cube[L][0][1], cube[L][0][2]];
    for (var i = 0; i < 3; i++) {
      cube[F][0][i] = eR[i];
      cube[R][0][i] = eB[i];
      cube[B][0][i] = eL[i];
      cube[L][0][i] = eF[i];
    }
  }

  function moveUi() {
    rotateFaceCCW(U);
    var eF = [cube[F][0][0], cube[F][0][1], cube[F][0][2]];
    var eR = [cube[R][0][0], cube[R][0][1], cube[R][0][2]];
    var eB = [cube[B][0][0], cube[B][0][1], cube[B][0][2]];
    var eL = [cube[L][0][0], cube[L][0][1], cube[L][0][2]];
    for (var i = 0; i < 3; i++) {
      cube[F][0][i] = eL[i];
      cube[L][0][i] = eB[i];
      cube[B][0][i] = eR[i];
      cube[R][0][i] = eF[i];
    }
  }

  function moveD() {
    rotateFaceCW(D);
    var eF = [cube[F][2][0], cube[F][2][1], cube[F][2][2]];
    var eR = [cube[R][2][0], cube[R][2][1], cube[R][2][2]];
    var eB = [cube[B][2][0], cube[B][2][1], cube[B][2][2]];
    var eL = [cube[L][2][0], cube[L][2][1], cube[L][2][2]];
    for (var i = 0; i < 3; i++) {
      cube[F][2][i] = eL[i];
      cube[L][2][i] = eB[i];
      cube[B][2][i] = eR[i];
      cube[R][2][i] = eF[i];
    }
  }

  function moveDi() {
    rotateFaceCCW(D);
    var eF = [cube[F][2][0], cube[F][2][1], cube[F][2][2]];
    var eR = [cube[R][2][0], cube[R][2][1], cube[R][2][2]];
    var eB = [cube[B][2][0], cube[B][2][1], cube[B][2][2]];
    var eL = [cube[L][2][0], cube[L][2][1], cube[L][2][2]];
    for (var i = 0; i < 3; i++) {
      cube[F][2][i] = eR[i];
      cube[R][2][i] = eB[i];
      cube[B][2][i] = eL[i];
      cube[L][2][i] = eF[i];
    }
  }

  function moveF() {
    rotateFaceCW(F);
    var eU = [cube[U][2][0], cube[U][2][1], cube[U][2][2]];
    var eR = [cube[R][0][0], cube[R][1][0], cube[R][2][0]];
    var eD = [cube[D][0][0], cube[D][0][1], cube[D][0][2]];
    var eL = [cube[L][0][2], cube[L][1][2], cube[L][2][2]];
    for (var i = 0; i < 3; i++) {
      cube[U][2][i] = eL[2 - i];
      cube[R][i][0] = eU[i];
      cube[D][0][i] = eR[2 - i];
      cube[L][i][2] = eD[i];
    }
  }

  function moveFi() {
    rotateFaceCCW(F);
    var eU = [cube[U][2][0], cube[U][2][1], cube[U][2][2]];
    var eR = [cube[R][0][0], cube[R][1][0], cube[R][2][0]];
    var eD = [cube[D][0][0], cube[D][0][1], cube[D][0][2]];
    var eL = [cube[L][0][2], cube[L][1][2], cube[L][2][2]];
    for (var i = 0; i < 3; i++) {
      cube[U][2][i] = eR[i];
      cube[R][i][0] = eD[2 - i];
      cube[D][0][i] = eL[i];
      cube[L][i][2] = eU[2 - i];
    }
  }

  function moveB() {
    rotateFaceCW(B);
    var eU = [cube[U][0][0], cube[U][0][1], cube[U][0][2]];
    var eR = [cube[R][0][2], cube[R][1][2], cube[R][2][2]];
    var eD = [cube[D][2][0], cube[D][2][1], cube[D][2][2]];
    var eL = [cube[L][0][0], cube[L][1][0], cube[L][2][0]];
    for (var i = 0; i < 3; i++) {
      cube[U][0][i] = eR[i];
      cube[R][i][2] = eD[2 - i];
      cube[D][2][i] = eL[i];
      cube[L][i][0] = eU[2 - i];
    }
  }

  function moveBi() {
    rotateFaceCCW(B);
    var eU = [cube[U][0][0], cube[U][0][1], cube[U][0][2]];
    var eR = [cube[R][0][2], cube[R][1][2], cube[R][2][2]];
    var eD = [cube[D][2][0], cube[D][2][1], cube[D][2][2]];
    var eL = [cube[L][0][0], cube[L][1][0], cube[L][2][0]];
    for (var i = 0; i < 3; i++) {
      cube[U][0][i] = eL[2 - i];
      cube[L][i][0] = eD[i];
      cube[D][2][i] = eR[2 - i];
      cube[R][i][2] = eU[i];
    }
  }

  var MOVES = {
    'R': moveR, 'Ri': moveRi,
    'L': moveL, 'Li': moveLi,
    'U': moveU, 'Ui': moveUi,
    'D': moveD, 'Di': moveDi,
    'F': moveF, 'Fi': moveFi,
    'B': moveB, 'Bi': moveBi
  };

  var MOVE_LABELS = {
    'R': 'R', 'Ri': "R'",
    'L': 'L', 'Li': "L'",
    'U': 'U', 'Ui': "U'",
    'D': 'D', 'Di': "D'",
    'F': 'F', 'Fi': "F'",
    'B': 'B', 'Bi': "B'"
  };

  // === Rendering ===

  function buildNet() {
    var net = document.getElementById('rcube-net');
    net.innerHTML = '';

    var faceOrder = [
      { id: U, cls: 'rcube-face-U' },
      { id: L, cls: 'rcube-face-L' },
      { id: F, cls: 'rcube-face-F' },
      { id: R, cls: 'rcube-face-R' },
      { id: B, cls: 'rcube-face-B' },
      { id: D, cls: 'rcube-face-D' }
    ];

    for (var fi = 0; fi < faceOrder.length; fi++) {
      var f = faceOrder[fi];
      var faceDiv = document.createElement('div');
      faceDiv.className = 'rcube-face ' + f.cls;
      faceDiv.setAttribute('data-face', f.id);

      for (var r = 0; r < 3; r++) {
        for (var c = 0; c < 3; c++) {
          var cell = document.createElement('div');
          cell.className = 'rcube-cell';
          cell.setAttribute('data-face', f.id);
          cell.setAttribute('data-row', r);
          cell.setAttribute('data-col', c);
          faceDiv.appendChild(cell);
        }
      }

      net.appendChild(faceDiv);
    }
  }

  function renderCube() {
    var cells = document.querySelectorAll('.rcube-cell');
    for (var i = 0; i < cells.length; i++) {
      var cell = cells[i];
      var f = parseInt(cell.getAttribute('data-face'));
      var r = parseInt(cell.getAttribute('data-row'));
      var c = parseInt(cell.getAttribute('data-col'));
      var originalIndex = cube[f][r][c];
      var currentIndex = f * 9 + r * 3 + c;
      cell.style.backgroundColor = FACE_COLORS[Math.floor(originalIndex / 9)];
      cell.innerHTML =
        '<span class="rcube-cell-original">' + originalIndex + '</span>' +
        '<span class="rcube-cell-current">(' + currentIndex + ')</span>';
    }
  }

  // === Log ===

  function addToLog(moveLabel) {
    if (currentLogLine.length > 0) {
      currentLogLine += ' ';
    }
    currentLogLine += moveLabel;
    updateLogDisplay();
  }

  function updateLogDisplay() {
    var logEl = document.getElementById('rcube-log');
    var lines = logEl.value;
    // Replace the last line with current
    var allLines = lines.split('\n');
    if (allLines.length === 0 || (allLines.length === 1 && allLines[0] === '')) {
      logEl.value = currentLogLine;
    } else {
      allLines[allLines.length - 1] = currentLogLine;
      logEl.value = allLines.join('\n');
    }
    logEl.scrollTop = logEl.scrollHeight;
  }

  // === Public API (attached to window) ===

  window.rcubeDoMove = function(moveKey) {
    MOVES[moveKey]();
    addToLog(MOVE_LABELS[moveKey]);
    renderCube();
  };

  window.rcubeReset = function() {
    initCube();
    currentLogLine = '';
    document.getElementById('rcube-log').value = '';
    renderCube();
  };

  window.rcubeRandomMove = function() {
    var moveKeys = Object.keys(MOVES);
    var randomKey = moveKeys[Math.floor(Math.random() * moveKeys.length)];
    window.rcubeDoMove(randomKey);
  };

  window.rcubeNewLogLine = function() {
    var logEl = document.getElementById('rcube-log');
    if (currentLogLine.length > 0) {
      logEl.value += '\n';
    }
    currentLogLine = '';
  };

  // === Init ===

  initCube();
  buildNet();
  renderCube();
})();
</script>]]></content><author><name></name></author><category term="rubiks" /><category term="cube" /><summary type="html"><![CDATA[A simple interactive Rubik’s Cube displayed as a net (cross/T shape). Use the buttons below to perform standard cube rotations. The log panel on the right records each move. Press New Line to start a new log row, and Reset to restore the solved state and clear the log.]]></summary></entry><entry><title type="html">Flashing Android on the Orange Pi 5B from a Linux Host</title><link href="/orangepi/android/aosp/linux/2024/09/12/orangepi-android-flash-linux.html" rel="alternate" type="text/html" title="Flashing Android on the Orange Pi 5B from a Linux Host" /><published>2024-09-12T22:00:00+00:00</published><updated>2024-09-12T22:00:00+00:00</updated><id>/orangepi/android/aosp/linux/2024/09/12/orangepi-android-flash-linux</id><content type="html" xml:base="/orangepi/android/aosp/linux/2024/09/12/orangepi-android-flash-linux.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>In this post, I will show you how to flash an Android OS image on an Orange Pi 5B board using a Linux host.</p>

<p>Though it’s not as straightforward as it might seem.</p>

<h2 id="the-challenge">The Challenge</h2>
<p>The <a href="https://drive.google.com/drive/folders/11JLZcvjU1eN6SkJPZ7m9gm6q-_UVtqYd">Orange Pi 5B manual</a> mentions that building its Android based OS, needs to be done on a Linux host.</p>

<p>The build output is an <code class="language-plaintext highlighter-rouge">update.img</code> file that is possible to flash to the Orange Pi 5B device using the Windows only methods, through a TF card or a Type-C cable into eMMC.</p>

<p>The proposed approach in the manual for flashing is using different Windows compatible applications, such as <code class="language-plaintext highlighter-rouge">RKDevTool</code> or <code class="language-plaintext highlighter-rouge">SDDiskTool</code>.</p>

<p>This approach of building the OS on Linux, but can only be flashed using Windows, is an annoying approach as it requires a constant switch between the Linux and the Windows operating systems. 
Which is very time-consuming and significantly affects the feedback cycle.</p>

<p>I’ve also tried using Windows’ WSL for the build process, but it wasn’t able to install part of the required packages.</p>

<h2 id="the-rkdevelop-tool">The rkdevelop Tool</h2>
<p>The Orange Pi 5B has a Rockchip SoC, and Rockchip provides a tool called <a href="https://github.com/rockchip-linux/rkdeveloptool"><code class="language-plaintext highlighter-rouge">rkdeveloptool</code></a> which allows flashing OS images to the device.</p>

<p>The issue was that I couldn’t find any information on how to use this tool to flash an Android OS image to the Orange Pi 5B device, or any similar information.</p>

<p>To install the <code class="language-plaintext highlighter-rouge">rkdeveloptool</code> on Ubuntu, I’ve used the following commands:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> libudev-dev libusb-1.0-0-dev dh-autoreconf pkg-config libusb-1.0 build-essential git wget
git clone https://github.com/rockchip-linux/rkdeveloptool
<span class="nb">cd </span>rkdeveloptool
wget https://patch-diff.githubusercontent.com/raw/rockchip-linux/rkdeveloptool/pull/73.patch
wget https://patch-diff.githubusercontent.com/raw/rockchip-linux/rkdeveloptool/pull/85.patch
git am <span class="k">*</span>.patch
autoreconf <span class="nt">-i</span>
./configure
make <span class="nt">-j</span> <span class="si">$(</span><span class="nb">nproc</span><span class="si">)</span>
</code></pre></div></div>

<h2 id="the-loader-file">The loader file</h2>
<p>Before it is possible to flash the OS image to the Orange Pi 5B device, it is necessary to flash a loader file to the device.</p>

<p>The loader file could be found here: <a href="https://docs.radxa.com/en/rock5/rock5b/low-level-dev/maskrom/linux">Orange Pi 5B loader file</a>.
This file is used to boot the device into a mode where it is possible to flash the OS image to it.</p>

<p>It will be loaded as part of the flashing process. Command details will be provided in the next chapters.</p>

<h2 id="the-flashing-process">The Flashing Process</h2>
<p>As part of building the Android OS, there are several images that are created:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">uboot.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">misc.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">dtbo.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">vbmeta.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">boot.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">recovery.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">baseparameter.img</code></li>
  <li><code class="language-plaintext highlighter-rouge">super.img</code></li>
</ol>

<p>As part of the build process they all are combined into a single <code class="language-plaintext highlighter-rouge">update.img</code> file using RKImageMaker tool. 
It is done automatically by the build process, so we don’t need to worry about it.</p>

<p>Each image has its own responsibility and is flashed to a specific address on the device. 
I might cover the responsibilities of each image in a separate post.</p>

<p>For now we just need to know that we need all of them to flash the OS to the device.</p>

<p>On Windows the flashing process is described in detail in the Orange Pi 5B manual.
But there is no description on how to flash the Android OS on a Linux host.</p>

<p>Fortunately we can look at the logs of the Windows flashing process and see
that behind the scenes the Windows tool is splitting the <code class="language-plaintext highlighter-rouge">update.img</code> back to the
individual images and flashing them to the device one by one using specific addresses.</p>

<p>I found out the following addresses for each image:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">uboot.img</code> - 0x4000</li>
  <li><code class="language-plaintext highlighter-rouge">misc.img</code> - 0x9000</li>
  <li><code class="language-plaintext highlighter-rouge">dtbo.img</code> - 0xb000</li>
  <li><code class="language-plaintext highlighter-rouge">vbmeta.img</code> - 0xd000</li>
  <li><code class="language-plaintext highlighter-rouge">boot.img</code> - 0xd800</li>
  <li><code class="language-plaintext highlighter-rouge">recovery.img</code> - 0x3f800</li>
  <li><code class="language-plaintext highlighter-rouge">baseparameter.img</code> - 0x1f7800</li>
  <li><code class="language-plaintext highlighter-rouge">super.img</code> - 0x1f8000</li>
</ol>

<p>Now using the <code class="language-plaintext highlighter-rouge">rkdeveloptool</code> we can flash the images to the device.
Example command for flashing the <code class="language-plaintext highlighter-rouge">uboot.img</code> to the device:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>rkdeveloptool wl 0x4000 uboot.img
</code></pre></div></div>

<h2 id="the-full-process">The Full Process</h2>
<p>So basically the full process is pretty simple. 
After building the Android OS we need to do the following steps:</p>
<ol>
  <li>Put the device into maskrom mode - This is done by holding the maskrom button on the board while powering the device on.</li>
  <li>Flash the loader file to the device - <code class="language-plaintext highlighter-rouge">sudo rkdeveloptool db rk3588_spl_loader_v1.15.113.bin</code></li>
  <li>Flash the individual images to the device using specific addresses - <code class="language-plaintext highlighter-rouge">sudo rkdeveloptool wl &lt;address&gt; &lt;image_path&gt;</code></li>
  <li>Reboot the device - <code class="language-plaintext highlighter-rouge">sudo rkdeveloptool rd</code></li>
</ol>

<p>After that the device should boot into the flashed Android OS.</p>

<p>I’ve also prepared a bash script that automates the flashing process, 
which also does some basic validations before it starts the flashing process.</p>

<p>The full script:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Check if the correct number of arguments is provided</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"$#"</span> <span class="nt">-ne</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Usage: </span><span class="nv">$0</span><span class="s2"> &lt;folder_path&gt;"</span>
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># Set the folder path from the input parameter</span>
<span class="nv">FOLDER_PATH</span><span class="o">=</span><span class="nv">$1</span>

<span class="c"># Check if the folder exists</span>
<span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$FOLDER_PATH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Error: Folder '</span><span class="nv">$FOLDER_PATH</span><span class="s2">' does not exist."</span>
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># the loader file should be located in the current directory.</span>
<span class="nv">LOADER</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/rk3588_spl_loader_v1.15.113.bin"</span>

<span class="c"># Define the images and their offsets</span>
<span class="nb">declare</span> <span class="nt">-A</span> <span class="nv">IMAGES</span><span class="o">=(</span>
    <span class="o">[</span>uboot.img]<span class="o">=</span>0x4000
    <span class="o">[</span>misc.img]<span class="o">=</span>0x9000
    <span class="o">[</span>dtbo.img]<span class="o">=</span>0xb000
    <span class="o">[</span>vbmeta.img]<span class="o">=</span>0xd000
    <span class="o">[</span>boot.img]<span class="o">=</span>0xd800
    <span class="o">[</span>recovery.img]<span class="o">=</span>0x3f800
    <span class="o">[</span>baseparameter.img]<span class="o">=</span>0x1f7800
    <span class="o">[</span>super.img]<span class="o">=</span>0x1f8000
<span class="o">)</span>

<span class="c"># Check if the loader file exists</span>
<span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$LOADER</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"Error: Loader file '</span><span class="nv">$LOADER</span><span class="s2">' does not exist."</span>
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># Check if a device in Maskrom mode is connected</span>
<span class="nv">DEVICE_INFO</span><span class="o">=</span><span class="si">$(</span><span class="nb">sudo </span>rkdeveloptool ld | <span class="nb">grep</span> <span class="s2">"Maskrom"</span><span class="si">)</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$DEVICE_INFO</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"No device in Maskrom mode detected. Please connect the device and ensure it is in Maskrom mode."</span>
    <span class="nb">exit </span>1
<span class="k">else
    </span><span class="nb">echo</span> <span class="s2">"Device in Maskrom mode detected: </span><span class="nv">$DEVICE_INFO</span><span class="s2">"</span>
<span class="k">fi</span>

<span class="c"># Check if all required image files are available</span>
<span class="k">for </span>IMAGE <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="p">!IMAGES[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
    </span><span class="nv">IMAGE_PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$FOLDER_PATH</span><span class="s2">/</span><span class="nv">$IMAGE</span><span class="s2">"</span>
    <span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$IMAGE_PATH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Error: Required image file '</span><span class="nv">$IMAGE_PATH</span><span class="s2">' is missing."</span>
        <span class="nb">exit </span>1
    <span class="k">fi
done

</span><span class="nb">echo</span> <span class="s2">"All required image files are present."</span>

<span class="c"># Load the loader</span>
<span class="nb">echo</span> <span class="s2">"Loading the loader from </span><span class="nv">$LOADER</span><span class="s2">..."</span>
<span class="nb">sudo </span>rkdeveloptool db <span class="nv">$LOADER</span>

<span class="c"># Flash each image</span>
<span class="k">for </span>IMAGE <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="p">!IMAGES[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
    </span><span class="nv">OFFSET</span><span class="o">=</span><span class="k">${</span><span class="nv">IMAGES</span><span class="p">[</span><span class="nv">$IMAGE</span><span class="p">]</span><span class="k">}</span>
    <span class="nv">IMAGE_PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$FOLDER_PATH</span><span class="s2">/</span><span class="nv">$IMAGE</span><span class="s2">"</span>
    
    <span class="c"># Check if the image file exists</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$IMAGE_PATH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Flashing </span><span class="nv">$IMAGE</span><span class="s2"> at offset </span><span class="nv">$OFFSET</span><span class="s2">..."</span>
        <span class="nb">sudo </span>rkdeveloptool wl <span class="nv">$OFFSET</span> <span class="nv">$IMAGE_PATH</span>
    <span class="k">else
        </span><span class="nb">echo</span> <span class="s2">"Warning: Image file '</span><span class="nv">$IMAGE_PATH</span><span class="s2">' not found, aborting."</span>
        <span class="nb">exit </span>1
    <span class="k">fi
done</span>

<span class="c"># Reboot the device</span>
<span class="nb">echo</span> <span class="s2">"Rebooting the device..."</span>
<span class="nb">sudo </span>rkdeveloptool rd

<span class="nb">echo</span> <span class="s2">"Flashing process completed."</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>
<p>Now after we are able to flash the Android OS on a Linux host, the feedback cycles are much faster and the development process is much smoother.
In my experience, 
it has reduced my cognitive load and allowed me to focus more on the development process itself, 
and solving the actual problems, 
rather than wasting a lot of time on switching between the operating systems and remembering what I was doing and what I wanted to do.</p>

<p>Thanks for reading.</p>]]></content><author><name></name></author><category term="orangepi" /><category term="android" /><category term="aosp" /><category term="linux" /><summary type="html"><![CDATA[Overview In this post, I will show you how to flash an Android OS image on an Orange Pi 5B board using a Linux host.]]></summary></entry><entry><title type="html">Bridging Async and Sync: Plotting Async Data with matplotlib</title><link href="/matplotlib/python/asyncio/2024/02/06/live-matplotlib-plot-asyncio.html" rel="alternate" type="text/html" title="Bridging Async and Sync: Plotting Async Data with matplotlib" /><published>2024-02-06T22:00:00+00:00</published><updated>2024-02-06T22:00:00+00:00</updated><id>/matplotlib/python/asyncio/2024/02/06/live-matplotlib-plot-asyncio</id><content type="html" xml:base="/matplotlib/python/asyncio/2024/02/06/live-matplotlib-plot-asyncio.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>In my recent project, I aimed to visualize data from a Bluetooth Low Energy (BLE) device, a task that required
integrating different technologies. The <code class="language-plaintext highlighter-rouge">bleak</code> library was my choice for managing BLE communications in Python, known
for
its robust support for these devices.</p>

<p>The challenge arose when I wanted to plot this data using <code class="language-plaintext highlighter-rouge">matplotlib</code>, a library that excels in data visualization but
operates synchronously. Since <code class="language-plaintext highlighter-rouge">bleak</code> operates asynchronously, I faced the task of finding a way to bridge these two
different operational modes.</p>

<p><code class="language-plaintext highlighter-rouge">matplotlib</code>, while powerful, necessitates execution on the main thread—a requirement that complicates its use with
asynchronous data streams like those from <code class="language-plaintext highlighter-rouge">bleak</code>. This scenario highlighted a common issue in software development:
integrating synchronous and asynchronous systems in a seamless manner.</p>

<h2 id="solution">Solution</h2>

<p>To address the challenge of integrating asynchronous data reception with synchronous plotting, I devised a solution that
involves running the <code class="language-plaintext highlighter-rouge">asyncio</code> event loop in a separate thread, while executing the plotting operations on the main
thread.</p>

<p>Here’s an overview of how I implemented this approach:</p>

<p>The key to this setup was utilizing <code class="language-plaintext highlighter-rouge">zmq</code> to simulate the data communication between the BLE device and the plotting
application. For this, the <code class="language-plaintext highlighter-rouge">aiozmq</code> library provided the necessary asynchronous support for <code class="language-plaintext highlighter-rouge">zmq</code> communication.</p>

<p>You can find the complete implementation in my GitHub
repository: <a href="https://github.com/igorbasko01/async-plotting">async-plotting</a>.</p>

<p>Let’s briefly discuss the code structure:</p>

<p>First, we establish a new event loop and run it in a background thread, allowing the main thread to handle plotting:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">run_event_loop</span><span class="p">(</span><span class="n">loop</span><span class="p">):</span>
    <span class="n">asyncio</span><span class="p">.</span><span class="nf">set_event_loop</span><span class="p">(</span><span class="n">loop</span><span class="p">)</span>
    <span class="n">loop</span><span class="p">.</span><span class="nf">run_until_complete</span><span class="p">(</span><span class="nf">main</span><span class="p">())</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">'</span><span class="s">__main__</span><span class="sh">'</span><span class="p">:</span>
    <span class="n">new_loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">new_event_loop</span><span class="p">()</span>
    <span class="n">t</span> <span class="o">=</span> <span class="n">threading</span><span class="p">.</span><span class="nc">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">run_event_loop</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">new_loop</span><span class="p">,))</span>
    <span class="n">t</span><span class="p">.</span><span class="nf">start</span><span class="p">()</span>
    <span class="nf">plotter</span><span class="p">()</span>
    <span class="n">t</span><span class="p">.</span><span class="nf">join</span><span class="p">()</span>
</code></pre></div></div>

<p>Within the new event loop, <code class="language-plaintext highlighter-rouge">zmq</code> communication handles both publishing and subscribing tasks concurrently:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">publisher_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">create_task</span><span class="p">(</span><span class="nf">publish_messages</span><span class="p">())</span>
    <span class="n">subscriber_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">create_task</span><span class="p">(</span><span class="nf">subscribe_to_messages</span><span class="p">(</span><span class="n">update_data</span><span class="p">))</span>

    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">gather</span><span class="p">(</span><span class="n">publisher_task</span><span class="p">,</span> <span class="n">subscriber_task</span><span class="p">)</span>
</code></pre></div></div>

<p>The subscriber receives messages and updates the plotting data through a callback (update_data), which queues the data
for plotting:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plotting_queue</span> <span class="o">=</span> <span class="nc">Queue</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">update_data</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">):</span>
    <span class="n">plotting_queue</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="sh">'</span><span class="s">utf-8</span><span class="sh">'</span><span class="p">)))</span>
</code></pre></div></div>

<p>Finally, the <code class="language-plaintext highlighter-rouge">plotter</code> function, running on the main thread, continuously reads from the queue and updates the plot:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">plotter</span><span class="p">():</span>
    <span class="n">plt</span><span class="p">.</span><span class="nf">ion</span><span class="p">()</span>
    <span class="n">fig</span><span class="p">,</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">()</span>
    <span class="n">x_data</span><span class="p">,</span> <span class="n">y_data</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>

    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">plotting_queue</span><span class="p">.</span><span class="nf">empty</span><span class="p">():</span>
            <span class="n">message_int</span> <span class="o">=</span> <span class="n">plotting_queue</span><span class="p">.</span><span class="nf">get</span><span class="p">()</span>
            <span class="n">x_data</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">x_data</span><span class="p">))</span>
            <span class="n">y_data</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="n">message_int</span><span class="p">))</span>
            <span class="n">ax</span><span class="p">.</span><span class="nf">clear</span><span class="p">()</span>
            <span class="n">ax</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">x_data</span><span class="p">,</span> <span class="n">y_data</span><span class="p">)</span>
            <span class="n">plt</span><span class="p">.</span><span class="nf">draw</span><span class="p">()</span>
            <span class="n">plt</span><span class="p">.</span><span class="nf">pause</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
        <span class="n">plt</span><span class="p">.</span><span class="nf">pause</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># Small pause to prevent busy waiting.
</span></code></pre></div></div>

<p>This method uses <code class="language-plaintext highlighter-rouge">plt.ion()</code> for interactive plotting and periodic pauses to ensure the GUI remains responsive and the
plot updates smoothly.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The technique outlined here is adaptable and can serve as a blueprint for similar asynchronous data visualization tasks.
It highlights Python’s flexibility in addressing complex programming scenarios, offering a straightforward path for
those looking to balance asynchronous data streams with real-time plotting.</p>]]></content><author><name></name></author><category term="matplotlib" /><category term="python" /><category term="asyncio" /><summary type="html"><![CDATA[Overview In my recent project, I aimed to visualize data from a Bluetooth Low Energy (BLE) device, a task that required integrating different technologies. The bleak library was my choice for managing BLE communications in Python, known for its robust support for these devices.]]></summary></entry><entry><title type="html">Handling GPIOs Made Easy on OrangePI Android</title><link href="/orangepi/kotlin/gpio/2024/01/09/gpios-orangepi-android.html" rel="alternate" type="text/html" title="Handling GPIOs Made Easy on OrangePI Android" /><published>2024-01-09T22:00:00+00:00</published><updated>2024-01-09T22:00:00+00:00</updated><id>/orangepi/kotlin/gpio/2024/01/09/gpios-orangepi-android</id><content type="html" xml:base="/orangepi/kotlin/gpio/2024/01/09/gpios-orangepi-android.html"><![CDATA[<h2 id="preface">Preface</h2>
<p>Welcome to an exciting chapter in the journey of <a href="http://strikeco.io">Strikeco</a>, where innovation meets the world of tennis simulation. At <strong>Strikeco</strong>, we’re not just creating tennis simulators; we’re redefining the very essence of interactive sports technology. Our mission is to blend cutting-edge hardware with seamless software integration to bring you the future of tennis experiences.</p>

<p>In this blog post, we’ll dive into our latest exploration with the OrangePi 5B SBC. This piece of technology has emerged as a frontrunner in our pursuit of performance excellence and user-friendly design. As we navigate through the intricacies of hardware optimization, each step brings us closer to designing a custom SBC tailored perfectly for our final product.</p>

<p>Join us on this adventure, as we share our insights and breakthroughs with the OrangePi 5B. Whether you’re a tech enthusiast, a tennis aficionado, or simply curious about our process, there’s something here for everyone. We’re excited to give you a behind-the-scenes look at how we’re pushing the boundaries of tennis simulation technology at Strikeco.</p>

<h2 id="overview">Overview</h2>
<p>Recently, our team embarked on an innovative project, bridging the gap between hardware and software: we are developing a custom D-pad controller for a Unity-based game, tailored to run seamlessly on a Single Board Computer (SBC). Our hardware of choice for testing? The robust and cost-effective OrangePI.</p>

<p>For the operating system, we chose Android OS, a decision influenced by its seamless compatibility and impressive performance alongside the Unity game engine.</p>

<p>While the specifics of the game itself are intriguing, this blog post will primarily focus on the technical side of things. Specifically, we’ll explore how to handle GPIOs on the OrangePI Android OS and how this functionality can be integrated within a Unity game.</p>

<p>Join us at Strikeco as we delve into the intricacies of integrating physical game controls with advanced software. We’ll be sharing our insights and practical steps, guiding anyone interested in undertaking a similar endeavor in their Unity projects, especially those involving unconventional gaming setups.</p>

<h2 id="orangepi-background">OrangePi Background</h2>
<p>In our search for the ideal SBC for this project, we chose the OrangePi 5B. This decision was based on its impressive performance capabilities — in terms of both CPU and GPU — and its cost-effectiveness.</p>

<p>The OrangePi 5B is equipped with two 4-core CPUs and the ARM Mali-G610 GPU, a combination that offers substantial power for demanding applications. This power is further complemented by an onboard NPU, an exciting feature that opens doors for future projects, potentially involving advanced computing tasks like machine learning.</p>

<p>Another appealing aspect of the OrangePi 5B is its integrated WIFI and Bluetooth functionality. This not only allows for versatile communication options with the device but also paves the way for interesting future explorations in wireless connectivity.</p>

<p>The board typically runs OrangePi OS (Druid), an Android-based operating system. This was a pivotal factor in our decision, as we were planning to develop a game using Unity. The Android platform is renowned for its compatibility with Unity, which we anticipated would ensure a smoother development process and optimal performance for our game.</p>

<p>In summary, the OrangePi 5B emerged as an excellent choice for us — a robust, feature-rich board that’s competitively priced. It perfectly suits our current project requirements and offers great potential for future experimentation and development.</p>

<h2 id="wiringop">WiringOP</h2>
<p>WiringOP is a pre-installed Android application on the OrangePi Android OS, serving as a gateway to exploring the physical pins of the SBC. It provides functionalities to read and write to these pins, offering a visual representation of their states.</p>

<p>One notable feature of the WiringOP app is a button that displays the state of all the pins, essentially mirroring the output of the command-line instruction <code class="language-plaintext highlighter-rouge">gpiox readall</code>. The resulting display looks something like this:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------+-----+----------+------+---+  OPi H6  +---+------+----------+-----+------+
 | GPIO | wPi |   Name   | Mode | V | Physical | V | Mode | Name     | wPi | GPIO |
 +------+-----+----------+------+---+----++----+---+------+----------+-----+------+
 |      |     |     3.3V |      |   |  1 <span class="o">||</span> 2  |   |      | 5V       |     |      |
 |  230 |   0 |    SDA.1 |  OFF | 0 |  3 <span class="o">||</span> 4  |   |      | 5V       |     |      |
 |  229 |   1 |    SCL.1 |  OFF | 0 |  5 <span class="o">||</span> 6  |   |      | GND      |     |      |
 |  228 |   2 |     PWM1 |  OFF | 0 |  7 <span class="o">||</span> 8  | 0 | OFF  | PD21     | 3   | 117  |
 |      |     |      GND |      |   |  9 <span class="o">||</span> 10 | 0 | OFF  | PD22     | 4   | 118  |
 |  120 |   5 |    RXD.3 | ALT4 | 0 | 11 <span class="o">||</span> 12 | 0 | OFF  | PC09     | 6   | 73   |
 |  119 |   7 |    TXD.3 | ALT4 | 0 | 13 <span class="o">||</span> 14 |   |      | GND      |     |      |
 |  122 |   8 |    CTS.3 |  OFF | 0 | 15 <span class="o">||</span> 16 | 0 | OFF  | PC08     | 9   | 72   |
 |      |     |     3.3V |      |   | 17 <span class="o">||</span> 18 | 0 | OFF  | PC07     | 10  | 71   |
 |   66 |  11 |   MOSI.0 | ALT4 | 0 | 19 <span class="o">||</span> 20 |   |      | GND      |     |      |
 |   67 |  12 |   MISO.0 | ALT4 | 0 | 21 <span class="o">||</span> 22 | 0 | OFF  | RTS.3    | 13  | 121  |
 |   64 |  14 |   SCLK.0 | ALT4 | 0 | 23 <span class="o">||</span> 24 | 0 | ALT4 | CE.0     | 15  | 69   |
 |      |     |      GND |      |   | 25 <span class="o">||</span> 26 | 0 | OFF  | PH03     | 16  | 227  |
 +------+-----+----------+------+---+----++----+---+------+----------+-----+------+
 | GPIO | wPi |   Name   | Mode | V | Physical | V | Mode | Name     | wPi | GPIO |
 +------+-----+----------+------+---+  OPi H6  +---+------+----------+-----+------+
</code></pre></div></div>

<p>From this table, two columns are particularly important: <code class="language-plaintext highlighter-rouge">wPi</code> and <code class="language-plaintext highlighter-rouge">V</code>. The <code class="language-plaintext highlighter-rouge">wPi</code> number is crucial for interacting with the pins through software, as it’s used to reference a pin’s state. The <code class="language-plaintext highlighter-rouge">V</code> column, on the other hand, indicates whether current is flowing through a pin, which is vital for setting or resetting it.</p>

<p>After our team experimented with WiringOP, we realized that if the app could read and write to the pins, we should be able to replicate this functionality in our Android application. But how to achieve this was not immediately obvious.</p>

<p>Our initial search for resources and discussions online didn’t yield straightforward answers. Consequently, we took a more direct approach: delving into the source code of WiringOP itself. Discovering that WiringOP came pre-installed with OrangePi OS led us to believe it was bundled with the operating system. This spurred our team to delve into the depths of OrangePI’s Android OS source code, aiming to uncover the mechanisms behind WiringOP’s functionality.</p>

<h2 id="orangepi-android-os">OrangePi Android OS</h2>
<p>In our endeavor to establish a connection between our Android Kotlin code and the GPIO pins on the OrangePi board, we initially considered exploring the <a href="https://github.com/orangepi-xunlong/wiringOP">WiringOP GitHub repository</a>. This approach, however, proved challenging as the repository lacked clear instructions for compiling the code for Android integration, and we found no further insights in the issues section.</p>

<p>Confronted with limited options, our team decided to delve directly into the OrangePi Android OS source code, available on their official website. The source code is split into several <code class="language-plaintext highlighter-rouge">tar.gz</code> files, all housed on their Google Drive under the <a href="https://drive.google.com/drive/folders/14efL7SWZ68CZCbUayngLL4iAtGQoV9a0">RK3588S_Android_Source_Code</a> directory.</p>

<p>To start working with the code, the first step is to combine and extract these files. This involves:</p>

<ol>
  <li>Combining the files into a single archive: <code class="language-plaintext highlighter-rouge">cat Android_12.tar.gz* &gt; Android_12.tar.gz</code></li>
  <li>Extracting the contents: <code class="language-plaintext highlighter-rouge">tar -xvf Android_12.tar.gz</code></li>
</ol>

<p>For Windows users, the <a href="https://cygwin.com/">Cygwin</a> tool can provide the necessary <code class="language-plaintext highlighter-rouge">tar</code> command. Be prepared, as the extraction process is quite time-consuming and requires patience.</p>

<p>Once the extraction was complete, we began our search for the WiringOP application. As anticipated, it was located in the <code class="language-plaintext highlighter-rouge">packages/apps/WiringOP</code> directory. Here, not only is the application’s source code present, but also, crucially, the C source code of WiringOP, similar to what’s seen on GitHub. The key difference here is the presence of several <code class="language-plaintext highlighter-rouge">Android.mk</code> files – absent in the GitHub repository – which are essential for building the required libraries and files for any Android application looking to access the GPIOs on the board.</p>

<p>In the following section, we will take a closer look at these components to understand what is required for our project and how we can effectively utilize them.</p>

<h2 id="wiringop-internals">WiringOP Internals</h2>
<p>In the heart of the WiringOP application lies the process of building the necessary libraries – the <code class="language-plaintext highlighter-rouge">.so</code> files. The most straightforward method we found involves using Android Studio. Open the <code class="language-plaintext highlighter-rouge">packages/apps/WiringOP</code> directory in Android Studio and navigate to the <code class="language-plaintext highlighter-rouge">com.example.demo.MainActiviy</code> file. From there, execute the <code class="language-plaintext highlighter-rouge">Build-&gt;Make Module 'wiringOp.app.main'</code> command.</p>

<p>This process generates an <code class="language-plaintext highlighter-rouge">.apk</code> file, specifically <code class="language-plaintext highlighter-rouge">app-debug.apk</code>, located in the <code class="language-plaintext highlighter-rouge">build/outputs/apk/debug/</code> folder. The compiled libraries we need are tucked inside this <code class="language-plaintext highlighter-rouge">.apk</code>, under the <code class="language-plaintext highlighter-rouge">lib</code> folder. Within, you’ll find two subfolders: <code class="language-plaintext highlighter-rouge">armeabi-v7a</code> and <code class="language-plaintext highlighter-rouge">arm64-v8a</code>, each corresponding to a different ARM architecture. For our project, we chose <code class="language-plaintext highlighter-rouge">arm64-v8a</code>. This involved incorporating the entire folder into our Android project, specifically under the <code class="language-plaintext highlighter-rouge">src/main/jniLibs/</code> directory.</p>

<p>Having successfully built the WiringOP libraries, the next step is to establish a communication interface with them. In the WiringOP app, under the <code class="language-plaintext highlighter-rouge">com.example.wiringop</code> package, there’s a crucial file named <code class="language-plaintext highlighter-rouge">wpiControl</code>. It’s a class filled with static methods enabling interaction with the WiringOP libraries.</p>

<p>An important note here: when integrating <code class="language-plaintext highlighter-rouge">wpiControl</code> into your application, it’s essential to retain its original package structure, i.e., <code class="language-plaintext highlighter-rouge">com.example.wiringop</code>. While not aesthetically pleasing, this is necessary for the JNI (Java Native Interface) to locate the functions in the libraries. While it might be feasible to modify this using tools like <code class="language-plaintext highlighter-rouge">javac -h</code>, we didn’t explore this option for our current project.</p>

<p>Before integrating the wpiControl class into our application, there is an essential step to consider. The WiringPi library maps the memory directly to the pins, necessitating read and write permissions to the physical memory of the OrangePi device. This is achieved by executing <code class="language-plaintext highlighter-rouge">chmod 666 /dev/mem</code> before calling the <code class="language-plaintext highlighter-rouge">wiringPiSetup()</code> method. The most practical way to run this command is at your application’s startup.</p>

<p>A word of caution: the <code class="language-plaintext highlighter-rouge">chmod 666</code> command sets permissions that are not secure, especially for a production environment. While this approach was suitable for our initial testing, we recommend caution and encourage further research into more secure alternatives, especially in different or more extensive project contexts.</p>

<p>Now, let’s discuss how we integrate and utilize the <code class="language-plaintext highlighter-rouge">wpiControl</code> class in our Android application.</p>

<h2 id="our-project">Our Project</h2>
<p>So far in this post, I’ve referenced our project several times, and it’s time to delve deeper into its specifics. The project is a Unity game controlled by a D-pad, designed to run on the OrangePi5b with Android OS.</p>

<p>The aim was to make the D-pad function like a standard Android D-pad, recognized and handled by Unity without needing custom event monitoring. To achieve this, we utilized the Android <code class="language-plaintext highlighter-rouge">input</code> CLI command to simulate key presses.</p>

<p>Incorporating this functionality into Unity required creating an Android plugin. This plugin is essentially an Android module containing the compiled libraries and the <code class="language-plaintext highlighter-rouge">wpiControl</code> class from WiringOP, which interacts with the GPIOs and executes the <code class="language-plaintext highlighter-rouge">input</code> command.</p>

<p>The plugin works by starting a thread that constantly checks the GPIO pins. When it detects a button press on the D-pad, it triggers the corresponding <code class="language-plaintext highlighter-rouge">input</code> command to simulate a D-pad key press in Unity.</p>

<p>Here is an example command for simulating a DPAD_UP key press:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>input keyevent 19
</code></pre></div></div>

<p>We based our key simulations on the <code class="language-plaintext highlighter-rouge">KEYCODE_DPAD_*</code> constants found in the <code class="language-plaintext highlighter-rouge">android.view.KeyEvent</code> class of the Android SDK. The Android plugin was developed in Android Studio as an Android Library module, where we included the necessary <code class="language-plaintext highlighter-rouge">arm64-v8a</code> libraries and the <code class="language-plaintext highlighter-rouge">wpiControl</code> class.</p>

<p>For creating an Android plugin, we just need to create a new project in Android Studio, and create a new module of Android Library type.</p>

<p>Copy the <code class="language-plaintext highlighter-rouge">arm64-v8a</code> folder under the <code class="language-plaintext highlighter-rouge">src/main/jniLibs/</code> folder (create the <code class="language-plaintext highlighter-rouge">jniLibs</code> folder if it doesn’t exist).</p>

<p>Copy the <code class="language-plaintext highlighter-rouge">wpiControl</code> file, under the <code class="language-plaintext highlighter-rouge">src/main/java/</code> folder into the <code class="language-plaintext highlighter-rouge">com.example.wiringop</code> package.</p>

<p>The core of the plugin is the <code class="language-plaintext highlighter-rouge">UnityEntrypoint</code> class, which handles GPIO reading and triggers key events:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">UnityEntrypoint</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// The thread that will run our GPIO handling loop.</span>
    <span class="k">private</span> <span class="kd">var</span> <span class="py">executor</span> <span class="p">=</span> <span class="nc">Executors</span><span class="p">.</span><span class="nf">newSingleThreadExecutor</span><span class="p">()</span>

    <span class="c1">// This will start the main GPIO loop that handles reading from the pins and invoking the relevant key</span>
    <span class="k">fun</span> <span class="nf">start</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">executor</span><span class="p">.</span><span class="n">isShutdown</span><span class="p">)</span>
            <span class="n">executor</span> <span class="p">=</span> <span class="nc">Executors</span><span class="p">.</span><span class="nf">newSingleThreadExecutor</span><span class="p">()</span>
        <span class="n">executor</span><span class="p">.</span><span class="nf">execute</span> <span class="p">{</span>
            <span class="nf">mainGpioLoop</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">mainGpioLoop</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Prepare the necessary things for using the wiringPi library to read GPIOs.</span>
        <span class="nf">runRootCommand</span><span class="p">(</span><span class="s">"chmod 666 /dev/mem"</span><span class="p">)</span>
        <span class="n">wpiControl</span><span class="p">.</span><span class="nf">wiringPiSetup</span><span class="p">()</span>

        <span class="c1">// All the D-Pad related pins should be in the IN mode.</span>
        <span class="nf">initializeDpadKeysPins</span><span class="p">()</span>

        <span class="c1">// Start looping and handling GPIOs</span>
        <span class="k">while</span> <span class="p">(!</span><span class="nc">Thread</span><span class="p">.</span><span class="nf">currentThread</span><span class="p">().</span><span class="n">isInterrupted</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">handleKeysPress</span><span class="p">()</span>
            <span class="nc">Thread</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="c1">// Wait a bit before continuing to read from GPIOs.</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">initializeDpadKeysPins</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">dpadKeyPins</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">pin</span><span class="p">)</span> <span class="p">-&gt;</span> 
            <span class="n">wpiControl</span><span class="p">.</span><span class="nf">pinMode</span><span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">handleKeysPress</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">dpadKeyPins</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">pin</span><span class="p">)</span> <span class="p">-&gt;</span> 
            <span class="kd">val</span> <span class="py">pinValue</span> <span class="p">=</span> <span class="n">wpiControl</span><span class="p">.</span><span class="nf">digitalRead</span><span class="p">(</span><span class="n">pin</span><span class="p">)</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">pinValue</span> <span class="p">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="nf">invokeKey</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">invokeKey</span><span class="p">(</span><span class="n">dpadKey</span><span class="p">:</span> <span class="nc">DpadKey</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">keyCode</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">dpadKey</span><span class="p">)</span> <span class="p">{</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">UP</span> <span class="p">-&gt;</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="nc">KEYCODE_DPAD_UP</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">DOWN</span> <span class="p">-&gt;</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="nc">KEYCODE_DPAD_DOWN</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">LEFT</span> <span class="p">-&gt;</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="nc">KEYCODE_DPAD_LEFT</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">RIGHT</span> <span class="p">-&gt;</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="nc">KEYCODE_DPAD_RIGHT</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">ENTER</span> <span class="p">-&gt;</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="nc">KEYCODE_ENTER</span>
        <span class="p">}</span>
        <span class="kd">val</span> <span class="py">cmd</span> <span class="p">=</span> <span class="s">"input keyevent $keyCode"</span>
        <span class="c1">// This is a simplified approach, it might be better to also catch exceptions and print the output of the command to catch errors.</span>
        <span class="kd">val</span> <span class="py">process</span> <span class="p">=</span> <span class="nc">Runtime</span><span class="p">.</span><span class="nf">getRuntime</span><span class="p">().</span><span class="nf">exec</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
        <span class="n">process</span><span class="p">.</span><span class="nf">waitFor</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// This is basically running a command after switching to the root user.</span>
    <span class="k">private</span> <span class="k">fun</span> <span class="nf">runRootCommand</span><span class="p">(</span><span class="n">cmd</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Please note that it is a simplified example, and we might need to aslo implement output printing and exception handling.</span>
        <span class="kd">val</span> <span class="py">process</span> <span class="p">=</span> <span class="nc">Runtime</span><span class="p">.</span><span class="nf">getRuntime</span><span class="p">().</span><span class="nf">exec</span><span class="p">(</span><span class="s">"su"</span><span class="p">)</span>
        <span class="kd">val</span> <span class="py">dos</span> <span class="p">=</span> <span class="nc">DataOutputStream</span><span class="p">(</span><span class="n">process</span><span class="p">.</span><span class="n">outputStream</span><span class="p">)</span>
        <span class="n">dos</span><span class="p">.</span><span class="nf">writeBytes</span><span class="p">(</span><span class="s">"$cmd\n"</span><span class="p">)</span>
        <span class="n">dos</span><span class="p">.</span><span class="nf">flush</span><span class="p">()</span>
        <span class="n">dos</span><span class="p">.</span><span class="nf">writeBytes</span><span class="p">(</span><span class="s">"exit\n"</span><span class="p">)</span>
        <span class="n">dos</span><span class="p">.</span><span class="nf">flush</span><span class="p">()</span>
        <span class="n">process</span><span class="p">.</span><span class="nf">waitFor</span><span class="p">()</span>
        <span class="n">dos</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Stops the thread</span>
    <span class="k">fun</span> <span class="nf">stop</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">executor</span><span class="p">.</span><span class="nf">shutdown</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
        <span class="k">enum</span> <span class="kd">class</span> <span class="nc">DpadKey</span> <span class="p">{</span>
            <span class="nc">UP</span><span class="p">,</span>
            <span class="nc">DOWN</span><span class="p">,</span>
            <span class="nc">LEFT</span><span class="p">,</span>
            <span class="nc">RIGHT</span><span class="p">,</span>
            <span class="nc">ENTER</span>
        <span class="p">}</span>

        <span class="c1">// Here we should adjust the pin numbers, to correspond to the D-Pad buttons.</span>
        <span class="kd">val</span> <span class="py">dpadKeyPins</span> <span class="p">=</span> <span class="nf">mapOf</span><span class="p">(</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">UP</span> <span class="n">to</span> <span class="mi">4</span><span class="p">,</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">DOWN</span> <span class="n">to</span> <span class="mi">9</span><span class="p">,</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">LEFT</span> <span class="n">to</span> <span class="mi">6</span><span class="p">,</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">RIGHT</span> <span class="n">to</span> <span class="mi">3</span><span class="p">,</span>
            <span class="nc">DpadKey</span><span class="p">.</span><span class="nc">ENTER</span> <span class="n">to</span> <span class="mi">10</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Building this module in Android Studio generates an <code class="language-plaintext highlighter-rouge">.aar</code> file, located in the <code class="language-plaintext highlighter-rouge">build/outputs/aar/</code> folder. This file is then integrated into the Unity project, a process we will describe in the next section.</p>

<h2 id="unity">Unity</h2>
<p>With our Android plugin now ready, the next step is to integrate it into our Unity game. Unity handles Android plugins through the <code class="language-plaintext highlighter-rouge">AndroidJavaObject</code>, a powerful interface for interacting with Java objects. More information on this can be found in the <a href="https://docs.unity3d.com/Manual/android-plugins-java-code-from-c-sharp.html">Unity documentation</a>.</p>

<p>The key is to create a C# class within Unity that initializes and controls the <code class="language-plaintext highlighter-rouge">UnityEntrypoint</code> class we developed in the Android plugin. This class will manage the starting and stopping of our GPIO listening loop.</p>

<p>Here’s an example of how this can be implemented in Unity:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">AndroidInputSimulator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="n">AndroidJavaObject</span> <span class="n">_plugin</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Initialize the Android plugin</span>
        <span class="n">_plugin</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AndroidJavaObject</span><span class="p">(</span><span class="s">"our.package.UnityEntrypoint"</span><span class="p">);</span>
        <span class="c1">// Start the GPIO loop</span>
        <span class="n">_plugin</span><span class="p">.</span><span class="nf">Call</span><span class="p">(</span><span class="s">"start"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Stop</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Stop the GPIO loop</span>
        <span class="n">_plugin</span><span class="p">?.</span><span class="nf">Call</span><span class="p">(</span><span class="s">"stop"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>This <code class="language-plaintext highlighter-rouge">AndroidInputSimulator</code> class can be used anywhere in our Unity game to start listening to GPIO inputs. When the <code class="language-plaintext highlighter-rouge">Start</code> method is called, it initiates the <code class="language-plaintext highlighter-rouge">mainGpioLoop</code> in a separate thread on the Android device, which listens for GPIO changes and simulates key presses. These simulated key presses are then recognized by Unity’s input system as if they were regular key presses.</p>

<p>To build the game’s <code class="language-plaintext highlighter-rouge">.apk</code> file for deployment, we need to adjust a few settings in Unity’s player settings under the Android tab. Specifically, in the Configuration section, make the following updates:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Scripting Backend = IL2CPP
Target Architectures = ARM64 (only)
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ARM64</code> architecture setting is crucial because our Android library is built for <code class="language-plaintext highlighter-rouge">arm64-v8a</code>. The <code class="language-plaintext highlighter-rouge">IL2CPP</code> scripting backend is required since Unity cannot use <code class="language-plaintext highlighter-rouge">ARM64</code> with the <code class="language-plaintext highlighter-rouge">mono</code> backend.</p>

<p>With these settings configured, you can now build and run the <code class="language-plaintext highlighter-rouge">.apk</code> on your device, and the game should respond to the D-pad inputs as intended.</p>

<h2 id="conclusion">Conclusion</h2>
<p>In this post, we’ve journeyed through the intricate process of integrating a custom D-pad controller with a Unity game on an OrangePI SBC running Android OS. Let’s briefly recap the key steps we undertook:</p>

<ol>
  <li>
    <p><strong>Choosing the Hardware</strong>: We started by selecting the OrangePI 5B for its impressive performance and cost-effectiveness, coupled with its compatibility with Android OS – a prime choice for Unity game development.</p>
  </li>
  <li>
    <p><strong>Exploring WiringOP</strong>: We delved into the WiringOP application, which offered a gateway to interact with the GPIOs on the OrangePI. This involved understanding its functionality and the crucial <code class="language-plaintext highlighter-rouge">wPi</code> and <code class="language-plaintext highlighter-rouge">V</code> columns in its GPIO table.</p>
  </li>
  <li>
    <p><strong>Diving into OrangePI’s Android OS</strong>: Faced with limited resources online, we explored the OrangePI Android OS source code to understand and replicate the functionality of WiringOP in our own Android application.</p>
  </li>
  <li>
    <p><strong>Building the Android Plugin</strong>: We created an Android plugin in Android Studio, incorporating the WiringOP libraries and the <code class="language-plaintext highlighter-rouge">wpiControl</code> class. This plugin was essential in reading GPIO inputs and simulating key presses using the <code class="language-plaintext highlighter-rouge">input</code> CLI command.</p>
  </li>
  <li>
    <p><strong>Integrating with Unity</strong>: In Unity, we used the <code class="language-plaintext highlighter-rouge">AndroidJavaObject</code> to integrate our Android plugin, allowing us to control the game with the custom D-pad. We also discussed the necessary Unity player settings for building the game’s <code class="language-plaintext highlighter-rouge">.apk</code> file.</p>
  </li>
</ol>

<p>As we look to the future, one promising avenue to explore is integrating this custom D-pad setup with Unity’s new Input System. This modern, flexible system offers more options for input configuration and might provide an even smoother integration process and enhanced gameplay experience. It’s an exciting prospect for further enhancing the interactivity of games on platforms like OrangePI.</p>

<p>Embarking on a project that intertwines hardware and software aspects can be challenging but equally rewarding. Through this journey, we’ve seen how creativity and perseverance can lead to innovative solutions, broadening the horizons of what’s possible in game development and hardware interaction.</p>]]></content><author><name></name></author><category term="orangepi" /><category term="kotlin" /><category term="gpio" /><summary type="html"><![CDATA[Preface Welcome to an exciting chapter in the journey of Strikeco, where innovation meets the world of tennis simulation. At Strikeco, we’re not just creating tennis simulators; we’re redefining the very essence of interactive sports technology. Our mission is to blend cutting-edge hardware with seamless software integration to bring you the future of tennis experiences.]]></summary></entry><entry><title type="html">go/links Webserver Initial Implementation</title><link href="/python/webserver/fastapi/golinks/2022/02/08/golinks-webserver-initial-implementation.html" rel="alternate" type="text/html" title="go/links Webserver Initial Implementation" /><published>2022-02-08T22:00:00+00:00</published><updated>2022-02-08T22:00:00+00:00</updated><id>/python/webserver/fastapi/golinks/2022/02/08/golinks-webserver-initial-implementation</id><content type="html" xml:base="/python/webserver/fastapi/golinks/2022/02/08/golinks-webserver-initial-implementation.html"><![CDATA[<p><em>This is the second part of my project of implementing a go/links system.
You can find the first post in the series <a href="/python/database/golinks/2022/02/05/golinks-clone-design.html">here</a>.</em></p>
<h2 id="overview">Overview</h2>
<p>My overall plan in implementing the go/links solution was to validate that 
I’m able to route requests to the webserver from the browser and that it
would redirect my request to some website, and I will get the result in 
the browser.</p>

<p>I wanted to make that validation as fast as possible, so I wanted to start
with the most basic and simple webserver that I can build. For that I went
with <a href="https://fastapi.tiangolo.com/">FastApi</a>, and created a very basic 
webserver.</p>

<h2 id="some-tdd">Some TDD</h2>
<p>Lately I’ve started using the TDD (Test Driven Development) process more
often. As I saw that it helps me to design pretty good applications, and
also add some tests that would allow to refactor the code later with a 
peace of mind, as I already had tests to cover the areas that I change
and still be somewhat sure that the behavior is correct.</p>

<p>I’ve created a project library, added there the following <code class="language-plaintext highlighter-rouge">requirements.txt</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fastapi==0.72
uvicorn[standard]==0.17
requests==2.27.1
pytest==6.2.5
</code></pre></div></div>
<p>Created a virtual environment and installed the requirements there.</p>

<p>From there I’ve started with the tests, instead of starting with the 
implementation of the webserver.</p>

<p>I’ve created two tests that would describe the behavior that I wanted
from my basic webserver at this stage of the project. The tests were:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">test_get_link_not_found</span><span class="p">():</span>
    <span class="bp">...</span>

<span class="k">def</span> <span class="nf">test_get_links_no_links</span><span class="p">():</span>
    <span class="bp">...</span>
</code></pre></div></div>

<p>In the first test, I test what should happen if a link was requested but
it wasn’t found.</p>

<p>And in the second test I make sure that a GET ALL links request returns 
an empty list of links.</p>

<p>These behaviors are good enough behaviors to see that the webserver sends
some responses.</p>

<p>Filling those tests using the <a href="https://fastapi.tiangolo.com/tutorial/testing/#using-testclient">FastAPI test example</a> 
was pretty easy. The whole file eventually looked like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">fastapi.testclient</span> <span class="kn">import</span> <span class="n">TestClient</span>
<span class="kn">from</span> <span class="n">golinks.main</span> <span class="kn">import</span> <span class="n">app</span>

<span class="n">client</span> <span class="o">=</span> <span class="nc">TestClient</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">test_get_link_not_found</span><span class="p">():</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">/links/non_existent_link</span><span class="sh">'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">404</span>
    <span class="k">assert</span> <span class="n">response</span><span class="p">.</span><span class="nf">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="sh">'</span><span class="s">detail</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">Link non_existent_link not found</span><span class="sh">'</span><span class="p">}</span>


<span class="k">def</span> <span class="nf">test_get_links_no_links</span><span class="p">():</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">/links</span><span class="sh">'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
    <span class="k">assert</span> <span class="n">response</span><span class="p">.</span><span class="nf">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="sh">'</span><span class="s">links</span><span class="sh">'</span><span class="p">:</span> <span class="p">[]}</span>
</code></pre></div></div>
<p>At this point of course the tests couldn’t run, as we didn’t have some of the 
components specifically the <code class="language-plaintext highlighter-rouge">app</code>.
But we already had some tests, and we knew what was missing, so we can now
create our webserver (the <code class="language-plaintext highlighter-rouge">app</code>).</p>

<p>Creating a FastAPI app is a breeze, we just need to instantiate it, like so:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>

<span class="n">app</span> <span class="o">=</span> <span class="nc">FastAPI</span><span class="p">()</span>
</code></pre></div></div>

<p>And now we just need to add the endpoints that we need, such as endpoints for:</p>
<ol>
  <li>Fetching all the links</li>
  <li>Fetching specific information about a link</li>
</ol>

<p>The whole code for the webserver for now is as follows:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span><span class="p">,</span> <span class="n">HTTPException</span>

<span class="n">app</span> <span class="o">=</span> <span class="nc">FastAPI</span><span class="p">()</span>


<span class="nd">@app.get</span><span class="p">(</span><span class="sh">'</span><span class="s">/links</span><span class="sh">'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_all_links</span><span class="p">():</span>
    <span class="k">return</span> <span class="p">{</span><span class="sh">'</span><span class="s">links</span><span class="sh">'</span><span class="p">:</span> <span class="p">[]}</span>


<span class="nd">@app.get</span><span class="p">(</span><span class="sh">'</span><span class="s">/links/{link_id}</span><span class="sh">'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_link</span><span class="p">(</span><span class="n">link_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="k">raise</span> <span class="nc">HTTPException</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">404</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="sa">f</span><span class="sh">'</span><span class="s">Link </span><span class="si">{</span><span class="n">link_id</span><span class="si">}</span><span class="s"> not found</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div></div>
<p>We can see that we allow two endpoints a <code class="language-plaintext highlighter-rouge">/links/</code> and a <code class="language-plaintext highlighter-rouge">/links/{link_id}</code>.
The first one is without a parameter and we expect it to return all the links
that we have stored in the database, but for now we have nothing, as we don’t 
even have a database, so it just returns an empty list of links.</p>

<p>The second one is the one that should just return information about the link.
But because we still haven’t implemented any storage for that and we for sure
have no links to return, it just raises a not found HTTP status code.</p>

<p>Now we can run our tests and see if they work as expected using <code class="language-plaintext highlighter-rouge">pytest</code> like
that:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">PYTHONPATH</span><span class="o">=</span><span class="nb">.</span> pytest
</code></pre></div></div>

<p>And we can even run the webserver and query it by running:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uvicorn golinks.main:app
</code></pre></div></div>
<p>By default, it should bind the webserver to port 8000, so sending requests
to it should return the expected results. For example:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-v</span> http://127.0.0.1:8000/links/some_link

...
&lt; HTTP/1.1 404 Not Found
...
<span class="o">{</span><span class="s2">"detail"</span>:<span class="s2">"Link some_link not found"</span><span class="o">}</span>
</code></pre></div></div>
<p>Or if trying the <code class="language-plaintext highlighter-rouge">/links</code> endpoint:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://127.0.0.1:8000/links
 
<span class="o">{</span><span class="s2">"links"</span>:[]<span class="o">}</span>
</code></pre></div></div>

<h2 id="next-steps">Next Steps</h2>
<p>For now this functionality is enough, for the next time, I would like to
check if using the browser with the a <code class="language-plaintext highlighter-rouge">go/</code> domain works, and does it pass
requests to the webserver.</p>

<p>Thanks for reading and see you next time.</p>]]></content><author><name></name></author><category term="python" /><category term="webserver" /><category term="fastapi" /><category term="golinks" /><summary type="html"><![CDATA[This is the second part of my project of implementing a go/links system. You can find the first post in the series here. Overview My overall plan in implementing the go/links solution was to validate that I’m able to route requests to the webserver from the browser and that it would redirect my request to some website, and I will get the result in the browser.]]></summary></entry><entry><title type="html">go/links clone design</title><link href="/python/database/golinks/2022/02/05/golinks-clone-design.html" rel="alternate" type="text/html" title="go/links clone design" /><published>2022-02-05T22:00:00+00:00</published><updated>2022-02-05T22:00:00+00:00</updated><id>/python/database/golinks/2022/02/05/golinks-clone-design</id><content type="html" xml:base="/python/database/golinks/2022/02/05/golinks-clone-design.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>I have an old itch that always was in the back of my mind for years, where I
wanted to understand how databases work under the hood.</p>

<p>Of course there are a lot of different types of databases, and each one 
contains a lot of different components. So it is a lot of ground to cover.
Instead of just reading about them, I also want to implement bits and pieces.</p>

<p>So I decided that I should start working on some project that I can benefit
from, and also implement some aspects of a database. I thought about starting
with some simple implementation of a key-value store. The project that I think
that will benefit of such a database is a clone of the go/links system.</p>

<p>Basically a go/links system, is a sort of bookmarking application for your browser,
where when you input into the address bar some address that starts with <code class="language-plaintext highlighter-rouge">go/</code> and 
a keyword, it will redirect the browser to some pre-stored site for that keyword. 
For example it is possible to define that <code class="language-plaintext highlighter-rouge">go/facebook</code> will take you to <code class="language-plaintext highlighter-rouge">www.facebook.com</code>,
Or to any specific link that you desire.</p>

<h2 id="design">Design</h2>
<p>The basic components that I would need for the architecture of the system, are:</p>
<ol>
  <li>Webserver</li>
  <li>Database</li>
</ol>

<p>The Database will store the key-value items, where the key would be the link keyword, 
for example <code class="language-plaintext highlighter-rouge">facebook</code>. And the value would be the underlying link, <code class="language-plaintext highlighter-rouge">www.facebook.com</code> for example.
I will write the database from scratch in Python, as this is really what I want to practice. 
I don’t look for great performance but mostly practicing concepts.</p>

<p>Basically it will hold an in memory dictionary, that will be flushed to disk and initialized from
disk. I will use <a href="https://developers.google.com/protocol-buffers" target="_blank">Protocol Buffers</a> 
for the schema of the items that will be stored to disk. It allows serializing the data to a more
compact representation, and also provide schema evolution if changes will occur in the future.</p>

<p>The Webserver will receive the requests of the <code class="language-plaintext highlighter-rouge">go/</code> type, and redirect to the underlying value
that is stored in the database.
It will also allow creating and updating links.
I will use <a href="https://fastapi.tiangolo.com/" target="_blank">FastAPI</a> framework for the server.</p>

<p>In order to direct <code class="language-plaintext highlighter-rouge">go/</code> requests to the webserver, I will update the <code class="language-plaintext highlighter-rouge">/etc/hosts</code> file.</p>

<h2 id="next-steps">Next Steps</h2>
<p>In my next posts I will take the implementation step by step, and describe how I implement 
each part of the system, so you could try and replicate and even do better.</p>

<p>Thanks for reading, and see you soon.</p>

<p><a href="/python/webserver/fastapi/golinks/2022/02/08/golinks-webserver-initial-implementation.html">Next post</a> in the series.</p>]]></content><author><name></name></author><category term="python" /><category term="database" /><category term="golinks" /><summary type="html"><![CDATA[Overview I have an old itch that always was in the back of my mind for years, where I wanted to understand how databases work under the hood.]]></summary></entry><entry><title type="html">Arithmetic AST with fastparse</title><link href="/scala/fastparse/2021/11/10/fastparse-arithmetic-dsl.html" rel="alternate" type="text/html" title="Arithmetic AST with fastparse" /><published>2021-11-10T22:00:00+00:00</published><updated>2021-11-10T22:00:00+00:00</updated><id>/scala/fastparse/2021/11/10/fastparse-arithmetic-dsl</id><content type="html" xml:base="/scala/fastparse/2021/11/10/fastparse-arithmetic-dsl.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>In our Data Quality platform, we allow users to onboard different types of datasets.
One of our initial goals on the platform was to provide the ability to write
expectations for those datasets, which in turn help to determine the quality of
the dataset.</p>

<p>The expectations are using the metrics that our platform produces for the datasets.
Example metrics that the platform can produce for each dataset:</p>
<ul>
  <li>Number of rows.</li>
  <li>Number of rows with null values in specific column.</li>
  <li>Average/Min/Max number of items in a column of type collection.</li>
</ul>

<p>Each metric would return some number, in consequence our expectations
should be able to do some arithmetic expression on those metrics.</p>

<p>A sample expression could look like: <code class="language-plaintext highlighter-rouge">{null_email}/{count_rows}</code></p>

<p>The <code class="language-plaintext highlighter-rouge">{null_email}</code> placeholder expresses the amount of rows with a <code class="language-plaintext highlighter-rouge">null</code> value in the
<code class="language-plaintext highlighter-rouge">email</code> column, and <code class="language-plaintext highlighter-rouge">{count_rows}</code> expresses the amount of rows in the dataset.
Using the above expression, we get the proportion of rows that have null values
in the email column. On the result of that expression the user can define a threshold
which will describe the expectation.</p>

<p>For example: <code class="language-plaintext highlighter-rouge">{null_email}/{count_rows} &lt; 0.05</code></p>

<p>From the above expectation, the user expects that the dataset should contain less
then 5% of rows with null value in the email column.</p>

<p>The users would provide these expectations through an API that the platform exposed.
For an easier usage for the user, we allowed them to pass the expression as simple
string, and the service would parse and evaluate it.</p>

<p>But which parser should I use for parsing and evaluating?</p>

<h2 id="parsers">Parsers</h2>
<p>For implementation simplicity I chose to use the <a href="https://github.com/j-easy/easy-rules">jeasy/easy-rules</a>
repository with the MVEL expression language parser. And it worked pretty well.
It allowed me to parse and evaluate the whole expectation out of the box.
But naturally the requirements evolved and we wanted to introduce extra features in the expression itself,
and wanted some more control over the expression language.</p>

<p>As a consequence of the changed requirements, I have started looking for other
libraries that would provide me with more control over the parsing of the expressions.
Our main language is Scala, so I looked for a Scala library that I could have used.</p>

<p>The first option that pops in, is using the <a href="https://github.com/scala/scala-parser-combinators">scala-parser-combinators</a>
library, as once it was a built-in in the standard Scala library. But after investigating
a bit more, it seemed that it was a pretty slow library, performance wise, so I kept
looking, and found the <a href="https://github.com/com-lihaoyi/fastparse">FastParse</a> parsing library by Li Haoyi.
The first example of the library was an arithmetic parser, so it was somewhat a breeze
to create the parser that I wanted. The main caveat was, that I didn’t want to evaluate
the expression directly when parsed, but instead to create an AST, which I could
evaluate later when providing the relevant metrics.</p>

<p>With a bit of manipulation I was able to create a parser that creates the AST, and
the AST itself would be able to evaluate itself when needed.</p>

<h2 id="the-solution">The Solution</h2>
<p>First of all I defined the Expressions that I would expect.</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="k">trait</span> <span class="nc">Expression</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Div</span><span class="o">(</span><span class="n">r</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">,</span> <span class="n">l</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Expression</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Mul</span><span class="o">(</span><span class="n">r</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">,</span> <span class="n">l</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Expression</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Add</span><span class="o">(</span><span class="n">r</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">,</span> <span class="n">l</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Expression</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Sub</span><span class="o">(</span><span class="n">r</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">,</span> <span class="n">l</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Expression</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Num</span><span class="o">(</span><span class="n">value</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Expression</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Metric</span><span class="o">(</span><span class="n">id</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Expression</span>
</code></pre></div></div>
<p>It contains the four basic arithmetic expressions, an expression that
represents a number and an expression that represents a Metric.</p>

<p>After that I have defined the actual Fastparse patterns that would map onto
those expressions.</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">number</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="nc">CharIn</span><span class="o">(</span><span class="s">"0-9"</span><span class="o">).</span><span class="py">rep</span><span class="o">(</span><span class="mi">1</span><span class="o">)).!.</span><span class="py">map</span><span class="o">(</span><span class="nc">Num</span><span class="o">)</span>
<span class="k">def</span> <span class="nf">metric</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="s">"{"</span> <span class="o">~</span> <span class="n">number</span><span class="o">.!</span> <span class="o">~</span> <span class="s">"}"</span><span class="o">).</span><span class="py">map</span><span class="o">(</span><span class="nc">Metric</span><span class="o">)</span>
<span class="k">def</span> <span class="nf">factor</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="n">number</span> <span class="o">|</span> <span class="n">metric</span> <span class="o">|</span> <span class="n">parens</span><span class="o">)</span>
<span class="k">def</span> <span class="nf">parens</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="s">"("</span> <span class="o">~/</span> <span class="n">addSub</span> <span class="o">~</span> <span class="s">")"</span><span class="o">)</span>

<span class="k">def</span> <span class="nf">divMul</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="n">factor</span> <span class="o">~</span> <span class="o">(</span><span class="nc">CharIn</span><span class="o">(</span><span class="s">"*/"</span><span class="o">).!</span> <span class="o">~/</span> <span class="n">factor</span><span class="o">).</span><span class="py">rep</span><span class="o">).</span><span class="py">map</span><span class="o">((</span><span class="n">astBuilder</span> <span class="k">_</span><span class="o">).</span><span class="py">tupled</span><span class="o">)</span>
<span class="k">def</span> <span class="nf">addSub</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="n">divMul</span> <span class="o">~</span> <span class="o">(</span><span class="nc">CharIn</span><span class="o">(</span><span class="s">"+\\-"</span><span class="o">).!</span> <span class="o">~/</span> <span class="n">divMul</span><span class="o">).</span><span class="py">rep</span><span class="o">).</span><span class="py">map</span><span class="o">((</span><span class="n">astBuilder</span> <span class="k">_</span><span class="o">).</span><span class="py">tupled</span><span class="o">)</span>
<span class="k">def</span> <span class="nf">expr</span><span class="o">[</span><span class="k">_</span><span class="kt">:</span> <span class="kt">P</span><span class="o">]</span><span class="k">:</span> <span class="kt">P</span><span class="o">[</span><span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span> <span class="nf">P</span><span class="o">(</span><span class="n">addSub</span> <span class="o">~</span> <span class="nc">End</span><span class="o">)</span>
</code></pre></div></div>
<p>The patterns are basically the arithmetic example pattern from the
fastparse <a href="https://com-lihaoyi.github.io/fastparse/">documentation</a>
But instead of evaluating the result while parsing, it builds the AST.
For the simple metric and number patterns, I map the parsed result directly on
the relevant Expressions.</p>

<p>For the Arithmetic patterns (divMul and addSub) I modified a bit the example
to create the relevant Expressions</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">astBuilder</span><span class="o">(</span><span class="n">initial</span><span class="k">:</span> <span class="kt">Expression</span><span class="o">,</span> <span class="n">rest</span><span class="k">:</span> <span class="kt">Seq</span><span class="o">[(</span><span class="kt">String</span>, <span class="kt">Expression</span><span class="o">)])</span><span class="k">:</span> <span class="kt">Expression</span> <span class="o">=</span> <span class="o">{</span>
    <span class="nv">rest</span><span class="o">.</span><span class="py">foldLeft</span><span class="o">(</span><span class="n">initial</span><span class="o">)</span> <span class="o">{</span>
      <span class="nf">case</span> <span class="o">(</span><span class="n">left</span><span class="o">,</span> <span class="o">(</span><span class="n">operator</span><span class="o">,</span> <span class="n">right</span><span class="o">))</span> <span class="k">=&gt;</span>
        <span class="n">operator</span> <span class="k">match</span> <span class="o">{</span>
          <span class="k">case</span> <span class="s">"*"</span> <span class="k">=&gt;</span> <span class="nc">Mul</span><span class="o">(</span><span class="n">left</span><span class="o">,</span> <span class="n">right</span><span class="o">)</span>
          <span class="k">case</span> <span class="s">"/"</span> <span class="k">=&gt;</span> <span class="nc">Div</span><span class="o">(</span><span class="n">left</span><span class="o">,</span> <span class="n">right</span><span class="o">)</span>
          <span class="k">case</span> <span class="s">"+"</span> <span class="k">=&gt;</span> <span class="nc">Add</span><span class="o">(</span><span class="n">left</span><span class="o">,</span> <span class="n">right</span><span class="o">)</span>
          <span class="k">case</span> <span class="s">"-"</span> <span class="k">=&gt;</span> <span class="nc">Sub</span><span class="o">(</span><span class="n">left</span><span class="o">,</span> <span class="n">right</span><span class="o">)</span>
        <span class="o">}</span>
    <span class="o">}</span>
  <span class="o">}</span>
</code></pre></div></div>

<p>And if we run some tests, we can see the following results</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"the parser"</span> <span class="n">should</span> <span class="s">"create an AST of only numeric expression"</span> <span class="n">in</span> <span class="o">{</span>
    <span class="k">val</span> <span class="nv">expression</span> <span class="k">=</span> <span class="s">"3/2"</span>
    <span class="k">val</span> <span class="nv">ast</span> <span class="k">=</span> <span class="nv">Parser</span><span class="o">.</span><span class="py">parse</span><span class="o">(</span><span class="n">expression</span><span class="o">)</span>
    <span class="n">ast</span> <span class="n">shouldEqual</span> <span class="nc">Right</span><span class="o">(</span><span class="nc">Div</span><span class="o">(</span><span class="nc">Num</span><span class="o">(</span><span class="s">"3"</span><span class="o">),</span> <span class="nc">Num</span><span class="o">(</span><span class="s">"2"</span><span class="o">)))</span>
  <span class="o">}</span>

  <span class="n">it</span> <span class="n">should</span> <span class="s">"create an AST of mixed operators"</span> <span class="n">in</span> <span class="o">{</span>
    <span class="k">val</span> <span class="nv">expression</span> <span class="k">=</span> <span class="s">"3+4*5"</span>
    <span class="nv">Parser</span><span class="o">.</span><span class="py">parse</span><span class="o">(</span><span class="n">expression</span><span class="o">)</span> <span class="n">shouldEqual</span> <span class="nc">Right</span><span class="o">(</span><span class="nc">Add</span><span class="o">(</span><span class="nc">Num</span><span class="o">(</span><span class="s">"3"</span><span class="o">),</span> <span class="nc">Mul</span><span class="o">(</span><span class="nc">Num</span><span class="o">(</span><span class="s">"4"</span><span class="o">),</span> <span class="nc">Num</span><span class="o">(</span><span class="s">"5"</span><span class="o">))))</span>
  <span class="o">}</span>

  <span class="n">it</span> <span class="n">should</span> <span class="s">"create an AST with a metric"</span> <span class="n">in</span> <span class="o">{</span>
    <span class="k">val</span> <span class="nv">expression</span> <span class="k">=</span> <span class="s">"3+{4}/5"</span>
    <span class="nv">Parser</span><span class="o">.</span><span class="py">parse</span><span class="o">(</span><span class="n">expression</span><span class="o">)</span> <span class="n">shouldEqual</span> <span class="nc">Right</span><span class="o">(</span><span class="nc">Add</span><span class="o">(</span><span class="nc">Num</span><span class="o">(</span><span class="s">"3"</span><span class="o">),</span> <span class="nc">Div</span><span class="o">(</span><span class="nc">Metric</span><span class="o">(</span><span class="s">"4"</span><span class="o">),</span> <span class="nc">Num</span><span class="o">(</span><span class="s">"5"</span><span class="o">))))</span>
  <span class="o">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">Parser</code> object is just a wrapper for the fastparse parser, it just converts the
parsing result into an <code class="language-plaintext highlighter-rouge">Either</code>.</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">object</span> <span class="nc">Parser</span> <span class="o">{</span>
  <span class="k">type</span> <span class="kt">Error</span> <span class="o">=</span> <span class="nc">String</span>
  <span class="k">def</span> <span class="nf">parse</span><span class="o">(</span><span class="n">expression</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span><span class="k">:</span> <span class="kt">Either</span><span class="o">[</span><span class="kt">Error</span>, <span class="kt">Expression</span><span class="o">]</span> <span class="k">=</span>
    <span class="nv">fastparse</span><span class="o">.</span><span class="py">parse</span><span class="o">(</span><span class="n">expression</span><span class="o">,</span> <span class="nv">Patterns</span><span class="o">.</span><span class="py">expr</span><span class="o">(</span><span class="k">_</span><span class="o">))</span>
      <span class="o">.</span><span class="py">fold</span><span class="o">((</span><span class="n">e</span><span class="o">,</span> <span class="k">_</span><span class="o">,</span> <span class="k">_</span><span class="o">)</span> <span class="k">=&gt;</span> <span class="nc">Left</span><span class="o">(</span><span class="n">e</span><span class="o">),</span> <span class="o">(</span><span class="n">s</span><span class="o">,</span> <span class="k">_</span><span class="o">)</span> <span class="k">=&gt;</span> <span class="nc">Right</span><span class="o">(</span><span class="n">s</span><span class="o">))</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="next-steps">Next steps</h2>
<p>From that simple example we were able to add more “Functions” to the expression
that would be part of the syntax that we support in our Data Quality Expectations.
And that allows us to customize the language of the expectations according to
our user needs.</p>

<p>Thanks for reading !</p>]]></content><author><name></name></author><category term="scala" /><category term="fastparse" /><summary type="html"><![CDATA[Overview In our Data Quality platform, we allow users to onboard different types of datasets. One of our initial goals on the platform was to provide the ability to write expectations for those datasets, which in turn help to determine the quality of the dataset.]]></summary></entry><entry><title type="html">An example of Pureconfig’s Sealed Families</title><link href="/scala/pureconfig/2021/07/06/Pureconfig-type-system.html" rel="alternate" type="text/html" title="An example of Pureconfig’s Sealed Families" /><published>2021-07-06T22:00:00+00:00</published><updated>2021-07-06T22:00:00+00:00</updated><id>/scala/pureconfig/2021/07/06/Pureconfig-type-system</id><content type="html" xml:base="/scala/pureconfig/2021/07/06/Pureconfig-type-system.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>In this post I will show, how we leveraged <a href="https://pureconfig.github.io/">PureConfig</a>, and were able to use
its <a href="https://pureconfig.github.io/docs/overriding-behavior-for-sealed-families.html">Sealed Families</a>
feature in order to have multiple configuration options
for the same resource.</p>

<p>Or more specifically, how we allowed to pass in our configuration,
different types of authentication configurations when creating an
RDS connection configuration.</p>

<h2 id="what-is-pureconfig">What is Pureconfig</h2>
<p>PureConfig is Github’s library for basically converting configuration files
into Scala Classes, more specifically and common, into case classes.</p>

<p>It is pretty simple to load a Typesafe supported configuration file into a case class using that
library, as simple as the following configuration and code:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"db-conn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"localhost"</span><span class="w">
    </span><span class="nl">"port"</span><span class="p">:</span><span class="w"> </span><span class="mi">5432</span><span class="w">
    </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="w">
    </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">pureconfig._</span>
<span class="k">import</span> <span class="nn">pureconfig.generic.auto._</span>

<span class="k">object</span> <span class="nc">Main</span> <span class="k">extends</span> <span class="nc">App</span> <span class="o">{</span>
  <span class="k">case</span> <span class="k">class</span> <span class="nc">DBConn</span><span class="o">(</span><span class="n">host</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">port</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span> <span class="n">user</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">password</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span>
  <span class="k">case</span> <span class="k">class</span> <span class="nc">ServiceConf</span><span class="o">(</span><span class="n">dbConn</span><span class="k">:</span> <span class="kt">DBConn</span><span class="o">)</span>

  <span class="k">val</span> <span class="nv">conf</span> <span class="k">=</span> <span class="nv">ConfigSource</span><span class="o">.</span><span class="py">default</span><span class="o">.</span><span class="py">load</span><span class="o">[</span><span class="kt">ServiceConf</span><span class="o">]</span>

  <span class="nf">println</span><span class="o">(</span><span class="n">conf</span><span class="o">)</span> <span class="c1">// Right(ServiceConf(DBConn(localhost,5432,admin,admin)))</span>
<span class="o">}</span>
</code></pre></div></div>
<p>As we can see, it is pretty straightforward. We define a couple of case classes
that describe the configuration file, and ask pureconfig to create an instance
of that class filled with the correct values.</p>
<h2 id="what-is-a-sealed-family">What is a Sealed Family</h2>
<p>A Sealed Family allows us to create a sealed family of case classes which allows
us to create different types of configuration options, depending on the type
that we specify.</p>

<p>For example if we have different authentication options for our database connection,
one option is to connect by user/password and the other one is to use
user/IAM Token (for AWS RDS as an example), we can define them using a sealed trait,
and some case classes.</p>

<p>We would see that exact example in the following sections.</p>

<h2 id="why-do-we-use-it">Why do we use it</h2>
<p>In our case as I have already mentioned, we rely on that feature for our connection
to a Database. As we are using the AWS RDS service, we have also enabled there
the IAM authentication method. Which means that the password for connecting to the
Database is generated using an AWS <code class="language-plaintext highlighter-rouge">RDSIamAuthTokenGenerator</code>. For that generator
some IAM related configurations are needed. An example of how to generate a real
token you can find in my previous <a href="/scala/aws/rds/hikaricp/2021/05/01/HikaricCP-RDS-IAM.html">post</a>.</p>

<p>But of course there are cases where we want to check our service locally, and so
we would want to connect using a simple user/password option.</p>

<p>A sample configuration with IAM (<code class="language-plaintext highlighter-rouge">./resources/db-iam.conf</code>):</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"db-conn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"localhost"</span><span class="w">
    </span><span class="nl">"port"</span><span class="p">:</span><span class="w"> </span><span class="mi">5432</span><span class="w">
    </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="w">
    </span><span class="nl">"auth"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"iam"</span><span class="w">
        </span><span class="nl">"iam"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"region"</span><span class="p">:</span><span class="w"> </span><span class="s2">"us-west-2"</span><span class="w">
            </span><span class="nl">"assumed-role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:iam::9999999999:role/some-role"</span><span class="w">
            </span><span class="nl">"assume-role-session-name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"my-session"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>A sample configuration with simple password (<code class="language-plaintext highlighter-rouge">./resources/db-password.conf</code>):</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"db-conn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"localhost"</span><span class="w">
    </span><span class="nl">"port"</span><span class="p">:</span><span class="w"> </span><span class="mi">5432</span><span class="w">
    </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="w">
    </span><span class="nl">"auth"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"password"</span><span class="w">
        </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>As we can see those configurations should create two different case classes.
The <code class="language-plaintext highlighter-rouge">type</code> attribute, will tell PureConfig which case class to use.
The next section will contain a working example with some explanations.</p>
<h2 id="implementation">Implementation</h2>
<p>The first basic configuration is needed for the IAM configuration that was mentioned
in the previous section:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">IamConf</span><span class="o">(</span><span class="n">region</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">assumedRole</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">assumeRoleSessionName</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span>
</code></pre></div></div>
<p>We would like to allow the previously mentioned <code class="language-plaintext highlighter-rouge">DBConn</code> case class to handle two types
of authentications. The IAM one, and a simple password one.</p>

<p>For that we will create a <code class="language-plaintext highlighter-rouge">sealed trait</code> that will combine both of the options:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="k">trait</span> <span class="nc">DBAuth</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">password</span><span class="o">(</span><span class="n">value</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">DBAuth</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">iam</span><span class="o">(</span><span class="n">iam</span><span class="k">:</span> <span class="kt">IamConf</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">DBAuth</span>
</code></pre></div></div>
<p>We will call that trait <code class="language-plaintext highlighter-rouge">DBAuth</code> and extend the options (case classes) with that trait.</p>

<p>The simple <code class="language-plaintext highlighter-rouge">password</code> option, will just contain the password itself.</p>

<p>The <code class="language-plaintext highlighter-rouge">iam</code> option, will contain all the relevant fields that were mentioned in the <code class="language-plaintext highlighter-rouge">IamConf</code> case class.</p>

<p>And now the <code class="language-plaintext highlighter-rouge">DBConn</code> case class would look like this:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">DBConn</span><span class="o">(</span><span class="n">host</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">port</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span> <span class="n">user</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">auth</span><span class="k">:</span> <span class="kt">DBAuth</span><span class="o">)</span>
</code></pre></div></div>
<p>We can notice that there is an <code class="language-plaintext highlighter-rouge">auth</code> field, that is of type of the <code class="language-plaintext highlighter-rouge">DBAuth</code> trait. And it will accept
correctly the type of the auth that is specified in the configuration file.</p>

<p>The <code class="language-plaintext highlighter-rouge">ServiceConf</code> case class stays the same:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">ServiceConf</span><span class="o">(</span><span class="n">dbConn</span><span class="k">:</span> <span class="kt">DBConn</span><span class="o">)</span>
</code></pre></div></div>

<p>So now, if we load the configuration files for each of the types we would get the following results:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ConfigSource</span><span class="o">.</span><span class="py">file</span><span class="o">(</span><span class="s">"./resources/db-password.conf"</span><span class="o">).</span><span class="py">load</span><span class="o">[</span><span class="kt">ServiceConf</span><span class="o">]</span>
<span class="c1">// Right(ServiceConf(DBConn(localhost,5432,admin,password(admin))))</span>

<span class="nv">ConfigSource</span><span class="o">.</span><span class="py">file</span><span class="o">(</span><span class="s">"./resources/db-iam.conf"</span><span class="o">).</span><span class="py">load</span><span class="o">[</span><span class="kt">ServiceConf</span><span class="o">]</span>
<span class="c1">// Right(ServiceConf(DBConn(localhost,5432,admin,iam(IamConf(us-west-2,arn:aws:iam::9999999999:role/some-role,my-session)))))</span>
</code></pre></div></div>

<p>All is good, but there is a little problem. If we want to access the <code class="language-plaintext highlighter-rouge">auth</code> fields,
we are unable to do it easily as the <code class="language-plaintext highlighter-rouge">DBAuth</code> trait doesn’t contain any common fields
with the extending case classes.</p>

<p>Which means, that this code won’t work:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">configPass</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">auth</span><span class="o">.</span><span class="py">password</span><span class="o">)</span>
</code></pre></div></div>
<p>Because <code class="language-plaintext highlighter-rouge">DBAuth</code> doesn’t have a <code class="language-plaintext highlighter-rouge">password</code> attribute.</p>

<h1 id="common-attribute">Common attribute</h1>
<p>Basically what we tried to achieve with the <code class="language-plaintext highlighter-rouge">DBAuth</code> configuration. Is to have a
single place for having the password that is needed for the connection. It can
come from different configuration types, but eventually it should return for us a
password.</p>

<p>So basically we have a common attribute for both implementations, and it is the
<code class="language-plaintext highlighter-rouge">password</code> attribute.</p>

<p>For the simple <code class="language-plaintext highlighter-rouge">password</code> configuration type, we will just return the password that
is written in the configuration.</p>

<p>But, for the <code class="language-plaintext highlighter-rouge">iam</code> configuration type, we would want to use the IAM attributes
to generate a token that we would use as the password.</p>

<p>Because we would need to generate the token whenever we call the <code class="language-plaintext highlighter-rouge">password</code> attribute,
we would want to make that attribute a function. So we would add a method that is
called <code class="language-plaintext highlighter-rouge">password</code> to the <code class="language-plaintext highlighter-rouge">DBAuth</code> trait, and implement it in each of the subtypes:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="k">trait</span> <span class="nc">DBAuth</span> <span class="o">{</span>
    <span class="k">def</span> <span class="nf">password</span><span class="k">:</span> <span class="kt">String</span>
<span class="o">}</span>

<span class="k">case</span> <span class="k">class</span> <span class="nc">password</span><span class="o">(</span><span class="n">value</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">DBAuth</span> <span class="o">{</span>
    <span class="k">override</span> <span class="k">def</span> <span class="nf">password</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">value</span>
<span class="o">}</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">iam</span><span class="o">(</span><span class="n">iam</span><span class="k">:</span> <span class="kt">IamConf</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">DBAuth</span> <span class="o">{</span>
    <span class="k">override</span> <span class="k">def</span> <span class="nf">password</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="nc">TokenGenerator</span><span class="o">(</span><span class="n">iam</span><span class="o">).</span><span class="py">getToken</span>
<span class="o">}</span>

<span class="c1">// Just a dummy class for the token generation feature.</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">TokenGenerator</span><span class="o">(</span><span class="n">iam</span><span class="k">:</span> <span class="kt">IamConf</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">def</span> <span class="nf">getToken</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">"some token"</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Now it is possible to call the following code, in order to get the appropriate password:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">configPass</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">auth</span><span class="o">.</span><span class="py">password</span><span class="o">)</span>
<span class="c1">// Right(admin)</span>

<span class="nv">configIam</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">auth</span><span class="o">.</span><span class="py">password</span><span class="o">)</span>
<span class="c1">// Right(some token)</span>
</code></pre></div></div>
<h1 id="more-beautification">More Beautification</h1>
<p>Currently if we would like to access the user and password attributes, it would
look a bit finicky, at least to me.
Let’s look at the following example:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">confPass</span> <span class="k">=</span> <span class="nv">configPass</span><span class="o">.</span><span class="py">getOrElse</span><span class="o">(</span><span class="k">throw</span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">)</span> <span class="c1">// Extract the configuration object or throw an exception.</span>
<span class="nv">confPass</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">user</span>
<span class="nv">confPass</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">auth</span><span class="o">.</span><span class="py">password</span>
</code></pre></div></div>
<p>We can see that the path to the user and to the password, is a bit different.
I would prefer to have the password on the same level as the user. The solution is
pretty easy, we can add a method to the <code class="language-plaintext highlighter-rouge">DBConn</code> that would shorten the path for us.
Like this:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">DBConn</span><span class="o">(</span><span class="n">host</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">port</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span> <span class="n">user</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">auth</span><span class="k">:</span> <span class="kt">DBAuth</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">def</span> <span class="nf">password</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="nv">auth</span><span class="o">.</span><span class="py">password</span>
<span class="o">}</span>
</code></pre></div></div>
<p>And now it is possible to call the <code class="language-plaintext highlighter-rouge">password</code> attribute on the same level as the <code class="language-plaintext highlighter-rouge">user</code> attribute.</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">confPass</span> <span class="k">=</span> <span class="nv">configPass</span><span class="o">.</span><span class="py">getOrElse</span><span class="o">(</span><span class="k">throw</span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">)</span> <span class="c1">// Extract the configuration object or throw an exception.</span>
<span class="nv">confPass</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">user</span>
<span class="nv">confPass</span><span class="o">.</span><span class="py">dbConn</span><span class="o">.</span><span class="py">password</span>
</code></pre></div></div>
<p>And it will call the appropriate <code class="language-plaintext highlighter-rouge">password</code> method of the appropriate <code class="language-plaintext highlighter-rouge">DBAuth</code> implementation.</p>

<p>Hope this post was helpful, thank you for reading.</p>]]></content><author><name></name></author><category term="scala" /><category term="pureconfig" /><summary type="html"><![CDATA[Overview In this post I will show, how we leveraged PureConfig, and were able to use its Sealed Families feature in order to have multiple configuration options for the same resource.]]></summary></entry><entry><title type="html">HikariCP with RDS IAM authentication</title><link href="/scala/aws/rds/hikaricp/2021/05/01/HikaricCP-RDS-IAM.html" rel="alternate" type="text/html" title="HikariCP with RDS IAM authentication" /><published>2021-05-01T22:00:00+00:00</published><updated>2021-05-01T22:00:00+00:00</updated><id>/scala/aws/rds/hikaricp/2021/05/01/HikaricCP-RDS-IAM</id><content type="html" xml:base="/scala/aws/rds/hikaricp/2021/05/01/HikaricCP-RDS-IAM.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>As I tried to describe in the title, apparently using a ConnectionPool on Postgres RDS with the
IAM authentication is not that straightforward.</p>

<p>One of the caveats of the IAM authentication, is that the token that is generated is available for 15 minutes.
Which means that after 15 minutes a new connection needs to regenerate a token. But the connection pool that
we chose doesn’t have a built-in option to pass some token generator, or some similar capabilities.</p>

<p>For that we have created a simple <code class="language-plaintext highlighter-rouge">DataSource</code> that would generate a new token when a <code class="language-plaintext highlighter-rouge">getConnection</code> method
is called.</p>

<p>The whole solution is described in the following sections.</p>

<h2 id="how-to-authenticate-with-an-iam">How to authenticate with an IAM</h2>
<p>In order to do an IAM Authentication with RDS, we are using the AWS Java SDK.
And more specifically the following services:</p>
<ol>
  <li>AWSSecurityTokenServiceClientBuilder - In order to fetch the credentials.</li>
  <li>RdsIamAuthTokenGenerator - To generate the RDS token using the credentials.</li>
</ol>

<p>Both services are pretty straight forward, but in any case, this is how we fetch the credentials:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">stsClient</span> <span class="k">=</span> <span class="nv">AWSSecurityTokenServiceClientBuilder</span><span class="o">.</span><span class="py">standard</span>
      <span class="o">.</span><span class="py">withRegion</span><span class="o">(</span><span class="n">regionName</span><span class="o">)</span>
      <span class="o">.</span><span class="py">build</span>

<span class="k">val</span> <span class="nv">roleRequest</span> <span class="k">=</span> <span class="k">new</span> <span class="nc">AssumeRoleRequest</span><span class="o">()</span>
      <span class="o">.</span><span class="py">withRoleArn</span><span class="o">(</span><span class="n">assumedRole</span><span class="o">)</span>
      <span class="o">.</span><span class="py">withRoleSessionName</span><span class="o">(</span><span class="n">assumeRoleSessionName</span><span class="o">)</span>

<span class="k">val</span> <span class="nv">roleResponse</span> <span class="k">=</span> <span class="nv">stsClient</span><span class="o">.</span><span class="py">assumeRole</span><span class="o">(</span><span class="n">roleRequest</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">sessionCredentials</span> <span class="k">=</span> <span class="nv">roleResponse</span><span class="o">.</span><span class="py">getCredentials</span>

<span class="k">new</span> <span class="nc">BasicSessionCredentials</span><span class="o">(</span>
      <span class="nv">sessionCredentials</span><span class="o">.</span><span class="py">getAccessKeyId</span><span class="o">,</span>
      <span class="nv">sessionCredentials</span><span class="o">.</span><span class="py">getSecretAccessKey</span><span class="o">,</span>
      <span class="nv">sessionCredentials</span><span class="o">.</span><span class="py">getSessionToken</span>
<span class="o">)</span>
</code></pre></div></div>

<p>After we have the basic credentials, we can start creating the RDS token:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">generator</span> <span class="k">=</span> <span class="nv">RdsIamAuthTokenGenerator</span><span class="o">.</span><span class="py">builder</span>
      <span class="o">.</span><span class="py">credentials</span><span class="o">(</span><span class="k">new</span> <span class="nc">AWSStaticCredentialsProvider</span><span class="o">(</span><span class="n">credentials</span><span class="o">))</span>
      <span class="o">.</span><span class="py">region</span><span class="o">(</span><span class="n">region</span><span class="o">)</span>
      <span class="o">.</span><span class="py">build</span>

<span class="nv">generator</span><span class="o">.</span><span class="py">getAuthToken</span><span class="o">(</span>
      <span class="nv">GetIamAuthTokenRequest</span><span class="o">.</span><span class="py">builder</span>
        <span class="o">.</span><span class="py">hostname</span><span class="o">(</span><span class="nv">rdsConf</span><span class="o">.</span><span class="py">host</span><span class="o">)</span>
        <span class="o">.</span><span class="py">port</span><span class="o">(</span><span class="nv">rdsConf</span><span class="o">.</span><span class="py">port</span><span class="o">)</span>
        <span class="o">.</span><span class="py">userName</span><span class="o">(</span><span class="nv">rdsConf</span><span class="o">.</span><span class="py">userName</span><span class="o">)</span>
        <span class="o">.</span><span class="py">build</span>
<span class="o">)</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">rdsConf</code> is just a configuration object that we use to hold the relevant configuration for RDS.</p>

<p>Now that we have the authentication token, we can now use it as the password for connecting to our RDS instance.</p>

<p>But bear in mind that this token is only valid for 15 minutes. A small note here, if a connection was
established using that token, then while the connection is still open, it will work for more than 15 minutes.
But once the connection is broken the same token can’t be used to reconnect if the
15 minutes expiry time has passed.</p>

<h2 id="how-to-define-the-connection-pool">How to define the connection pool</h2>
<p>We went with using HikariCP as our connection pooling solution.
It was pretty straight forward to initialize it and start using it.
With a few lines of code we were able to replace our previous non CP solution.
Here is how we initialize it:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">hikariConf</span> <span class="k">=</span> <span class="k">new</span> <span class="nc">HikariConfig</span><span class="o">()</span>
<span class="nv">hikariConf</span><span class="o">.</span><span class="py">setJdbcUrl</span><span class="o">(</span><span class="nv">rdsConf</span><span class="o">.</span><span class="py">url</span><span class="o">)</span>
<span class="nv">hikariConf</span><span class="o">.</span><span class="py">setDataSource</span><span class="o">(</span><span class="k">new</span> <span class="nc">PGCustomDataSource</span><span class="o">(</span><span class="n">rdsConf</span><span class="o">,</span> <span class="n">buildConnectionProperties</span><span class="o">))</span>

<span class="k">val</span> <span class="nv">hikariDataSource</span> <span class="k">=</span> <span class="k">new</span> <span class="nc">HikariDataSource</span><span class="o">(</span><span class="n">hikariConf</span><span class="o">)</span>

<span class="nv">DSL</span><span class="o">.</span><span class="py">using</span><span class="o">(</span><span class="n">hikariDataSource</span><span class="o">,</span> <span class="nv">SQLDialect</span><span class="o">.</span><span class="py">valueOf</span><span class="o">(</span><span class="nv">rdsConf</span><span class="o">.</span><span class="py">driver</span><span class="o">.</span><span class="py">dbType</span><span class="o">))</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">PGCustomDataSource</code> is our implementation of the datasource, where we refresh the
RDS IAM token. We will see the solution in the next step.</p>

<p>The <code class="language-plaintext highlighter-rouge">buildConnectionProperties</code> is just some more properties that we use to enable <code class="language-plaintext highlighter-rouge">SSL</code>
connection to the RDS.</p>

<p>Basically after creating the <code class="language-plaintext highlighter-rouge">HikariDataSource</code> we pass it to our database abstraction
framework, which in our case is jOOQ.</p>

<h2 id="updating-the-password-when-a-connection-is-requested">Updating the password when a connection is requested</h2>
<p>The most naive approach for updating the password, is just to override the <code class="language-plaintext highlighter-rouge">getConnection</code> method
of the data source.
In our case we are connecting to a postgres DB, so we were using the <code class="language-plaintext highlighter-rouge">PGSimpleDataSource</code>
and overriding its <code class="language-plaintext highlighter-rouge">getConnection</code> method. Well actually we were overriding the one in
<code class="language-plaintext highlighter-rouge">BaseDataSource</code> which the <code class="language-plaintext highlighter-rouge">PGSimpleDataSource</code> extends.
The code is pretty simple:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">PGCustomDataSource</span><span class="o">(</span><span class="n">rdsConf</span><span class="k">:</span> <span class="kt">RdsConf</span><span class="o">,</span> <span class="n">connectionProperties</span><span class="k">:</span> <span class="kt">Properties</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">PGSimpleDataSource</span> <span class="o">{</span>
  <span class="k">override</span> <span class="k">def</span> <span class="nf">getConnection</span><span class="k">:</span> <span class="kt">Connection</span> <span class="o">=</span> <span class="o">{</span>
    <span class="nv">connectionProperties</span><span class="o">.</span><span class="py">setProperty</span><span class="o">(</span><span class="s">"user"</span><span class="o">,</span> <span class="nv">rdsConf</span><span class="o">.</span><span class="py">userName</span><span class="o">)</span>
    <span class="nv">connectionProperties</span><span class="o">.</span><span class="py">setProperty</span><span class="o">(</span><span class="s">"password"</span><span class="o">,</span> <span class="nv">rdsConf</span><span class="o">.</span><span class="py">password</span><span class="o">)</span>
    <span class="nv">DriverManager</span><span class="o">.</span><span class="py">getConnection</span><span class="o">(</span><span class="nv">rdsConf</span><span class="o">.</span><span class="py">url</span><span class="o">,</span> <span class="n">connectionProperties</span><span class="o">)</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">getConnection</code> method fetches the user and password from the configuration and
passes it to the <code class="language-plaintext highlighter-rouge">DriverManager</code>. Similar to what is going on in the <code class="language-plaintext highlighter-rouge">getConnection</code> of
<code class="language-plaintext highlighter-rouge">BaseDataSource</code>.</p>

<p>One note though, the <code class="language-plaintext highlighter-rouge">rdsConf.password</code> is actually a method, that generates the token.
We did a neat trick with that configuration where it can generate a password or fetch one
from the configuration object directly, using some <a href="https://github.com/pureconfig/pureconfig">pureconfig</a> magic.</p>

<p>And that is it, now our connection pool can refresh the RDS IAM tokens and allow our
service to continue working.</p>

<p>Thanks for reading !</p>]]></content><author><name></name></author><category term="scala" /><category term="AWS" /><category term="RDS" /><category term="hikaricp" /><summary type="html"><![CDATA[Overview As I tried to describe in the title, apparently using a ConnectionPool on Postgres RDS with the IAM authentication is not that straightforward.]]></summary></entry><entry><title type="html">Using YouTube’s API to download video description</title><link href="/youtube/google/python/2021/03/22/youtube-video-desc-api.html" rel="alternate" type="text/html" title="Using YouTube’s API to download video description" /><published>2021-03-22T22:00:00+00:00</published><updated>2021-03-22T22:00:00+00:00</updated><id>/youtube/google/python/2021/03/22/youtube-video-desc-api</id><content type="html" xml:base="/youtube/google/python/2021/03/22/youtube-video-desc-api.html"><![CDATA[<h2 id="overview">Overview</h2>
<p>I recently have started following a recipes channel on YouTube. The thing that
is missing for me, is that I can’t search for recipes with specific ingredients.
Luckily the videos description contains the ingredients. So I was
thinking about downloading the descriptions of all the videos. And then cataloging
them somewhere for easier search.</p>

<p>In this post, I will show you how I have downloaded all the descriptions of all
the videos in the channel using Google’s Youtube API, Python and the <code class="language-plaintext highlighter-rouge">requests</code> package.</p>

<h2 id="api">API</h2>
<p>First of all to use Youtube’s API, we need to onboard it in <a href="https://console.cloud.google.com/">Google Cloud Console</a>.
Under <code class="language-plaintext highlighter-rouge">APIs &amp; Services</code> choose <code class="language-plaintext highlighter-rouge">Library</code>, search for <code class="language-plaintext highlighter-rouge">YouTube Data API</code>, in my case it
was version 3 (v3), choose it and enable it.</p>

<p>After that we need to create an API Key (for the naive solution). We can find it under
the credentials tab.</p>

<p>When we have the key in hand, we can move to the next section and start calling the API.
In my case I have used Python for that with the <a href="https://requests.readthedocs.io/en/master/">requests</a> package.</p>

<h2 id="fetching-metadata">Fetching metadata</h2>
<p>As I have already mentioned, the whole idea of that mini project was to fetch the descriptions of
the videos from a specific channel. There were two endpoints that I had to use in order
to fetch the descriptions.</p>

<p>The first one, was to fetch all the video IDs of that channel, and then for each
video to fetch the description.</p>

<p>The first endpoint that I have used, was the <a href="https://developers-dot-devsite-v2-prod.appspot.com/youtube/v3/docs/search/list#type">search</a>.
Using this one, I was able to fetch all the video IDs.
I have used the following URL:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">'</span><span class="s">https://youtube.googleapis.com/youtube/v3/search?part=snippet&amp;maxResults=50&amp;type=video&amp;channelId=</span><span class="si">{</span><span class="n">channel_id</span><span class="si">}</span><span class="s">&amp;key=</span><span class="si">{</span><span class="n">self</span><span class="p">.</span><span class="n">api_key</span><span class="si">}</span><span class="sh">'</span>
</code></pre></div></div>
<p>The parameters that I have used in the request were:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">part=snippet</code> - Specifies that I want to have a snippet of the data of the results. It is what the documentation asks to use.</li>
  <li><code class="language-plaintext highlighter-rouge">maxResults=50</code> - The number of results to return. 50 is the max.</li>
  <li><code class="language-plaintext highlighter-rouge">type=video</code> - What type of results are we interested in. In my case it was only videos. Otherwise it might return playlists.</li>
  <li><code class="language-plaintext highlighter-rouge">channelId={channel_id}</code> - The results for a specific channel.</li>
  <li><code class="language-plaintext highlighter-rouge">key={api_key}</code> - The API key that we have created earlier.</li>
</ol>

<p>In my case the the amount of videos in the channel was greater than 50, so I had to
use <code class="language-plaintext highlighter-rouge">nextPageToken</code> which contains the ID for the next page of the results. So my subsequent
search requests used <code class="language-plaintext highlighter-rouge">pageToken={next_page}</code>.</p>

<p>The result of the search request also contained a description for each video that it returned,
but it was an abbreviated one, so I had to use another endpoint to fetch the whole description
of each video.</p>

<p>For each result, I have created a list of video IDs using the following list comprehension:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">video_ids</span> <span class="o">=</span> <span class="p">[</span>
            <span class="n">search_result</span><span class="p">[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">videoId</span><span class="sh">'</span><span class="p">]</span>
            <span class="k">for</span> <span class="n">search_result</span> <span class="ow">in</span> <span class="n">response</span><span class="p">[</span><span class="sh">'</span><span class="s">items</span><span class="sh">'</span><span class="p">]</span>
            <span class="k">if</span> <span class="sh">'</span><span class="s">videoId</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">search_result</span><span class="p">[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">]</span>
        <span class="p">]</span>
</code></pre></div></div>
<p>And using the <a href="https://developers-dot-devsite-v2-prod.appspot.com/youtube/v3/docs/videos/list">videos</a> endpoint,
I would pass the list of IDs to get the result with the full description.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">'</span><span class="s">https://youtube.googleapis.com/youtube/v3/videos?part=snippet&amp;id=</span><span class="si">{</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">video_ids</span><span class="p">)</span><span class="si">}</span><span class="s">&amp;key=</span><span class="si">{</span><span class="n">self</span><span class="p">.</span><span class="n">api_key</span><span class="si">}</span><span class="sh">'</span>
</code></pre></div></div>
<p>The parameters in this case were:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">part=snippet</code> - As in the search request.</li>
  <li><code class="language-plaintext highlighter-rouge">id={",".join(video_ids)}</code> - A comma separated video IDs list.</li>
  <li><code class="language-plaintext highlighter-rouge">key={api_key}</code> - The API key that we have created earlier, the same one we used in the search request.
The result of this request contained a result for each video that was passed with its full description.</li>
</ol>

<p>And that is basically what I did to fetch the descriptions of all the videos of a channel.</p>

<p>Thanks for reading !</p>]]></content><author><name></name></author><category term="youtube" /><category term="google" /><category term="python" /><summary type="html"><![CDATA[Overview I recently have started following a recipes channel on YouTube. The thing that is missing for me, is that I can’t search for recipes with specific ingredients. Luckily the videos description contains the ingredients. So I was thinking about downloading the descriptions of all the videos. And then cataloging them somewhere for easier search.]]></summary></entry></feed>