This is an open-source device I designed with the financial support of the Universities of Bern and Geneva, used for scientific outreach and communication, and as an art display! You can built your own with the elements on this page! If you build your own, please acknowledge this page when displaying the object.

If, after reading this page, you have questions, need help to build your own, or would like a quote for me to build one for you, don’t hesitate to contact me directly by email.

What is it?

Let start’s by a visual demonstration with a short video:

Inspired by the « symphony » of Trappist-1 created by SYSTEM Sounds and the Pinball from HotPopRobot (see below for videos of both), the « sound and light show » of Trappist-1 is a 1×1 meter model of the exoplanetary system Trappist-1, consisting of LED strips simulating the orbits of the seven Trappist-1 planets, with various luminous and acoustic signals to represent specific phenomena such as transits. The orbits are made to scale to the actual system: 1/200 000 000 000 spatially and 1/50 000 temporally (exception: the star would be too small to see at that scale so it was made bigger). In addition, the phases of the planets have been included to represent the system as accurately as possible. This outreach and education project is open-source, made with off-the-shelf components and relatively low budget, in order to be easily replicated by other interested scientists/institutes, communicators and educators. The design, based on Arduino electronics, makes it also very easily adaptable for rescaling (lower budgets), or for other systems like the Solar system, the Galilean moons (especially for a size comparison with Trappist, if done at the same scale), or any other multiplanetary systems.

Here are a video of the first version I built and a video of the second version with the sound switched on:

What is it for ?

This model can be used solely as a nice eye-catcher to get the attention of the public or an exhibition art-piece… In truth, I consider it only as an art piece and it works perfectly as a display piece on its on too. But not only! Indeed, placed correctly during a public event, it can be seen from afar and attract people, may they be younger or older. Then it can be used as a conversation starter, to quickly explain in 2 minutes the various phenomena it displays… or go in depth and use it for up to 30-45 minutes of explanations! Here is what is represented on the object:

  • On normal mode: each orbit is represented by an LED strip on low luminosity and with a specific colour. The position of a planet on the orbit, is represented by the LED at that position being at full luminosity. It gives the opportunity to talk about the periods of the planets, from 1.5 days to 18 days, the link to their semi-major axis, and compare this planetary system to our own.
  • It indicates the time of the transit of a planet by flashing all of the orbit at full luminosity, with the colour staying the same. It also plays specific sounds. I usually discuss here about the transit method for the detection of exoplanets, its values and its limits (e.g: stellar activity), the TTVs and the other methods of detection (RV mainly).
  • When two adjacent planets are in conjunction, a small portion of each orbit is flashing at full luminosity and in white. It also plays specific sounds. This is meant to be used as an introduction to the orbital resonances within the system (as a reminder: 8:5 5:3 3:2 3:2 4:3 3:2). Note: the periods of the planets have been slightly adjusted to be perfect resonances.
  • In addition, there is now at random time and with a random angle, an increase of the star luminosity, followed by a cone of orange LEDs at full brightness, from center to exterior, in a curved propagation. This represent the stellar activity, and the importance of coronal mass ejections for M-dwarves, which can be harmful for the development of life even if a planet is in the habitable zone. Of course, they are not at the temporal scale and with a realistic propagation, otherwise we would not have time to see them correctly. Naturally, the path is curved to be used as a starting point to discuss their composition, plasma properties, the solar wind, the sun and earth magnetosphere, the importance of the latter for life and technology, and the dangers posed to us by the Sun CME.
  • Following a request in a conference for a specific talk, I also have a version of the code where the colours of the orbits represent their situation in comparison to the habitable zone. Planets too close to the star have red orbits, too far: blue, while the ones in the habitable zone are green.

 

How is it made ? (in short)

This project was built with a budget of 250 to 500 euros of material. The total cost will be depending on the machines you have available at your host institution (e.g: laser cutter or CNC) or in a shared makerspace (e.g: FabLab, MakerSpace, etc.), as well as on the involvement (or not) of the persons in charge of your institute workshop. Structure: the current version of this model is built on a wood frame made of battens, on which lays 4 laser-cut plywood panels. Laser cutting the panels made it possible to obtain perfect circular grooves for the LED strips. If you have no other choice than carving/cutting manually, XPS foam/extruded polystyrene or EVA foam (a knife is enough then), can be an interesting choice, although they will be less resistant to damage. The wood is painted black for a better contrast, but can be painted in green where relevant to represent the habitable zone of the Trappist-1 star. A hemisphere of plastic is used for the star. Electronics: The main component cost-wise will be the individually addressable RGB LEDs strips (60/meter) for which you will need 3x5m meters. They are controlled by an Arduino Mega electronic board which can be easily programmed in a language similar to C, and which can also be reprogrammed on the fly to produce a different show. The choice of the Mega in the Arduino series is linked to memory problems to address several hundreds of LEDs at the same time. This number of LEDs also impose the need of a higher current than the Arduino can offer, which means a DC 5V 5A power supply will be necessary too. Finally, two small speakers and a switch will be needed. And of course cables, soldering tin, etc. The electronics schematics and the code to run on the Arduino are freely available below.

All you need to assemble it

The links correspond to the elements I have bought myself. As such, I can attest they are working properly. This doesn’t constitute however an endorsement of the brands and product listed nor a specific recommendation: you should use what is the most accessible to you and correspond.

Frame

  • Wood battens (4*1.50 meters), section as you prefer.
  • Thin plywood for the laser cutter.
  • Wood glue
  • Wood screws * 16 to 32 short and 8 long.
  • 4*90 degree brackets
  • Black paint, brush with acrylic, or spray paint.
  • Any kind of saw, ideally a bandsaw.
  • Rasp/File/Sandpaper.
  • A drill to pre-drill the holes for the screws, in order to avoid splitting the wood.
  • A half sphere of ~7-10cm diameter for the central star. It needs to let light go through. Personally I recycled the protection case of a Grand Ferrero Rocher as it has a nice yellow/gold colour working when the LEDs are off, and a nice wavy texture imitating stellar granulation and activity.
  • Eventually cotton wool to put in the sphere in order to diffuse light and avoid luminous spots.

If you don’t have access to a laser cutter, you can used extruded foam/polystyrene also called XPS foam, or multiple layers of foamcore, EVA foam (the material for floor jigsaw foam tiles) can also be a good option if you can access it easily. The circles for the LED can be carved with a sharpe knife, cutter or wood burner/soldering iron (wear a mask for the toxic fumes!!!). I recommend to protect the foam with 2 or more coats of ModPodge, PVA glue, wood glue or a similar product. Remember for painting: spray paint can melt the foam, so avoid it.

Electronics

  • Soldering iron
  • Soldering tin, and I strongly recommend to use flux to ease the soldering process, as well as a stand with clamps. The will very greatly improve the easiness of the assembly for a negligible additional cost.
  • Electronics cables/wire, you will need at least 15 meters, I recommend at least 0.1 mm² section wires.
  • One to two meters of cable to connect to the electrical network. I recommend adding a switch to it so you can turn it on/off without unplugging the device. Important: don’t let the device run permanently. The theoretical limit for the internal clock of the arduino is several days, but to avoid overheating, I recommend switching it off at least half an hour every 5-6h for an everyday use. To display it in the corridor of our institute, I added a programmable plug to handle it automatically. In case of events, it ran with no problem for 12h in a row with overnight breaks.
  • Power supply AC 110/200V to DC 5V-5A-25W. It is necessary to power so many LEDs, the 2A-10W should in theory be enough as all the LEDs should not be at full power at the same time, but given they are almost same price, I recommend to go for safety. I used this model.
  • An electronic card similar to an Arduino Mega, model ATmega2560. I used this model. Given we are addressing few hundreds of LEDs, it is imperative to take a Mega, as smaller models don’t have enough memory capacity to save all the LEDs.
  • Individually addressable RGB LEDs strips. You will need 3×5 meters. Aim for the 60 LEDs/meter strips. The 30/meter strips will look less good. Versions with 144 LEDs/meter exist also but will overload the arduino memory so should be avoided too. I used this model: please note that many types exist others than the WS2812B, it is perfectly fine to use other models, remember though if you do so that you might need to update sections of the code to match the new type of strips. You can decide to upgrade to 144 LEDs/meter strips for a substantial increase of cost, in which case you will need additional electronic (e.g.: multiple arduino board) and potentially a more powerful power supply.
  • Two small speakers for the sound. Important, you should put them in series with a switch to avoid getting the sound all the time: 15 minutes with it in continuous during an event will probably be driving you mad enough. To be truth, although it was part of the original plan, I ended up almost never switching it on as the light alone is more than enough and more comfortable for the staff around. As such, I only implemented a very basic sound, if you want to use sound extensively, I recommend upgrading it with something like an Adafruit Audio FX Sound Board for example.

I have 3D models and CAD drawings to be used with a laser cutter or other CNC tools available. Those are not directly available on my website as they are custom fitted to the specific elements I used. Please contact me directly, I will gladly send you a version customised to your specific other elements!!! To note: you will need 2 layers of ~3mm plywood for the build: one layer for all the cut arcs, and another one for support underneath.


This first code is the latest version and include the coronal mass ejection visualisation. Make sure to update it accordingly to the specificity of your own built (number of LEDs for each strips, etc.) It is heavily commented so you should have no problem finding what you search for. You should not need to update any elements after the command line « #define COLOR_ORDER ».

Click to expand and see the code

#include <FastLED.h> //This package is to be able to manage the LED strips, you can download it freely from GitHub and install it in the library folder of your arduino software.
//We define now the pins on the arduino ATmega2560 that we are going to use to address the led strips. One strip per planet orbit + one for the star. One data pin per strip.
#define LED_PINb 2
#define LED_PINc 3
#define LED_PINd 4
#define LED_PINe 5
#define LED_PINf 6
#define LED_PINg 7
#define LED_PINh 8
#define LED_PINstar 9
//We define the number of led on each strips, this was precalculated but adjusted with the real number of LED.
#define NUM_LEDSb 30
#define NUM_LEDSc 42
#define NUM_LEDSd 60
#define NUM_LEDSe 80
#define NUM_LEDSf 106
#define NUM_LEDSg 130
#define NUM_LEDSh 173
#define NUM_LEDSstar 15
//This is the type of LED we are using
#define LED_TYPE WS2812B
//This is the order in which each of the individual RGB led must be given color information.
#define COLOR_ORDER GRB //but we are still giving the color code in RGB order in this programm, it is just for the arduino side that we precise that…
//We define here the period of our planets, in 10*days (to get int). The longer periods have been slightly modified to get perfect resonant chain of 8:5, 5:3, 3:2, 3:2, 4:3, 3:2
#define periodb 15 // e.g: period of the first planet of the system.
#define periodc 24
#define periodd 40
#define periode 60
#define periodf 90
#define periodg 120
#define periodh 180
/*first transit c : 282.80570 Those are the days of the first transits of the various planets of Trappist-1, the identical part at the beginning of those numbers has been removed for better lisibility.
first transit g:294.78600
first transit e 312.71300
first transitf: 321.52520
first transit b:322.51531
first transit d:560.79730
first transit h: 662.55467*/
//We now define the phase shift as they are not all transiting at the same time (otherwise, at t=0 a planet is transiting). Phase is to remove from the current position, in a 0 to 1 phase of the orbit.
float dephaseb = (10.0*0.696)/float(periodb);
float dephasec = (10.0*1.875)/float(periodc);
float dephased = (10.0*1.903)/float(periodd);
float dephasee = (10.0*1.661)/float(periode);
float dephasef = (10.0*8.535)/float(periodf);
float dephaseg = (10.0*10.633)/float(periodg);
float dephaseh = (10.0*5.676)/float(periodh);
//The total period of the whole animation, in milliseconds. It means it is 2 times the period of the outer planets given the resonnance of 3:2 for the last couple of planets. We can’t really go faster than 15 seconds (5 seconds minimum to see orbits properly, after displaying is too slow and it makes jump some leds – less that 15 seconds is too fast to see something happening in the center and also the show function for led is quite slow), can’t be longer than 32,767 (int is stored on 16 bit).
#define totalperiod 31104 //This specific period make the mock-up is at the 1:50000 scale in time, while at scale 1:200 000 000 000 in space.
//Two variables to do the timing of the actions. First one is the start of the arduino (in milliseconds) and second one is the current time in millis.
unsigned long periodStartMillis;
unsigned long currentMillis;
//Define various colors, one for each planet et the star. Each has a « low » version more faint.
#define colorb ( 255, 0, 0); //Full red
#define colorblow ( 10, 0, 0);
#define colorc ( 215, 35, 0); //Orange
#define colorclow ( 9, 1, 0);
#define colord ( 175, 80, 0);//Yellow
#define colordlow ( 7, 3, 0);
#define colore ( 0, 255, 0);//Full green
#define colorelow ( 0, 6, 0);
#define colorf ( 10, 155, 100);//Cyan
#define colorflow ( 0, 7, 3);
#define colorg ( 0, 0, 255);//Full blue
#define colorglow ( 0, 0, 50);
#define colorh ( 100, 0, 155);//Purple
#define colorhlow ( 3, 0, 7);
#define colorstar ( 255, 3, 0);//Red with a little bit of orange
#define colorstarlow ( 10, 1, 0);
#define colorwhite ( 85, 85, 85);//White
#define colorwhitemax ( 255, 255, 255);
#define colorwhitelow ( 5, 5, 5);
#define coloroff ( 0, 0, 0);//Led off

//Define the led strips
CRGB ledsb[NUM_LEDSb];
CRGB ledsc[NUM_LEDSc];
CRGB ledsd[NUM_LEDSd];
CRGB ledse[NUM_LEDSe];
CRGB ledsf[NUM_LEDSf];
CRGB ledsg[NUM_LEDSg];
CRGB ledsh[NUM_LEDSh];
CRGB ledsstar[NUM_LEDSstar];

//The following variables are only to simulate a stellar flares
bool flareIsHappening=false;
unsigned long flareStartMillis;
int flareAngle;

void setup() {
// put your setup code here, to run once:
/*For test and debugging only

Serial.begin(9600);
pinMode(13, OUTPUT);
pinMode(22, INPUT);
*/
//Initiate the led strips
FastLED.addLeds<LED_TYPE, LED_PINb, COLOR_ORDER>(ledsb, NUM_LEDSb);
FastLED.addLeds<LED_TYPE, LED_PINc, COLOR_ORDER>(ledsc, NUM_LEDSc);
FastLED.addLeds<LED_TYPE, LED_PINd, COLOR_ORDER>(ledsd, NUM_LEDSd);
FastLED.addLeds<LED_TYPE, LED_PINe, COLOR_ORDER>(ledse, NUM_LEDSe);
FastLED.addLeds<LED_TYPE, LED_PINf, COLOR_ORDER>(ledsf, NUM_LEDSf);
FastLED.addLeds<LED_TYPE, LED_PINg, COLOR_ORDER>(ledsg, NUM_LEDSg);
FastLED.addLeds<LED_TYPE, LED_PINh, COLOR_ORDER>(ledsh, NUM_LEDSh);
FastLED.addLeds<LED_TYPE, LED_PINstar, COLOR_ORDER>(ledsstar, NUM_LEDSstar);
//define initial time for counting periods
periodStartMillis=millis();
//Light up the star with its permanent color
for (int i=0; i<= NUM_LEDSstar-1; i++){
ledsstar[i] = CRGB colorstar;
}
//Display the color indicated in previous instructions.
FastLED.show();
}

void loop() {
Serial.begin(9600);
// put your main code here, to run repeatedly:

/*For testing purpose only, idea was to get a button slowing down time when pressed. Caused problems.

int totalperiod = 15000;

Serial.print(digitalRead(22));
if (digitalRead(22)==LOW){
digitalWrite(13,HIGH);
totalperiod = 150000;}
else{
digitalWrite(13,LOW);
}*/

//The current time is updated
currentMillis=millis();
//The time since the start of the arduino and since first phase.
unsigned long periodTOTposition = (currentMillis-periodStartMillis);

// Calculate for each planet, first the time in the current orbit of the planet, and then the corresponding phase/position on the LED strip.
float periodbposition = periodTOTposition%(totalperiod/(periodh*2/periodb));
int PosLedsb=0;
if (((float(periodbposition)/float(totalperiod/(periodh*2/periodb))) – dephaseb)>0.0){
PosLedsb = int(((float(periodbposition)/float(totalperiod/(periodh*2/periodb))) – dephaseb)*(NUM_LEDSb));
}
else{
PosLedsb = int(((float(periodbposition)/float(totalperiod/(periodh*2/periodb))) – dephaseb + 1.0)*(NUM_LEDSb));
}

float periodcposition = periodTOTposition%(totalperiod/(periodh*2/periodc));
int PosLedsc=0;
if (((float(periodcposition)/float(totalperiod/(periodh*2/periodc))) – dephasec)>0.0){
PosLedsc = int(((float(periodcposition)/float(totalperiod/(periodh*2/periodc))) – dephasec)*(NUM_LEDSc));
}
else{
PosLedsc = int(((float(periodcposition)/float(totalperiod/(periodh*2/periodc))) – dephasec + 1.0)*(NUM_LEDSc));
}

float perioddposition = periodTOTposition%(totalperiod/(periodh*2/periodd));
int PosLedsd=0;
if (((float(perioddposition)/float(totalperiod/(periodh*2/periodd))) – dephased)>0.0){
PosLedsd = int(((float(perioddposition)/float(totalperiod/(periodh*2/periodd))) – dephased)*(NUM_LEDSd));
}
else{
PosLedsd = int(((float(perioddposition)/float(totalperiod/(periodh*2/periodd))) – dephased +1.0)*(NUM_LEDSd));
}

float periodeposition = periodTOTposition%(totalperiod/(periodh*2/periode));
int PosLedse=0;
if (((float(periodeposition)/float(totalperiod/(periodh*2/periode))) – dephasee)>0.0){
PosLedse = int(((float(periodeposition)/float(totalperiod/(periodh*2/periode))) – dephasee)*(NUM_LEDSe));
}
else{
PosLedse = int(((float(periodeposition)/float(totalperiod/(periodh*2/periode))) – dephasee +1.0 )*(NUM_LEDSe));
}

float periodfposition = periodTOTposition%(totalperiod/(periodh*2/periodf));
int PosLedsf=0;
if (((float(periodfposition)/float(totalperiod/(periodh*2/periodf))) – dephasef)>0.0){
PosLedsf = int(((float(periodfposition)/float(totalperiod/(periodh*2/periodf))) – dephasef)*(NUM_LEDSf));
}
else{
PosLedsf = int(((float(periodfposition)/float(totalperiod/(periodh*2/periodf))) – dephasef +1.0)*(NUM_LEDSf));
}

float periodgposition = periodTOTposition%(totalperiod/(periodh*2/periodg));
int PosLedsg=0;
if (((float(periodgposition)/float(totalperiod/(periodh*2/periodg))) – dephaseg)>0.0){
PosLedsg = int(((float(periodgposition)/float(totalperiod/(periodh*2/periodg))) – dephaseg)*(NUM_LEDSg));
}
else{
PosLedsg = int(((float(periodgposition)/float(totalperiod/(periodh*2/periodg))) – dephaseg + 1.0)*(NUM_LEDSg));
}

float periodhposition = periodTOTposition%(totalperiod/(periodh*2/periodh));
int PosLedsh=0;
if (((float(periodhposition)/float(totalperiod/(periodh*2/periodh))) – dephaseh)>0.0){
PosLedsh = int(((float(periodhposition)/float(totalperiod/(periodh*2/periodh))) – dephaseh)*(NUM_LEDSh));
}
else{
PosLedsh = int(((float(periodhposition)/float(totalperiod/(periodh*2/periodh))) – dephaseh + 1.0)*(NUM_LEDSh));
}

//For each planet we are now telling which LED should be on with the color full brightness to represent the planet. Also, If it is at transit time, then all the LED from the orbit are lighting up. Otherwise, if one of the two closest planet (one on each side interior and exterior) is in conjunction with it and the star, light up the 10 leds nearby in white).
//Otherwise, all the other led are in colour of orbit but low intensity value.

//Doing it for planet b
if (PosLedsb == 0 || PosLedsb == NUM_LEDSb-1 || PosLedsb == 1 ){
for (int i=0; i<NUM_LEDSb; i++){
ledsb[i]= CRGB colorb;
tone(13,1625,50);
}
}

else if(((float(PosLedsc+1)/(float(NUM_LEDSc))) >= ((float(PosLedsb+1)/(float(NUM_LEDSb)))-(0.5/(float(NUM_LEDSb))))) && ((float(PosLedsc+1)/(float(NUM_LEDSc))) <= ((float(PosLedsb+1)/(float(NUM_LEDSb)))+(0.5/(float(NUM_LEDSb)))))){
for (int i=-10; i<10; i++){
ledsb[(PosLedsb-i)%NUM_LEDSb]= CRGB colorwhitelow;
}
ledsb[PosLedsb]= CRGB colorb;
}
else {
for (int i=0; i<NUM_LEDSb; i++){
ledsb[i]= CRGB colorblow;
}
ledsb[PosLedsb]= CRGB colorb;
}

//Doing it for planet c
if (PosLedsc == 0 || PosLedsc == NUM_LEDSc-1 || PosLedsc == 1 ){
for (int i=0; i<NUM_LEDSc; i++){
ledsc[i]= CRGB colorc;
tone(13,1014,50);
}
}
else if(((float(PosLedsc+1)/(float(NUM_LEDSc))) >= ((float(PosLedsb+1)/(float(NUM_LEDSb)))-(0.5/(float(NUM_LEDSb))))) && ((float(PosLedsc+1)/(float(NUM_LEDSc))) <= ((float(PosLedsb+1)/(float(NUM_LEDSb)))+(0.5/(float(NUM_LEDSb)))))){
for (int i=-5; i<5; i++){
ledsc[(PosLedsc-i)%NUM_LEDSc]= CRGB colorwhite;
}
ledsc[PosLedsc]= CRGB colorc;
}
else if(((float(PosLedsd+1)/(float(NUM_LEDSd))) >= ((float(PosLedsc+1)/(float(NUM_LEDSc)))-(0.5/(float(NUM_LEDSc))))) && ((float(PosLedsd+1)/(float(NUM_LEDSd))) <= ((float(PosLedsc+1)/(float(NUM_LEDSc)))+(0.5/(float(NUM_LEDSc)))))){
for (int i=-10; i<10; i++){
ledsc[(PosLedsc-i)%NUM_LEDSc]= CRGB colorwhite;
}
ledsc[PosLedsc]= CRGB colorc;
}
else {
for (int i=0; i<NUM_LEDSc; i++){
ledsc[i]= CRGB colorclow;
}
ledsc[PosLedsc]= CRGB colorc;
}

//Doing it for planet d
if (PosLedsd == 0 || PosLedsd == NUM_LEDSd-1 || PosLedsd == 1 ){
for (int i=0; i<NUM_LEDSd; i++){
ledsd[i]= CRGB colord;
tone(13,606,50);
}
}
else if(((float(PosLedsd+1)/(float(NUM_LEDSd))) >= ((float(PosLedsc+1)/(float(NUM_LEDSc)))-(0.5/(float(NUM_LEDSc))))) && ((float(PosLedsd+1)/(float(NUM_LEDSd))) <= ((float(PosLedsc+1)/(float(NUM_LEDSc)))+(0.5/(float(NUM_LEDSc)))))){
for (int i=-5; i<5; i++){
ledsd[(PosLedsd-i)%NUM_LEDSd]= CRGB colorwhite;
}
ledsd[PosLedsd]= CRGB colord;
}
else if(((float(PosLedse+1)/(float(NUM_LEDSe))) >= ((float(PosLedsd+1)/(float(NUM_LEDSd)))-(0.5/(float(NUM_LEDSd))))) && ((float(PosLedse+1)/(float(NUM_LEDSe))) <= ((float(PosLedsd+1)/(float(NUM_LEDSd)))+(0.5/(float(NUM_LEDSd)))))){
for (int i=-10; i<10; i++){
ledsd[(PosLedsd-i)%NUM_LEDSd]= CRGB colorwhite;
}
ledsd[PosLedsd]= CRGB colord;
}
else {
for (int i=0; i<NUM_LEDSd; i++){
ledsd[i]= CRGB colordlow;
}
ledsd[PosLedsd]= CRGB colord;
}

//Doing it for planet e
if (PosLedse == 0 || PosLedse == NUM_LEDSe-1 || PosLedse == 1 ){
for (int i=0; i<NUM_LEDSe; i++){
ledse[i]= CRGB colore;
tone(13,402,50);
}
}
else if(((float(PosLedse+1)/(float(NUM_LEDSe))) >= ((float(PosLedsd+1)/(float(NUM_LEDSd)))-(0.5/(float(NUM_LEDSd))))) && ((float(PosLedse+1)/(float(NUM_LEDSe))) <= ((float(PosLedsd+1)/(float(NUM_LEDSd)))+(0.5/(float(NUM_LEDSd)))))){
for (int i=-5; i<5; i++){
ledse[(PosLedse-i)%NUM_LEDSe]= CRGB colorwhite;
}
ledse[PosLedse]= CRGB colore;
}
else if(((float(PosLedsf+1)/(float(NUM_LEDSf))) >= ((float(PosLedse+1)/(float(NUM_LEDSe)))-(0.5/(float(NUM_LEDSe))))) && ((float(PosLedsf+1)/(float(NUM_LEDSf))) <= ((float(PosLedse+1)/(float(NUM_LEDSe)))+(0.5/(float(NUM_LEDSe)))))){
for (int i=-10; i<10; i++){
ledse[(PosLedse-i)%NUM_LEDSe]= CRGB colorwhite;
}
ledse[PosLedse]= CRGB colore;
}
else {
for (int i=0; i<NUM_LEDSe; i++){
ledse[i]= CRGB colorelow;
}
ledse[PosLedse]= CRGB colore;
}

//Doing it for planet f
if (PosLedsf == 0 || PosLedsf == NUM_LEDSf-1 || PosLedsf == 1 ){
for (int i=0; i<NUM_LEDSf; i++){
ledsf[i]= CRGB colorf;
tone(13,266,50);
}
}
else if(((float(PosLedsf+1)/(float(NUM_LEDSf))) >= ((float(PosLedse+1)/(float(NUM_LEDSe)))-(0.5/(float(NUM_LEDSe))))) && ((float(PosLedsf+1)/(float(NUM_LEDSf))) <= ((float(PosLedse+1)/(float(NUM_LEDSe)))+(0.5/(float(NUM_LEDSe)))))){
for (int i=-5; i<5; i++){
ledsf[(PosLedsf-i)%NUM_LEDSf]= CRGB colorwhite;
}
ledsf[PosLedsf]= CRGB colorf;
}
else if(((float(PosLedsg+1)/(float(NUM_LEDSg))) >= ((float(PosLedsf+1)/(float(NUM_LEDSf)))-(0.5/(float(NUM_LEDSf))))) && ((float(PosLedsg+1)/(float(NUM_LEDSg))) <= ((float(PosLedsf+1)/(float(NUM_LEDSf)))+(0.5/(float(NUM_LEDSf)))))){
for (int i=-10; i<10; i++){
ledsf[(PosLedsf-i)%NUM_LEDSf]= CRGB colorwhite;
}
ledsf[PosLedsf]= CRGB colorf;
}
else {
for (int i=0; i<NUM_LEDSf; i++){
ledsf[i]= CRGB colorflow;
}
ledsf[PosLedsf]= CRGB colorf;
}

//Doing it for planet g
if (PosLedsg == 0 || PosLedsg == NUM_LEDSg-1 || PosLedsg == 1 ){
for (int i=0; i<NUM_LEDSg; i++){
ledsg[i]= CRGB colorg;
tone(13,199,50);
}
}
else if(((float(PosLedsg+1)/(float(NUM_LEDSg))) >= ((float(PosLedsf+1)/(float(NUM_LEDSf)))-(0.5/(float(NUM_LEDSf))))) && ((float(PosLedsg+1)/(float(NUM_LEDSg))) <= ((float(PosLedsf+1)/(float(NUM_LEDSf)))+(0.5/(float(NUM_LEDSf)))))){
for (int i=-5; i<5; i++){
ledsg[(PosLedsg-i)%NUM_LEDSg]= CRGB colorwhite;
}
ledsg[PosLedsg]= CRGB colorg;
}
else if(((float(PosLedsh+1)/(float(NUM_LEDSh))) >= ((float(PosLedsg+1)/(float(NUM_LEDSg)))-(0.5/(float(NUM_LEDSg))))) && ((float(PosLedsh+1)/(float(NUM_LEDSh))) <= ((float(PosLedsg+1)/(float(NUM_LEDSg)))+(0.5/(float(NUM_LEDSg)))))){
for (int i=-10; i<10; i++){
ledsg[(PosLedsg-i)%NUM_LEDSg]= CRGB colorwhite;
}
ledsg[PosLedsg]= CRGB colorg;
}
else {
for (int i=0; i<NUM_LEDSg; i++){
ledsg[i]= CRGB colorglow;
}
ledsg[PosLedsg]= CRGB colorg;
}

//Doing it for planet h
if (PosLedsh == 0 || PosLedsh == NUM_LEDSh-1 || PosLedsh == 1 ){
for (int i=0; i<NUM_LEDSh; i++){
ledsh[i]= CRGB colorh;
tone(13,131,50);
}
}
else if(((float(PosLedsh+1)/(float(NUM_LEDSh))) >= ((float(PosLedsg+1)/(float(NUM_LEDSg)))-(0.5/(float(NUM_LEDSg))))) && ((float(PosLedsh+1)/(float(NUM_LEDSh))) <= ((float(PosLedsg+1)/(float(NUM_LEDSg)))+(0.5/(float(NUM_LEDSg)))))){
for (int i=-5; i<5; i++){
ledsh[(PosLedsh-i)%NUM_LEDSh]= CRGB colorwhite;
}
ledsh[PosLedsh]= CRGB colorh;
}
else {
for (int i=0; i<NUM_LEDSh; i++){
ledsh[i]= CRGB colorhlow;
}
ledsh[PosLedsh]= CRGB colorh;
}

//Flare visualisation
//We first test if a coronal mass ejection is already occuring or not, to have only one at any time.
// Serial.println(flareIsHappening);
if (flareIsHappening) {
//if there is a CME, then we do all the step of displaying in it.
//During one second the brightness of the star increase and then it decreases back to normal in the next 1.6 seconds.
if ((currentMillis-flareStartMillis) < 1000){
#define colorstar ( 255, ((currentMillis-flareStartMillis)/6), 0);
for (int i=0; i<= NUM_LEDSstar-1; i++){
ledsstar[i] = CRGB colorstar;
}
}
if ((currentMillis-flareStartMillis) > 1000 && (currentMillis-flareStartMillis) < 2600){
#define colorstar ( 255, (167-(((currentMillis-flareStartMillis)-1000)/10)), 0);
for (int i=0; i<= NUM_LEDSstar-1; i++){
ledsstar[i] = CRGB colorstar;
}
}
//For the closest strip of LEDs, we set a part of it: in the direction of the CME, plus or minus a given angle (6 here), to the color orange. This occurs during time 1.5 to 6.55 seconds of the CME. The « +360 » is to avoid negative numbers.
if ((currentMillis-flareStartMillis) > 1500 && (currentMillis-flareStartMillis) < 6550){
int posLedsFlareStart = int((float(((flareAngle-6+360)%(360)))/(float(360)))*(NUM_LEDSb));
int posLedsFlareStop = int((float(((flareAngle+6+360)%(360)))/(float(360)))*(NUM_LEDSb));
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledsb[i] = CRGB colorc;
}
}
else {
for (int i=posLedsFlareStart; i<= NUM_LEDSb-1; i++){
ledsb[i] = CRGB colorc;
}
for (int i=0; i<= posLedsFlareStop; i++){
ledsb[i] = CRGB colorc;
}
}
ledsb[PosLedsb]= CRGB colorb;
}
//For the second strip, the timing are slightly delayed. The angle +- is bigger (7) to account for the progressive spread, and we can also notice a -4 offset, to give the curvature to the cone. We repeat the same operation for all the other strips after, increase the width of the angle and its offsett each time, as well as the timing delay.
if ((currentMillis-flareStartMillis) > 1900 && (currentMillis-flareStartMillis) < 7100){
int posLedsFlareStart = int((float(((flareAngle-7-4+360)%(360)))/(float(360)))*(NUM_LEDSc));
int posLedsFlareStop = int((float(((flareAngle+7-4+360)%(360)))/(float(360)))*(NUM_LEDSc));
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledsc[i] = CRGB colorc;
}
}
else {
for (int i=posLedsFlareStart; i<= NUM_LEDSc-1; i++){
ledsc[i] = CRGB colorc;
}
for (int i=0; i<= posLedsFlareStop; i++){
ledsc[i] = CRGB colorc;
}
}
ledsc[PosLedsc]= CRGB colorc;
}
if ((currentMillis-flareStartMillis) > 2400 && (currentMillis-flareStartMillis) < 7600){
int posLedsFlareStart = int((float(((flareAngle-8-10+360)%(360)))/(float(360)))*(NUM_LEDSd));
int posLedsFlareStop = int((float(((flareAngle+8-10+360)%(360)))/(float(360)))*(NUM_LEDSd));
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledsd[i] = CRGB colorc;
}
}
else {
for (int i=posLedsFlareStart; i<= NUM_LEDSd-1; i++){
ledsd[i] = CRGB colorc;
}
for (int i=0; i<= posLedsFlareStop; i++){
ledsd[i] = CRGB colorc;
}
}
ledsd[PosLedsd]= CRGB colord;
}
if ((currentMillis-flareStartMillis) > 3000 && (currentMillis-flareStartMillis) < 8100){
int posLedsFlareStart = int((float(((flareAngle-9-15+360)%(360)))/(float(360)))*(NUM_LEDSe));
int posLedsFlareStop = int((float(((flareAngle+9-15+360)%(360)))/(float(360)))*(NUM_LEDSe));
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledse[i] = CRGB colorc;
}
}
else {
for (int i=posLedsFlareStart; i<= NUM_LEDSe-1; i++){
ledse[i] = CRGB colorc;
}
for (int i=0; i<= posLedsFlareStop; i++){
ledse[i] = CRGB colorc;
}
}
ledse[PosLedse]= CRGB colore;
}
if ((currentMillis-flareStartMillis) > 3700 && (currentMillis-flareStartMillis) < 8650){
int posLedsFlareStart = int((float(((flareAngle-10-21+360)%(360)))/(float(360)))*(NUM_LEDSf));
int posLedsFlareStop = int((float(((flareAngle+10-21+360)%(360)))/(float(360)))*(NUM_LEDSf));
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledsf[i] = CRGB colorc;
}
}
else {
for (int i=posLedsFlareStart; i<= NUM_LEDSf-1; i++){
ledsf[i] = CRGB colorc;
}
for (int i=0; i<= posLedsFlareStop; i++){
ledsf[i] = CRGB colorc;
}
}
ledsf[PosLedsf]= CRGB colorf;
}
if ((currentMillis-flareStartMillis) > 4450 && (currentMillis-flareStartMillis) < 9200){
int posLedsFlareStart = int((float(((flareAngle-11-27+360)%(360)))/(float(360)))*(NUM_LEDSg));
int posLedsFlareStop = int((float(((flareAngle+11-27+360)%(360)))/(float(360)))*(NUM_LEDSg));
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledsg[i] = CRGB colorc;
}
}
else {
for (int i=posLedsFlareStart; i<= NUM_LEDSg-1; i++){
ledsg[i] = CRGB colorc;
}
for (int i=0; i<= posLedsFlareStop; i++){
ledsg[i] = CRGB colorc;
}
}
ledsg[PosLedsg]= CRGB colorg;
}
if ((currentMillis-flareStartMillis) > 5200 && (currentMillis-flareStartMillis) < 9900){
int posLedsFlareStart = int((float(((flareAngle-12-34+360)%(360)))/(float(360)))*(NUM_LEDSh));
int posLedsFlareStop = int((float(((flareAngle+12-34+360)%(360)))/(float(360)))*(NUM_LEDSh));
// Serial.println(posLedsFlareStart);
// Serial.println(posLedsFlareStop);
if (posLedsFlareStart <= posLedsFlareStop){
for (int i=posLedsFlareStart; i<= posLedsFlareStop; i++){
ledsh[i] = CRGB colorc;
}
}
else {
for (int i=0; i<= posLedsFlareStop; i++){
ledsh[i] = CRGB colorc;
}
for (int i=posLedsFlareStart; i<= NUM_LEDSh-1; i++){
ledsh[i] = CRGB colorc;
}
}
ledsh[PosLedsh]= CRGB colorh;
}

if ((currentMillis-flareStartMillis) >10000){
#define colorstar ( 255, 3, 0);
flareIsHappening=false;
for (int i=0; i<= NUM_LEDSstar-1; i++){
ledsstar[i] = CRGB colorstar;
}
}
}

//If there is no CME happening currently, we take a random value between 0 and 700, if it is equal to 20, then a CME visualisation start. Reduce/Increase the value of 700 if you want respectively CME to occur more/less often.
//We also draw randoly the angle of the CME.
else{
int randVal=random(700);
// Serial.println(randVal);
if ( randVal== 20){
flareIsHappening=true;
flareStartMillis=currentMillis;
flareAngle=random(360);
}
}
//Displaying all the LED change for this turn of the loop
//Giving the show() function is very slow, you should apply it only for all the LEDs at the same time, and not one by one as you do the calculation.
FastLED.show();
//End of the main loop
}

If you witness a bug, please contact me so I can update the code.

Note: the arduino Mega has multiple « 5V » and « GND » slots as visible on the schematic below. They are interconnected together so you can plug any element separately on a different 5V/GND slot of the card. It is particularly useful for example if you want your LEDs to be on one side of the arduino and the power supply on another, you don’t have to connect them to the same 5V/GND slot.

N.B.: All the strips are connected in parallel.

Start with the frame.

  1. Start cutting the wood battens with 45 degree angles and at the appropriate length in order to assemble a 1x1m square. Depending on your choice of material, you might want to slightly change the dimensions as the battens are the easiest thing to cut nicely probably. As an example, my first version being built with polystyrene sheets 1m long, I made the frame to encapsulate them leading to a 106*106cm frame instead.
  2. Assemble the battens with the short screws and the 90 degree angle brackets. Remember to pre-drill the holes so the screws don’t split the wood. I also cut small sections of battens to make 8 feet (two in each corner), using long screws. That enables to lay the model flat without resting on the electronic directly as the arduino and power supply  are most certainly thicker than the battens chosen. Those feet are also practical to grab the model when transporting it, or to wrap the alimentation cable around.
  3. Laser cut your panels and glue them together and with the frame. Or carve the circles in your material. The important part here is to make grooves that are 3 to 5 mm deep and that have a very slightly smaller thickness than your led strips in their squishy plastics tubing protection cases. That will allow to simply pinch them in the grooves and require no glue to assemble them. In addition, the tubing will act as a diffusive material to reduce the source point effect of the LEDs. The circular grooves need to have the following diameters: 173mm, 237 mm, 333mm, 438mm, 576mm, 701mm and 927mm. It is up to you to decided if this is the center, inner edge or outer edge of the circular groove, as long as you stay consistent. You will also need a groove adapted to the size of your half sphere for the star.
  4. Choose a direction that will be representing the direction of the transits. Your electronic assembly will be at the end of this line so choose in accordance. Along that direction, drill/cut holes in each groove to pass the cables and/or the LED strips. Don’t forget one in the center for the star’s strip.
  5. You want to paint in black the frame before to put any electronics in.

Now that the frame is built, we can take care of the electronics! There is no particular steps to follow, you just need to make sure to follow the electronic circuit indicated. I can however offer a few advices to avoid some mistakes:

  • A great feature of those LEDs strips is that they can be cut anywhere with simple scissor. For the length you need to cut, measure directly with the strips on the circle, make sure to cut a length with 1-2 (smaller ones) or 3-4 (longer ones) additional LEDs to be sure you will not miss any in the final assembly. You can still cut the extremity at the end of the assembly to remove the extra.
  • I recommend soldering all the cables to « electronic pin strips » and then clips those on the arduino. Makes sure to use the appropriate male or female one depending what is equipping your arduino model.
  • In ordre to reinforce the connexion during transport: you can put hot glue on the connexion once soldered. In addition to strengthen them, it will also act as an electrical insulation.
  • When measuring you cable length from the arduino to each LEDs strips, make sure to account for the thickness of your structure!!! (I did this mistake 😉 )
  • You can easily and quickly build boxes to hold the power supply and the arduino + speakers and switch, using scrap wood, plastic, plexiglass, cardboard, etc. Full disclosure, mine were simply assembled together, and attached to the frame with generous amount of hot glue as it was a prototype. I can also provided 3D models of boxes to print though if you would like, and would definitely do that to build a second copy!
  • Solder all the strips together and with the connector for the arduino, before to pass them through the holes in the frame. The longer you can solder the electronic without having to deal with this massive 1x1m square in your working area, the easiest will it be.
  • The LEDs strips have three ports on which you need to solder your cables. It is usually +, data and – in order, but this can change on your specific model so verify beforehand. There is a sense for the data input so make sure to spot the small arrow indicating it. In addition, to make it easier to fit through the holes, you should solder the cables with a 90 degrees angle. In order to avoid short-circuits, you can solder the middle cable on the back side of the strip pad: both side can be used to solder!
  • If you are not used to soldering, use a bit of flux on the soldering pads of the LEDs strip, and put a little bit of tin on them with the help of the iron. Do the same with the cables. Only after use the iron to remelt the pad and cable tin and solder them together. It will make it much easier to assemble than if you try to solder directly the cable to the pad without this preliminary step. It will also require less tin, thus reducing the size of the soldering point and the probability of short-circuits!
  • Take care when soldering on the LED strips to not overheat them. Indeed, between each LED of the strip you have a tiny controller soldered on the strip, overheating the strip can lead to damage on this micro controller and failure of the LEDs.
  • For safety so no one get electrocuted (specially if you bring this to public event), make sure the power supply is fully covered with insulant material so no one can touch its metal frame by accident.
  • Don’t forget to upload the code from a computer to your arduino, and to modify in the code the reference of LEDs strips if you use others, and/or the number of led in each strip if different.
  • Before to connect the arduino to the power supply, make sure to power the latter, and measure its output with a voltmeter, you should have a small screw to adjust the output in case it’s not 5V. Make sure it doesn’t go over 5.5V or it could damage the arduino. I recommend readjusting the output tension with the screw again when everything is connected. The planet g and h strips in particular being quite long, there is relatively important tension lost along the way, it is slightly visible during transit when all the LEDs of the strips are switched on. As such, to reduce this effect and to slightly increase the luminosity in general, I set my power supply to deliver 5.2V.
  • Once all your electronic is soldered together, and before to permanently attached any elements, you should test everything works correctly! Just like I did in this video.

Encountering any problem? Don’t hesitate to contact me!

Various shots of the prototyping phase.

The lightshow at the Trappist-1 conference in 2019 in Liège (Belgium), many thanks to the organisers for their support to enable me to participate and bring this item there!