Vince's thoughts - Tag - Raspberry Pi2024-03-26T20:56:27+01:00Vincent Deconinckurn:md5:286ffe61391db532cf638df1092ae539DotclearIntroducing CHAI - a CAME gate controller to Home Assistant Interfaceurn:md5:4767a83d394757cea84a83dbf48534482024-01-20T14:09:00+00:002024-03-03T22:48:13+00:00VinceElectronicsESP8266HackingRaspberry Pi<p>I have a parking gate with a Came BXV controller and wanted to fully integrate it with a home automation system.
Using a good old ESP8266 and ESPHome, I can now exactly control the movements from Home Assistant, get feedback of the actual gate position and state, and control it remotely or set-up alarms in case it stays open for too long for example.</p>
<p>So let me introduce CHAI - a <strong><ins>C</ins></strong>AME gate controller to <strong><ins>H</ins></strong>ome <strong><ins>A</ins></strong>ssistant <strong><ins>I</ins></strong>nterface</p> <h3>Intro</h3>
<p>A few years ago, I installed a sliding parking gate with a <a href="https://www.came.com/us/installers/solutions/gates-operators/sliding-gates-operators/bxv" hreflang="en" title="Came BXV product range">Came BXV controller</a>.
I always thought it would be great to integrate it with a home automation system, for the following reasons:</p>
<ul>
<li>The provided RF remote control has a limited range and you can never be sure the command has been received when you press the button from far away (so you often end up clicking a second time, and if the first command was in fact received, the gate stops and you have to click again to close, again to stop, and one more time to get it to open again)</li>
<li>When you leave and the gate starts to close, a spurious condition could trip the IR security sensor and stop the closing motion, leaving the gate open for the day</li>
<li>When you're expecting a package, it can be useful to remotely open the gate for a short time and let the delivery guy drop your parcel at a safe place</li>
</ul>
<p>After a quick survey, I selected <a href="https://www.home-assistant.io/" title="Home assistant website">Home Assistant</a>, which looks like the sensitive choice nowadays, and tried to design a device acting as a Came controller with Home Assistant, referred to as CHAI hereafter <img src="/themes/default/smilies/smile.png" alt=":-)" class="smiley" /></p>
<p>So finally, with the great help of MacGyver, I gave it a shot and here are our thoughts and results.</p>
<h3>Target features</h3>
<ul>
<li>Must control the gate movements with distinct commands: OPEN - STOP - CLOSE</li>
<li>Must know for sure if the gate is fully open or closed</li>
<li>Must know if the gate is in movement</li>
<li>Must leave functional control using the original RF remote control (and reflect the situation in Home Assistant)</li>
<li>Must leave functional control using the original RF remote control <strong>even</strong> in case of power supply failure on CHAI</li>
<li>Must not do any action on the gate in case of CHAI reboot</li>
</ul>
<h3>Demonstration</h3>
<p>Before diving into the implementation details, here is a demonstration of the final installation working at my home.</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/gJ1Nn_VbDwY" frameborder="0" allowfullscreen></iframe>
<br/>
<br/>
<h3>Hardware design</h3>
<p><em>Please note that most of the information about the CAME controller comes from the official documentation, namely <a href="https://came-cdn.thron.com/delivery/public/document/came/fa01442-en/ugqsil/WEB/CAME-fa01442-en.pdf" title="Came Controller pdf manual link">the manual for my model</a>.</em></p>
<h4>Microcontroller</h4>
<p>I chose to base the CHAI on an ESP8266 chip on a <a href="https://www.aliexpress.com/item/32681374223.html" title="D1 mini clone on AliExpress">WeMos D1 mini clone board</a> as it is cheap, reliable and easy to interface. Plus, it has extended support in HomeAssistant through the <a href="https://esphome.io/guides/getting_started_hassio.html" title="ESP Home installation on Home Assistant page">ESPHome Add-On</a>.</p>
<h4>Power supply</h4>
<p>The power supply of the Came controller board is 24VAC coming from a transformer.</p>
<p>I first thought of plugging a voltage conversion module to that source to power the CHAI, but finally decided to limit connections to the original setup as much as possible, so I included a dedicated power supply from the mains.</p>
<p>I chose a <a href="https://www.aliexpress.com/item/1005003052023770.html" title="HiLink Module on AliExpress">Hi-Link 5V AC/DC module</a> I had laying around and added a small fuse in line. The 5V is fed to the D1 Mini which in turn produces the 3.3V required by the ESP. Currently no other device on the CHAI actually uses that 5V Voltage, but I thought it was futureproof to have 5V available.</p>
<h4>Simulating button press:</h4>
<p>The controller has terminals to connect buttons to:
<img src="http://blog.deconinck.info/public/CAME/2023-12-10_13-37-58.png" alt="" style="display:table; margin:0 auto;" />
By default, button 2 (STOP) is disabled and button 4 is configured as "step-by-step" mode (open-close).
We need to change the controller configuration as follows:</p>
<ul>
<li>“F1=ON” to activate button 2 as “stop”</li>
<li>“F7=3” to configure button 4 as “close”</li>
<li>“F8=2” to configure button 3 as “open”</li>
</ul>
<p>A few measurements indicate that the controller has pull-ups of 4.7KOhm to 5V on each contact, so connecting a N-channel MOSFET (2N7000) to GND, driven by a 3.3V pin of the ESP8266, will fit the bill.</p>
<p>Important note: I checked that all signals we will use below are referenced to the same ground. So connecting the CHAI ground to terminal 2 of this connector will make sure the two boards share a common ground.</p>
<p>Buttons 2 and 3 are Normally Open, so a 10kOhm pull-down on the gate of the MOSFET will make sure that they are not "pressed" active when left floating (even in case of reboot or power loss). The schematic is as follows (note that the final selection of ESP pins will be made later, this is just to show the external circuit logic):
<a href="http://blog.deconinck.info/public/CAME/1button_logic.png"><img src="http://blog.deconinck.info/public/CAME/.1button_logic_m.png" alt="" style="display:table; margin:0 auto;" /></a>
The STOP button, however, is Normally Closed (it can be used as an Emergency Stop button). In other words we have to make sure that the connection remains closed (MOSFET gate high) by default.</p>
<p>To achieve that pull-up even in case of power loss, we'll derive power from the other buttons, 2 and 3 (through 2 signal diodes (e.g. 1N4148) and a 10kOhm resistor to avoid interfering with those signals).</p>
<p>We'll also put two more diodes in series to drop the voltage and avoid exposing the ESP8266 input pin to a voltage above 3.3V. Something like:
<a href="http://blog.deconinck.info/public/CAME/3button_logic.png"><img src="http://blog.deconinck.info/public/CAME/.3button_logic_m.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<h4>Sensing limit switches</h4>
<p>The CAME motor and controller also contains 2 limit switches connected to the controller's F/FA/FC terminals:
<a href="http://blog.deconinck.info/public/CAME/2024-02-03_23-24-19_v2.png"><img src="http://blog.deconinck.info/public/CAME/.2024-02-03_23-24-19_v2_s.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<p>The limit switch themselves consist of a single spring that gets pushed left or right when the gate is fully open or closed, and flips one switch or the other:
<a href="http://blog.deconinck.info/public/CAME/2024-02-03_22-59-47.jpg"><img src="http://blog.deconinck.info/public/CAME/.2024-02-03_22-59-47_m.jpg" alt="" style="display:table; margin:0 auto;" /></a></p>
<p>The original schematic is as follows:
<a href="http://blog.deconinck.info/public/CAME/limit_switch.png"><img src="http://blog.deconinck.info/public/CAME/.limit_switch_s.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<ul>
<li>F (orange wire) is GND</li>
<li>FC (red wire, on the left) is held to GND unless the gate is fully closed.</li>
<li>FA (white wire, on the right) is held to GND unless the gate is fully open.</li>
</ul>
<p>The complementary outputs of the switches are located between the orange and red, and orange and white terminals, and are not used by the CAME controller.</p>
<p>When not held to GND, FA and FC are pulled up to 5V by the controller.</p>
<p>Instead of connecting in parallel to those red/white wires and having to deal with level conversion from 5V to 3.3V, I simply used the complementary outputs of the switches, connected to an ESP pin (via a 560Ohm protection resistor - cabling errors happen, ask me how I know ;-)).</p>
<p>The ESP pins are configured with an internal pull-up to 3.3V, so those wires are at 3.3V unless the gate is fully closed or fully open.</p>
<h4>Sensing motor movement</h4>
<p>The motor is powered by the controller via terminals M/N (1 on the schematic below), and an encoder gives positional feedback to the controller through the terminals labeled +/E/- (2):
<a href="http://blog.deconinck.info/public/CAME/2024-02-03_23-23-55.png"><img src="http://blog.deconinck.info/public/CAME/.2024-02-03_23-23-55_s.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<p>The measured DC voltage across terminals M/N is:</p>
<ul>
<li>30V when opening at full speed</li>
<li>15V when opening at half-speed (approaching fully open)</li>
<li>0V when the gate is not moving</li>
<li>-15V when closing at half-speed (approaching fully closed)</li>
<li>-30V when closing at full speed</li>
</ul>
<p>At first, I wanted to retrieve information from the encoder terminals. However, I could not make sense of the observed signals, which look like this:
<a href="http://blog.deconinck.info/public/CAME/2022-12-03_17-18-40_v3.jpg"><img src="http://blog.deconinck.info/public/CAME/.2022-12-03_17-18-40_v3_m.jpg" alt="" style="display:table; margin:0 auto;" /></a>
(strangely enough, the small peaks between the big ones only appear at half speed).</p>
<p><em>I might have measured wrong. If anybody has sucessfully decoded that signal, don't hesitate to reach back to me... I might make a version 2 <img src="/themes/default/smilies/smile.png" alt=":-)" class="smiley" /></em>.</p>
<p>Anyway, in the meantime, we'll use the time of travel in one direction or the other to get an estimate of the gate position (remember that the end positions will be known for sure thanks to the limit switches).</p>
<p>To achieve this, we will just need to know when the gate is moving, and in which direction. The easiest way is to check the motor terminals voltage, but to handle negative voltages, and to protect the ESP from "high" voltage and spikes that could happen at the motor terminals, we'll run them through optocouplers (one PC817 in each direction), as follows:
<a href="http://blog.deconinck.info/public/CAME/motor_logic.png"><img src="http://blog.deconinck.info/public/CAME/.motor_logic_m.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<h4>Reading IR sensor</h4>
<p>Although not used in any way by ESPHome's "covers" <a href="http://blog.deconinck.info/post/2024/01/20/Came-Home-Assistant-Interface#cover" title="Cover definition">(*)</a>, as we still have a free GPIO, I thought it might be interesting to know if the IR photocell is interrupted. For example, if the gate cannot be closed remotely, that information will tell whether there is an obstacle (car ?) preventing the closing, or if the cause is elsewhere.</p>
<p>According to the CAME manual, here is one way to connect an IR photocell:
<a href="http://blog.deconinck.info/public/CAME/2024-02-04_00-19-30.png"><img src="http://blog.deconinck.info/public/CAME/.2024-02-04_00-19-30_m.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<p>Terminal 2 is GND and terminal 10 is VCC (around 17V). The controller’s CX pin has a pull-up to 5V and is expected to be held to GND by the sensor unless the IR barrier is interrupted.</p>
<p>To adapt the level and protect the ESP, I've used a N-channel MOSFET (also 2N7000) as a buffered input as follows:
<a href="http://blog.deconinck.info/public/CAME/ir_logic.png"><img src="http://blog.deconinck.info/public/CAME/.ir_logic_m.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<h4>ESP8266 Pin selection</h4>
<p>The ESP8266 (and the Wemos D1) have assigned specific roles to certain pins. Although most can be reconfigured for other uses, the state of the pins at boot time is critical. Some require certain levels for a normal boot to happen, and some are pulled up or down by default. See <a href="https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/" title="Which GPIO pin should I use">this excellent RandomNerdTutorlal page</a> for all details.</p>
<p>With the requirement in mind to not interfere with the gate upon boot, I settled on the following pin assignment:</p>
<ul>
<li>D1 and D2: movement (motor rotation) sensing</li>
<li>D3: IR barrier sensing</li>
<li>D4: STOP button</li>
<li>D5 and D6: limit switches sensing</li>
<li>D7 and D8 : OPEN/CLOSE buttons</li>
</ul>
<h3>Final circuit:</h3>
<p>Here is the schematic (including a model of the controller to the right of the terminals):
<a href="http://blog.deconinck.info/public/CAME/v10_final.png"><img src="http://blog.deconinck.info/public/CAME/.v10_final_m.png" alt="" style="display:table; margin:0 auto;" /></a></p>
<p>And here is my working prototype (actually, as it is working fine, let's call it the production version :-)):
<a href="http://blog.deconinck.info/public/CAME/protoboard2_sml.jpg"><img src="http://blog.deconinck.info/public/CAME/.protoboard2_sml_m.jpg" alt="" style="display:table; margin:0 auto;" /></a></p>
<p>And protected in the black box at the bottom right :
<a href="http://blog.deconinck.info/public/CAME/20231222_145348_sml.jpg"><img src="http://blog.deconinck.info/public/CAME/.20231222_145348_sml_m.jpg" alt="" style="display:table; margin:0 auto;" /></a></p>
<h3>Software design</h3>
<h4>Environment</h4>
<p>As indicated above, I'm using Home Assistant (<ins>Important</ins>: install it as supervisor to be able to install AddOns. I recommend using <a href="https://www.home-assistant.io/installation/raspberrypi" title="Home Assistant on Raspberry Pi installation page">Home Assistant OS on a Raspberry Pi</a>), and the <a href="https://esphome.io/guides/getting_started_hassio.html" title="ESP Home installation on Home Assistant page">ESPHome Add-On for Home Assistant</a>.</p>
<h4>ESPHome component</h4>
<p>Parking gates are part of ESPHome's "covers" <a href="http://blog.deconinck.info/post/2024/01/20/Came-Home-Assistant-Interface#cover" title="Cover definition">(*)</a>, and several variants are supported. As we have limit switches that provide feedback to the ESP, the variant used here is called "feedback cover", which is part of the "time-based cover" family. In other words it can translate the "time of travel" to a position (in percentage) and conversely.</p>
<p>Like most configurations in Home Assistant, the ESPHome device is configured using YAML. Apart from the boilerplate sections (network connectivity, over-the-air update, possibility to force a reboot and get uptime and Wi-Fi quality metrics), the code defines mainly 3 outputs for the open/stop/close buttons and 5 binary sensors for the limit switches, motor sensing and IR barrier. Then, based on those, the "ParkingGate" cover is created and configured with the full opening/closing time (measured at 20s in my case) and a duration to emulate the button clicks (I used 300ms).</p>
<p>For debugging or lower level access, I also defined 3 "button" controls (to be able to bypass the cover and direcly simulate keypresses), but they are not needed for a functional gate. Similarly, I added a "name" field to the 5 binary sensors so that they show up in HA, but a name is not needed for the cover to work, and only the cover itself has to be given a name to be displayed in HA.</p>
<h4>Optional feature - "Postman" script</h4>
<p>Finally, the "postman" feature allows one to flip a switch in HA to trigger the following sequence to let a delivery guy leave a parcel inside: open gate partially / wait for 1 minute / close gate.</p>
<p>That feature is mainly a script, associated with a "switch" UI component that turns on and starts the script when you click on it, and turns off when the script is finished (with a bit of fiddling so that the script stops if you flip the switch back to off when it's running). Obviously, the postman script is completely optional.</p>
<h3>Final configuration</h3>
<p>Here is the complete YAML file I'm using:</p>
<hr />
<code style="white-space: pre;">
esphome:
name: "esp-parking"
friendly_name: ESP Parking
esp8266:
board: d1_mini
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "************"
ota:
password: "************"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esp-Parking Fallback Hotspot"
password: "************"
manual_ip:
static_ip: ************
gateway: ************
subnet: ************
captive_portal:
output:
- platform: gpio
pin: D7
id: gate_control_close
- platform: gpio
pin: D8
id: gate_control_open
- platform: gpio
pin: D4
id: gate_control_stop
inverted: true
binary_sensor:
- platform: gpio
id: is_gate_opening
name: is_gate_opening
pin:
number: D1
mode:
input: true
pullup: true
inverted: true
- platform: gpio
id: is_gate_closing
name: is_gate_closing
pin:
number: D2
mode:
input: true
pullup: true
inverted: true
- platform: gpio
name: "Gate obstacle detected"
pin:
number: D3
mode:
input: true
pullup: true
inverted: true
- platform: gpio
id: is_gate_open
name: is_gate_open
pin:
number: D5
mode:
input: true
pullup: true
inverted: true
- platform: gpio
id: is_gate_closed
name: is_gate_closed
pin:
number: D6
mode:
input: true
pullup: true
inverted: true
cover:
- platform: feedback
device_class: gate
name: "ParkingGate"
id: parking_gate
has_built_in_endstop: True
open_action:
- output.turn_on: gate_control_open
- delay: 300ms
- output.turn_off: gate_control_open
open_duration: 20s
open_endstop: is_gate_open
open_sensor: is_gate_opening
close_action:
- output.turn_on: gate_control_close
- delay: 300ms
- output.turn_off: gate_control_close
close_duration: 20s
close_endstop: is_gate_closed
close_sensor: is_gate_closing
stop_action:
- output.turn_on: gate_control_stop
- delay: 300ms
- output.turn_off: gate_control_stop
script:
- id: postman_access
then:
- cover.control:
id: parking_gate
position: 25%
- delay: 65s # must be = opening time + duration of the "open" phase
- cover.close: parking_gate
- delay: 8s # must be = closing time + a few secs (motor slows down when nearing full close)
- switch.turn_off: postman
switch:
- platform: template
name: "Postman"
id: postman
optimistic: True #otherwise it turns off automatically after a delay as it has no confirmation of a state
turn_on_action:
- script.execute: postman_access
turn_off_action:
- script.stop: postman_access
- cover.stop: parking_gate
button:
- platform: restart # reboot button
name: "Gate module reboot"
- platform: output
name: "Gate control open"
output: gate_control_open
duration: 300ms
- platform: output
name: "Gate control close"
output: gate_control_close
duration: 300ms
- platform: output
name: "Gate control stop"
output: gate_control_stop
duration: 300ms
sensor:
# wifi signal level in dBm
- platform: wifi_signal
name: "Gate WiFi signal"
update_interval: 60s
# ESP uptime (to track reboots, if any)
- platform: uptime
name: Uptime Sensor
</code>
<hr />
<p>In the UI, using the history explorer Add-On, one can check the cover state changes based on the individual binary sensors:
<img src="http://blog.deconinck.info/public/CAME/2024-02-04_18-22-31.png" alt="" style="display:table; margin:0 auto;" /></p>
<hr />
<p><a id="cover"></a>(*) The ESPHome term "covers" covers (no pun intended :-)) gates, blinds, curtains, garage doors, and basically all home elements that do open or close</p>A Dirt Cheap F*** Awesome Interactive Led Tableurn:md5:d922d84f870c7ab0bffa0cfc1a0c85862016-12-19T22:12:00+01:002020-02-08T15:55:16+01:00VinceElectronicsArduinoGeekHackingIRJavaLedRaspberry PiTable<br/><br/>
<p><img src="http://blog.deconinck.info/public/LedTable/space_invaders.gif" alt="Space invaders" title="Space invaders, janv. 2017" /></p>
<h2>tl;dr</h2>
<h3>What ?</h3>
<ul>
<li>led coffee table</li>
<li>touch-sensitive</li>
</ul>
<h3>What's special about it ?</h3>
<ul>
<li>IR detection through tranclucent acrylic</li>
<li>3 devices connected to a Raspberry Pi via a single serial port</li>
<li>Java program hacked without modifying the actual code</li>
<li>130 EUR (140 USD)</li>
</ul> <br/>
<div style="height:52px; width:100%; background-image: url('/public/LedTable/ws2812_1cell_sml.jpg'); background-repeat: repeat-x;"> </div>
<p>It's finally time I document this project I completed almost one year ago.</p>
<p>I wanted to play with large led displays for a long time (who doesn't ?), and browsing on AliExpress, I once landed on one of those WS2811/WS2812 addressable led strips for less than 3 EUR/meter, and thought "Wow, that's dirt cheap !".<br />
I bought one meter just to play with it, and was surprised to see how fun it was (easy to control and quite powerful), and I started to think about wiring them in a matrix shape. Most of the libraries already support custom width and height, so that's pretty cool.</p>
<p>A few weeks later, I came across <a href="https://youtu.be/_NEK5YwKGKk">that large led matrix by GreatScott</a> and I thought "F*** Awesome !", and following hours of youtube suggestions, I stumbled upon <a href="https://youtu.be/OLfF4b49MLs">this video by yohash84</a> and said to myself "interactivity, mmmhhh".</p>
<h2>And so the idea was born: I want a Dirt Cheap, F*** Awesome, Interactive Led Table.</h2>
<br/>
<div style="height:52px; width:100%; background-image: url('/public/LedTable/ws2812_1cell_sml.jpg'); background-repeat: repeat-x;"> </div>
<h3>First Things First: Non-interactive Version</h3>
<p>After a proof of concept with just my 1m long strip, cells delimited with carboard and a thin tracing paper diffuser, I was rather confident that using a strip was a simple and effective solution, and set a non-interactive table as my first goal.</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/Rp-Z7km0ZY8" frameborder="0" allowfullscreen></iframe>
<br/>
<h4>Hardware</h4>
<p>I started with IKEA's <a href="http://www.ikeahackers.net/2015/11/16-ways-use-lack-side-table-around-house.html">hacker-friendly Lack table</a>. Its honeycomb cardboard structure makes it really easy to get a hollow table.</p>
<pre></pre>
<p><a href="http://blog.deconinck.info/public/LedTable/IKEA_LACK_2016-08-12_1310.png"><img src="http://blog.deconinck.info/public/LedTable/.IKEA_LACK_2016-08-12_1310_t.jpg" alt="IKEA_LACK_2016-08-12_1310.png" title="IKEA_LACK_2016-08-12_1310.png, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151023_171049.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151023_171049_t.jpg" alt="20151023_171049.jpg" title="20151023_171049.jpg, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151023_171709.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151023_171709_t.jpg" alt="20151023_171709.jpg" title="20151023_171709.jpg, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151023_172704.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151023_172704_t.jpg" alt="20151023_172704.jpg" title="20151023_172704.jpg, janv. 2017" /></a></p>
<p>So first some calculations: I'll be using a strip with a density of 30 led/m, which leads to 3.33x3.33cm per matrix cell. The LACK table is 55x55cm, but the matrix cannot go from edge to edge because each corner of the table contains a bloc of particle wood (see picture above) to which the foot is attached. The frame is about 1cm and blocks are about 4x4cm each but I figured I could chop the inner corners of the blocks to maximise surface, so I settled on a matrix of 14x14 cells (or 46.67x46.67cm). The ~4 cm left around the matrix will be used to hide connections and will help the table remain sturdy. So here's the "final cut":</p>
<p><a href="http://blog.deconinck.info/public/LedTable/20151204_112527.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151204_112527_t.jpg" alt="Final cut" title="Final cut, janv. 2017" /></a></p>
<p>OK, with these numbers, the absolute minimum bill of material is:</p>
<ul>
<li><a href="http://www.ikea.com/be/fr/catalog/products/20011408/">Lack table</a>: 6 EUR</li>
<li>Led strip <a href="https://www.aliexpress.com/item/led/32682015405.html">7m, 30 led/m</a> (which makes 210. I need 14x14=196): 19 EUR</li>
<li>Power Supply : <a href="https://www.aliexpress.com/item/PSU/32721747605.html">5V 20A</a> (196 x 3 x 20mA = 12A required for the leds alone): 14 EUR</li>
<li>Controller: <a href="https://www.aliexpress.com/item/Nano-3-0/32647196840.html">Arduino Nano clone</a>: 2 EUR</li>
<li>Translucent acrylic plate: This is the hardest part to find cheap <img src="http://blog.deconinck.info/public/generic/sad.png" alt="" />. I ended up buying it locally but I it's available <a href="https://www.plexiglas-shop.com">online</a> for 26.5 EUR, including s/h</li>
</ul>
<p>The rest (remains of white paint, cardboard, wires, etc.) I had laying around.
Less than 70 EUR qualifies for "cheap", and if you can find or recycle a power supply or an acrylic plate, you qualify for "dirt cheap" <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> .</p>
<p>To create cell separations, I used thin corrugated cardboard but quickly found that cutting rectangular noches with regular spacing and depth was a very tedious task, so I piled up all the cardboard strips, stuck them between two old wooden planks and let my circular saw do the job...</p>
<p><a href="http://blog.deconinck.info/public/LedTable/20151205_185824.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151205_185824_t.jpg" alt="20151205_185824.jpg" title="20151205_185824.jpg, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151205_185925.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151205_185925_t.jpg" alt="20151205_185925.jpg" title="20151205_185925.jpg, janv. 2017" /></a></p>
<p>Note that I first painted the cardboard, as well as the inside of the table and 4 small pieces of wood for the 4 inner sides in white. That way, inner reflections will increase brightness and level out the intensity of each cell. I should have taken more care though, because the veneer wood layer of the LACK table is so thin that my masking tape pulled it out at places <img src="http://blog.deconinck.info/public/generic/sad.png" alt="" /> . I guess you get what you pay for.</p>
<p><a href="http://blog.deconinck.info/public/LedTable/20151205_121627.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151205_121627_t.jpg" alt="Inside" title="Inside, janv. 2017" /></a></p>
<p>I then pasted 14-led segments of the the self-adhesive strips to the bottom of the table, and connected the signal in series from line to line (in a zig-zag fashion), while the power for each segment is distributed from a central point to avoid the voltage drop across the 7-meter strip.</p>
<p><a href="http://blog.deconinck.info/public/LedTable/20151205_124312.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151205_124312_t.jpg" alt="Pasting strip" title="Pasting strip, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151205_191240.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151205_191240_t.jpg" alt="20151205_191240.jpg" title="20151205_191240.jpg, janv. 2017" /></a></p>
<p>The result with the acrylic plate (protection sheet not fully removed yet) is quite satisfactory :</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/AgjCyW8-iQg" frameborder="0" allowfullscreen></iframe>
<br/>
<h4>Software</h4>
<h5>Arduino Nano</h5>
<p>The animation above is simply the output of the XYMatrix example which is part of the <a href="http://fastled.io/" hreflang="en">FastLed library</a> - highly recommended !</p>
<h5>Raspberry Pi</h5>
<p>For more complex animations, and to handle the interactive part, small microcontrollers would probably be very limiting, so I switched to a Raspberry Pi as the main controller.</p>
<p>Without any doubt, the king of diy led matrix animation software is <a href="http://www.solderlab.de/index.php/software/glediator" hreflang="en">Glediator</a>. On the plus side, it's a really neat piece of software which allows highly customised animations and supports many matrix types and configurations, and it's free <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> . It is written in Java, which is a surprising decision for a software where performance is important, but it runs relatively well on the Raspberry Pi. However, the main drawback is that it's not open source...</p>
<p>In that configuration, the Arduino Nano is used as a converter from the Pi's serial signal to the WS2812 protocol. That is required because the Glediator running on Linux can suffer inaccurate timings due to the non-realtime OS, the Java VM garbage collection, etc., while the WS2812 leds requires a very precisely timed data stream.</p>
<p>I had a Raspberry Pi 2 laying around so I used that but probably a Raspberry Pi Zero or an Orange Pi could do the job for less. Anyway, let's say <a href="http://www.gearbest.com/raspberry-pi/pp_354347.html?lkid=10327633">33 EUR for a RPi 3</a> to be futureproof.</p>
<p>So here are the steps I took to set up the system:</p>
<ul>
<li>Install VNC on Pi (see <a href="http://elinux.org/RPi_VNC_Server" hreflang="en">here</a>).</li>
<li>Free the serial port of the Pi (used for debug and as a shell by default in the distribution) using raspi-config in recent distributions - see <a href="https://learn.adafruit.com/adafruit-nfc-rfid-on-raspberry-pi/freeing-uart-on-the-pi" hreflang="en">here</a>.</li>
<li>Install RxTx Java serial lib on Pi using "sudo apt-get install librxtx-java"</li>
<li>Unzip Glediator on the Pi and create a startup script in the "dist" folder as follows:</li>
</ul>
<p><code>#!/bin/sh</code><br />
<code>CLASSPATH=/usr/share/java/RxTxcomm.jar</code><br />
<code>LD<sub>LIBRARY</sub>PATH=/usr/lib/jni</code><br />
<code>java -Djava.library.path=/usr/lib/jni -Dgnu.io.rxtx.SerialPorts=/dev/ttyAMA0 -jar Glediator_V2.jar</code><br /></p>
<ul>
<li>For the Arduino Nano, use the <a href="http://www.solderlab.de/index.php/downloads/file/33-ws2812-glediator-interface-v1">Arduino sketch</a> provided on Glediator's website.</li>
<li>Start Glediator and configure matrix size (14x14) and pattern (in my case VS_BL for <ins>V</ins>ertical <ins>S</ins>nake starting at <ins>B</ins>ottom <ins>L</ins>eft) "Output" in Glediator protocol in GRB with the serial port on 115200 bauds.</li>
</ul>
<p>A note about baudrate: Most websites talk about a baud rate of 1000000 or 500000 bps, but with my Raspberry Pi 2, I could not achieve that bitrate at first and could not get anything to work until I reduced speed to 115200 bps as indicated above. However, the Nano (which has a 16MHz clock) is able to go much higher.</p>
<p>After more search, I discovered that the limitation to 115200 was on the Raspberry Pi side. The solution to go beyond 115200 was found on <a href="https://www.raspberrypi.org/forums/viewtopic.php?f=44&t=73673" hreflang="en">this post</a>. So, to get 1000000 bps, I added the following line to the /boot/config.txt of the Raspberry Pi:<br />
<code>init<sub>uart</sub>clock=16000000</code><br />
(and reboot of course).</p>
<p>Here is the result. Awesome <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> :</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/7pAEwA8f0_A" frameborder="0" allowfullscreen></iframe>
<br/>
<br/>
<br/>
<div style="height:52px; width:100%; background-image: url('/public/LedTable/ws2812_1cell_sml.jpg'); background-repeat: repeat-x;"> </div>
<h3>And Now for Something Completely Different: Interactivity!</h3>
<h4>Hardware</h4>
<p>It seems quite clear that infrared reflection is the cheapest way to detect a presence at small distance. The <a href="https://youtu.be/OLfF4b49MLs">video by yohash84</a>, already mentioned, is a very good demonstration of how IR can be used. However, there are a few highlights in this project:</p>
<ul>
<li>it has to work through translucent acrylic</li>
<li>use 1 IR detector per visible led (yohash84 has 1 detector per cell of 4 or 6 visible leds)</li>
<li>favour a circuit simpler than <a href="http://blog.deconinck.info/public/LedTable/2017-01-08_1118.png">this</a> <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> , if possible (no offense)</li>
<li>oh, and keep it dirt cheap of course</li>
</ul>
<p>Basically, that last point made me choose the cheapest emitting ang receiving IR diodes I could find, which are <a href="https://www.aliexpress.com/item/Wholesale-100-pcs-lot-5mm-IR-Infrared-LED-940nm-Lamp-Transmitting-Tube-Emitting-Diode-High-Power/32355514775.html">these</a> (1.92EUR/100pc) and <a href="https://www.aliexpress.com/item/100pcs-LED-5mm-940nm-IR-Infrared-Receiving-Diode-Round-Tube-Black-Light-Lamp-Receiver-5MM-led/32355315102.html">these</a> (2.27EUR/100pc). As I need 200 of each, that adds 8.5EUR to the bill.</p>
<p>Of course, at that price point, one shouldn't expect to find a datasheet, but adding a resistor in series with a reverse-polarized receiving diode (more exactly a photodiode, or according to the measures probably a phototransistor, but let's just call it receiving diode for simplicity <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" />) and measuring the voltage drop gives an image of the incoming IR. The basic circtuit is thus:</p>
<p><img src="http://blog.deconinck.info/public/LedTable/2017-01-14_1854.png" alt="Basic schema" title="Basic schema, janv. 2017" /></p>
<p>The hardest point was to achieve reliable IR detection through translucent acrylic. My preliminary tests showed that many parameters influence the measured IR level:</p>
<ul>
<li>direct illumination: the risk is that the receiving led gets blinded by direct flow from the emitting led</li>
<li>external lighting: natural (sun) or artificial (TL) lighting generate much IR, with the unfortunate drawback that it acts "against" the expected IR reflection (the IR flow is reduced by objects that cast a shadow while the reflection against these objects should increase the flow)</li>
<li>internal reflections: this is the obvious enemy when putting a plastic plate in front of the leds, and particularly as it is translucent and not transparent</li>
<li>component dispersion and building imprecisions</li>
</ul>
<p>All these elements made accurate detection a real challenge. Here are the strategies I put in place to solve it:</p>
<ul>
<li>limit direct illumination by putting small tubes around the leds. These are made from black cocktail straws (add 2 EUR for 2x40pcs of 12cm, which is enough for 196x2x2cm).</li>
<li>eliminate influence of external IR sources by using a differencial mode: measure IR illumination when IR source is off (Ioff) and when IR is source is on (Ion), and consider that reflection is Ion-Ioff. This is not really accurate because the observed voltage drop is not proportional with the illumination, but it gives satisfying results nonetheless</li>
<li>reduce the "mirror" effect of the acrylic plate by not putting emitting and receiving diodes at opposite corners, but on two adjacent corners of the bottom, and make them aim at the center of the top surface. Diffusing objects such as paper or hand should reflect much IR while mirror reflection of the emitted IR light against the top plate should fall in the opposite corner and not on the receiving led (see geometric model below). Also, a calibration phase is applied to measure the received light when nothing is on the table vs when a full white surface (paper) is on layed upon the table.</li>
<li>compensate difference in component dispersion and led positions by making the calibration independant per cell.</li>
</ul>
<p>Here is a geometric model of one cell:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/annotated_cell_model.png"><img src="http://blog.deconinck.info/public/LedTable/.annotated_cell_model_t.jpg" alt="annotated_cell_model.png" title="annotated_cell_model.png, janv. 2017" /></a></p>
<p>As calibration must be made cell per cell, and with both low and high thresholds, using comparators and potentiometers was out of question. Calibration is performed in software at the very end (on the Raspberry Pi) and all measurements are achieved by the Arduino Nano using A/D inputs (note that it is a separate Arduino Nano from the one driving the led strip, which has very strict timing constraints. Here is how I came up with the final circuit, step by step:</p>
<p>Let's start with just one cell :</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_1_led.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_1_led_t.jpg" alt="1 cell" title="1 cell, janv. 2017" /></a></p>
<p>OK, now as we don't have 196 A/D input, we have to multiplex several cells per input, like so:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_2_leds_A__real_GND_.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_2_leds_A__real_GND__t.jpg" alt="2 cells A" title="2 cells A, janv. 2017" /></a></p>
<p>But if the anodes of receiving diodes are all connected to GND, then the measure will combine all cells, so we use a GPIO per "column" instead of GND. When low, it acts as GND and "selects" one column (one cell in this case):</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_2_leds_B__GPIO_bit_no_diode_.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_2_leds_B__GPIO_bit_no_diode__t.jpg" alt="2 cells B 1" title="2 cells B 1, janv. 2017" /></a></p>
<p>However, the "non-selected" columns will cause a problem. If we set their GPIOs to VCC, it will "short circuit" the A/D through the (now forward polarized) IR receiving diode. If we just leave them in high impedance (turning them to inputs), there is still a path that would "leak" through the IR emitter led. Both issues are illustrated below:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_2_leds_B<strong>GPIO<sub>bit</sub>no_diode</strong>problem_1.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_2_leds_B__GPIO_bit_no_diode__problem_1_t.jpg" alt="2 cells issue 1" title="2 cells issue 1, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/Model_2_leds_B<strong>GPIO<sub>bit</sub>no_diode</strong>problem_2.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_2_leds_B__GPIO_bit_no_diode__problem_2_t.jpg" alt="2 cells issue 2" title="2 cells issue 2, janv. 2017" /></a></p>
<p>The solution is to add a diode in series with each IR receiver, polarized the opposite way, like so:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_2_leds_C<strong>GPIO</strong>_diode_.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_2_leds_C__GPIO___diode__t.jpg" alt="2 leds C" title="2 leds C, janv. 2017" /></a></p>
<p>We need to be able to switch the emitting led on or off and compare the received IR intensity. To do so, as the "GND" of each cell is common to the emitting and receiving leds, the anode of the emitting led also has to be connected to a GPIO instead of VCC. When low, nothing will be lit, but when high, one emitting led (the one that has an both active "VCC" and active "GND") will turn on:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_2_leds_D__GPIO_GND_diode_GPIO_VCC_.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_2_leds_D__GPIO_GND_diode_GPIO_VCC__t.jpg" alt="2 leds D" title="2 leds D, janv. 2017" /></a></p>
<p>Now we can duplicate this structure for each row of the matrix. The simple solution using a single GPIO for all leds (at the cost of a transistor to increase the current) looks like this:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_4_leds_Abis__single_GPIO_for_VCC_.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_4_leds_Abis__single_GPIO_for_VCC__t.jpg" alt="4 cells A" title="4 cells A, janv. 2017" /></a></p>
<p>In that configuration, all leds of the same column having both VCC and GND active at the same time light up together, resulting in cross-cell illumination. Switching to one "VCC" GPIO per row avoids this issue by only activating one cell at a time. So the final model is the following:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Model_4_leds_B__GPIO_for_VCC_.png"><img src="http://blog.deconinck.info/public/LedTable/.Model_4_leds_B__GPIO_for_VCC__t.jpg" alt="4 cells B" title="4 cells B, janv. 2017" /></a></p>
<p>Here is a video showing the proof-of-concept of this circuit in action:</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/73JzLD2NXpM" frameborder="0" allowfullscreen></iframe>
<br/>
<p>Note that after more testing, in the final version, I slightly changed the resistor values and settled on 330 ohm for limiting resistors on the IR emitters and 38.3K (because that's the one slightly lower than 47K I had in stock) in series with the IR receivers.</p>
<p>Now we need to expand that to 14 rows by 14 cells, but of course, the number of A/D inputs is limited (8 per Arduino Nano) so two Nanos are used to perform measurements on all 14 lines. For the GPIOs, each Nano drives the "programmable VCC" for 7 rows. For "GNDs" however, we don't have enough I/Os for controlling each of them. The solution is to add an I/O expander controlled by the Nanos with fewer pins. I selected the MCP23017, which can be found for <a href="https://www.aliexpress.com/item/1PCS-MCP23017/32663513053.html">less than 1 EUR</a> incuding s/h (add 5 EUR to the bill, 2 per Nano and 1 for the expander).</p>
<p>Now on to the real thing: Get the soldering iron and wire stripper!<br />
To bend led legs precisely and assemble each of the 14 columns, I made a small jig reusing one of the planks that helped cut the notches in cardboard separators. One stripped wire for the "GND" is stretched between two screws and the angles and positions for the leds are drawn on the wood. One leg of each led (the correct one <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> ) is then bent and soldered to the wire. When all are soldered, the other led legs are bent up by the same angle and the diode preventing forward current through the IR receivers (see above) is soldered too:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/mounting_anim.gif"><img src="http://blog.deconinck.info/public/LedTable/.mounting_anim_t.jpg" alt="Mounting animation" title="Mounting animation, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151220_180945.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151220_180945_t.jpg" alt="One mounted row" title="One mounted row, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20151231_114300.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151231_114300_t.jpg" alt="4 lines" title="4 lines, janv. 2017" /></a></p>
<p>Then when all 14 columns are ready, two wires have to run perpendicularly for each line (one for "VCC" and one for measurements). Unfortunately these will cross the RGB led strips, so to avoid all risk of short circuit, they are only partially stripped, making a beautiful (and extremely boring to make) dotted pattern (grey and orange are for the 7 rows linked to the first Nano, brown and purple are for the second one) :</p>
<p><a href="http://blog.deconinck.info/public/LedTable/20151231_114254.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20151231_114254_t.jpg" alt="partially stripped wires" title="partially stripped wires, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20160101_173550.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160101_173550_t.jpg" alt="soldering in progress" title="soldering in progress, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20160124_181939.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160124_181939_t.jpg" alt="fully soldered" title="fully soldered, janv. 2017" /></a></p>
<p>... and with straws:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/20160214_114024.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160214_114024_t.jpg" alt="Cut straws" title="Cut straws, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20160214_142035.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160214_142035_t.jpg" alt="Swtraws" title="Swtraws, janv. 2017" /></a></p>
<p>Pretty cool, huh <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> ?</p>
<p>Controlling the GPIOs is easy, but there are two issues :</p>
<ul>
<li><strong>synchronization</strong>: the I/O expander for GNDs can only be driven by one I2C master, but two Arduino Nanos will be cycling the VCCs, and they have to perform the measurements in sync.</li>
<li><strong>communication</strong>: the Raspberry Pi has a single serial port (and I'd rather have the data coming from a single source anyway). Note that to double the scan frequency, I wanted to run the two Nanos in parallel, so the two halves of the table are scanned simultaneously. The cross illumination between cells 7 rows apart is minimal.</li>
</ul>
<p>Both issues were solved at once by multiplexing the Tx signals and having the Nanos "spy" on each other to speak in turn. One of Nanos is the "master" and sends its data first. The other Nano is the "slave", it listens to the master and then sends its data in turn. During that phase, the master listens to the slave, and when it's done sending, the next row is processed. When all rows have been processed, the loop restarts. The multiplexing of the Tx is done using a two diodes and a pull-up to 3.3V (remember the RPi logic levels are not 5V tolerant), creating an "or" gate, as follows:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/2017-01-20_2136.png"><img src="http://blog.deconinck.info/public/LedTable/.2017-01-20_2136_m.jpg" alt="Nano logic" title="Nano logic, janv. 2017" /></a></p>
<p>For simplicity, both Arduino Nanos are identical both in software and hardware, except that pin 2 selects the behaviour (master or slave) depending if it's high or low. Each of them has 4 connectors :</p>
<ul>
<li>a 3-pin header to the Raspberry Pi (GND, 5V, Tx) - any can be used. The Tx just requires a pull-up to 3.3V on the RPi side</li>
<li>a 4-pin header to the I/O expander (GND, 5V, SDA, SCL) - any can be used</li>
<li>a 4-pin header to the each other (GND, 5V, Rx, Tx). The cable crosses Rx and Tx</li>
<li>a 2x7-pin header to the led matrix (7x "VCC" and 7x measure)</li>
</ul>
<p>Here are the schematic and a few pictures (with the Nano removed first, and then fully integrated):</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Nano_setup.png"><img src="http://blog.deconinck.info/public/LedTable/.Nano_setup_m.jpg" alt="Nano schema" title="Nano schema, janv. 2017" /></a></p>
<p><a href="http://blog.deconinck.info/public/LedTable/20160105_224409.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160105_224409_t.jpg" alt="Nano top" title="Nano top, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20160109_145359.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160109_145359_t.jpg" alt="Nano bottoms" title="Nano bottoms, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/2017-01-15_17.13.51.jpg"><img src="http://blog.deconinck.info/public/LedTable/.2017-01-15_17.13.51_t.jpg" alt="Nano integrated" title="Nano integrated, janv. 2017" /></a></p>
<p>The I/O expander is on its own perfboard, but it's really straightforward, with its 4-pin connector and a 2x7-pin header to the 14 "GND" lines. Here's what it looks like:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/2017-01-15_17.08.56.jpg"><img src="http://blog.deconinck.info/public/LedTable/.2017-01-15_17.08.56_t.jpg" alt="IO Expander" title="IO Expander, janv. 2017" /></a>
<a href="http://blog.deconinck.info/public/LedTable/20160103_233453.jpg"><img src="http://blog.deconinck.info/public/LedTable/.20160103_233453_t.jpg" alt="IO Expander connected" title="IO Expander connected, janv. 2017" /></a></p>
<p>As you can see, I used flat cables with 2x7 pin connectors to distribute signals for rows and columns. <a href="https://www.aliexpress.com/item/Cable/32397457097.html">Add 4 EURs for those</a>.</p>
<p>So here is the fully cabled IR matrix:</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/eFnuFky8Q-c" frameborder="0" allowfullscreen></iframe>
<br/>
<p>And here is the general schema:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Block_schema.png"><img src="http://blog.deconinck.info/public/LedTable/.Block_schema_m.jpg" alt="Block Schema" title="Block Schema, janv. 2017" /></a></p>
<h4>Software</h4>
<h5>Arduino Nanos</h5>
<p>Each Nano has an outer loop that counts to 14 and sets one of the "GND GPIO" low in turn, while the other 13 are high.
Inside this loop, the nano basically performs 2x7 measurements in a loop which goes:</p>
<ul>
<li>measure row <n> (ambiant level)</li>
<li>set "VCC GPIO" high on row <n></li>
<li>measure row <n> again (reflection level)</li>
<li>set "VCC GPIO" low on row <n></li>
</ul>
<p>Then</p>
<ul>
<li>it compute the delta (reflection - ambiant)</li>
<li>if the Nano is master, it sends a "start of message" byte, then the column number (0-13), then the 7 deltas, then listens and waits until it gets a "end of message" emitted by the slave</li>
<li>if the Nano is slave, it listens until it gets a "start of message", counts the 7 deltas emitted the master, then sends its own 7 delta plus a "end of message" byte</li>
</ul>
<p>Then the Nano starts again with GND active on the next column.</p>
<p>One interesting thing to note is that the only sync between the two Nanos is the serial message. Even though one of them actually drives the I/O expander, the fact that they run the same code guarantees a high precision in timings as they run in parallel. The only time when code differs (the sending phase), I added a delay (measured by experience) on the slave to accomodate the time it takes the master to receive and detect the "end of message" byte.</p>
<p>Fun fact: one might wonder how we can be sure the Nanos are kept in sync, particularly regarding column numbers. Indeed, in case there is a temporary loss of connection, or one of the Nanos restarts, or after any condition happens that requires a resync, we have no guarantee that the outer loop will be at the same column number in both Nanos. Surprisingly, that doesn't really matter as long as the master is the one controlling the I/O expander. Indeed the only thing that matters is that the number sent by the master in the message is the number of the actual column powered by the expander. As the slave does not write the column number in the message, if it doesn't drive the expander either, the column number the slave "thinks" it is on does not matter : it just performs 2 measurements per row and returns their difference.</p>
<p>One more thing regarding the Arduino Nano: the Library to control the I/O expander was found in <a href="http://forum.arduino.cc/index.php?topic=37976.0">this thread</a>.</p>
<p>Here is the <a href="http://blog.deconinck.info/public/LedTable/IR_test.v14.zip">sketch I used</a>.</p>
<h5>Raspberry Pi</h5>
<p>In Glediator, a Generator is the primary class that renders contents. Generators can then be combined together to create Scenes, which can then in turn be combined to create PlayLists.</p>
<p>The basic idea is to create a Generator that takes the serial input from the Nanos, calibrates their values, and then renders them as a greyscale level on a matrix. After that, one can for example combine that matrix with a Rainbow Generator with a "multiply" operation, so that output is dark where no obstacle is "seen" by the IR, and rainbow-colored where an obstacle is detected.</p>
<p>Unfortunately, Glediator is not open source as I said, and has seen no evolution in the last two years. However, The great advantage of Java for hackers is that it is easy to change the behaviour of a Java program by overriding classes. So I quicky analyzed Glediator's classes to see what could be done, and I came up with a way to add new "generators" (much like extensions are added to browsers) by just giving replacement classes higher priority than the original ones in the classpath, and leaving the original Glediator distribution untouched.</p>
<p>I tried contacting the authors - SolderLed - several times to propose them to include those changes but got no reply. So I guess contributions are not welcome, or they just leave the project as abandonware <img src="http://blog.deconinck.info/public/generic/sad.png" alt="" />. In that case I hope SolderLed won't mind if I share the changes here.</p>
<p>For those interested, <a href="http://blog.deconinck.info/public/LedTable/glediator_v2.0.3_patch_extensions.zip">here is the code I sent them as a proof-of-concept</a>. It contains the original Glediator v2.0.3 + a patch that overrides 4 Glediator classes to recognize "pluggable" generators + two new sample Generators: the first is a dummy "Uniform" color generator which is similar to the "Black" generator but with any color, and the second one is a "Game of Life", just because... you know.</p>
<p>Here's a demo with those two :</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/9RrbzB5FMiM" frameborder="0" allowfullscreen></iframe>
<br/>
<p>Next step: The Glediator generator reflecting exactly the values received from the IR matrix as a greyscale value (ok, there is mirroring mistake which was fixed later on). As you can see, it worked pretty well first time, but note that this is the ideal case, without white cardboard separators and translucent acrylic. Keep an eye on the screen at the right:</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/kt4Gk3W6J1I" frameborder="0" allowfullscreen></iframe>
<br/>
<p>Note how the different cells have different values when nothing is in front of the leds. This is partly due to component dispersion, but also to the imprécisions in my build regarding led orientation. If they emitter and receiver are more or less "in front" of each other, the quantity of received IR can greatly vary, hence the calibration phase. The difference is made even less visible once the separators, and more importantly the translucent acrilyc plate are in place, so we need to amplify that difference, and that is the goal of the calibration phase too.</p>
<p>That calibration is thus done by determining low and high thresholds for each cell, and performing a linear interpolation in between, as follows:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Calibration_logic.png"><img src="http://blog.deconinck.info/public/LedTable/.Calibration_logic_t.jpg" alt="Calibration logic" title="Calibration logic, janv. 2017" /></a></p>
<p>So the generator is first put in calibration mode, where it records the extreme "high" and "low" values observed for each cell, and stores them in two arrays. When exiting calibration mode, all incoming values will be "scaled" according to those extreme values. To check how it behaves, I developed the following UI:</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/hEghIgpFDLw" frameborder="0" allowfullscreen></iframe>
<br/>
<p>With this approach, results are much better, even with the full separators and top plate:</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/neU2FcqpaUI" frameborder="0" allowfullscreen></iframe>
<br/>
<p>However, this greyscale version doesn't give good results when combined with other effects using a "multiply" operation, because calibration is made with white paper laying flat on the table, which produces much higher reflection than average skin waved at the surface, resulting in a very dim image. As a first measure, a "threshold percentage" was added to convert this greyscale to binary ("black or white") values. Of course, while this percentage is a general setting, the actual value of the threshold varies from cell to cell according to the calibration. For example, a 30% threshold would be computed as follows on two cells:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Calibration_with_threshold.png"><img src="http://blog.deconinck.info/public/LedTable/.Calibration_with_threshold_t.jpg" alt="Calibration with threshold" title="Calibration with threshold, janv. 2017" /></a></p>
<p>This is now much better:</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/Vxz86e1xT8s" frameborder="0" allowfullscreen></iframe>
<br/>
<p>As a last improvement, I replaced the unique threshold by a narrow linear interpolation from a low threshold to a high threshold. For example between 20 and 40%:</p>
<p><a href="http://blog.deconinck.info/public/LedTable/Calibration_with_2_thresholds.png"><img src="http://blog.deconinck.info/public/LedTable/.Calibration_with_2_thresholds_t.jpg" alt="Calibration with 2 thresholds" title="Calibration with 2 thresholds, janv. 2017" /></a></p>
<p>And the results are really cool <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /> :</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/OdJgoZ6M1Aw" frameborder="0" allowfullscreen></iframe>
<br/>
<br/>
<div style="height:52px; width:100%; background-image: url('/public/LedTable/ws2812_1cell_sml.jpg'); background-repeat: repeat-x;"> </div>
<h3>Final bill</h3>
<ul>
<li>Non-interactive version (see above): 70 EUR</li>
<li>Raspberry Pi 3: 33 EUR</li>
<li>IR leds: 8.5 EUR</li>
<li>Straws: 2 EUR</li>
<li>2 more Arduino Nano clones: 4 EUR</li>
<li>MCP23017 I/O expander: 1 EUR</li>
<li>Flat cables with 2x7 pin connectors: 4 EUR</li>
<li>Cables, soldering, resistors, perfboard, etc.: say 5-10 EUR</li>
</ul>
<p>Total: <strong>130 EUR</strong> (140 USD), or even less if you can recycle a power supply, a Raspberry Pi or a translucent acrylic plate.</p>
<p>For all the stuff there is in that table, I really think that's <em>dirt cheap</em>.</p>
<h3>Final demo</h3>
<p>pixel matrix + interactivity = ?</p>
<p>Tetris of course.</p>
<iframe width="530" height="298" src="https://www.youtube.com/embed/_74hIv6vHGE" frameborder="0" allowfullscreen></iframe>
<br/>
<p>I say "<em>Fucking awesome !</em>"</p>
<p>Thanks for reading <img src="http://blog.deconinck.info/public/generic/smile.png" alt="" /></p>