Upgrading atokirina' lamp from Toruk (and Disney)

Started by Ertew, February 03, 2019, 04:34:31 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Ertew

Kaltxì.

Last year I saw Toruk show and... their merchandise store. Atokirina' lamp immediately catch my eyes. So I bought one, same as many of You.
(this applies to atokirina' lamp from ACE - Disney's Pandora as well)

But there's one small problem. Original lamp use rather standard RGB module that cycle color through whole spectrum of the rainbow. Imagine red and orange atokirina'  :(  oh wait, I don't need to imagine that  :facepalm:

So I as electronic engineer tried to solve this issue, and even add a bit of magic to my lamp.

Yeah, much better with white backlight and ability to mix a bit of RGB color to it 8)
I know, my project is pricey and probably overkill for the task, but I have fun making it and like to share my findings with You.
Feel free to follow my instructions or modify this project, as whole source code have to be freely available on github. (Once I finish the code. Right now i finished first hardware section.)


This project is planned to have 7 parts.

  • Theory of operation
  • Hardware selection
  • Software requirements
  • Wiring diagram and mechanical assembly
  • First run (test firmware)
  • Final version (my version) of the firmware
  • Improve me - place for future ideas, if anyone like to duplicate this project
At the moment I already finished hardware section (chapters 1-5) and take photo of every step. Sadly, describing that takes me longer than expected, especially when Discord brings up an interesting topic. So please be patient and feel free to ask any questions.

P.S. Please do not post this tutorial on other channels. Give me chance to post it officially after I finish all 6 chapters.


TL/DR
Open base of atokirina' lamp. Remove both red LEDs (red and orange). Solder single cold-white LED at that place. DONE. Your lamp no longer makes the atokirina' red.
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Toliman

It looks great :)

I hope that you will be successfull with this upgrade :)

Ertew

Chapter 1. A bit of theory.



  • What's the best light source?
    - LED of course  :D
    But there are so many types available...

    To clarify things, there are 3 main types of LEDs used in all sort of illumination projects.
    [2] RGB gives ability to mix basic Red, Green and Blue to get almost any color, except nice white.
    [6] White. IMHO gives far better light than mix of basic Red, Green and Blue. But lack of color.
    [4] RGBW is a combination of both worlds. Nice white and ability to add colors. My choice, but fell free to choose cheaper RGB.
    However LEDs require drivers. To simplify my build I choose most popular LED drivers available on the market - anything compatible with WS2811.
    [1] Spare driver, silicone chip inside plastic enclosure. It's name is WS2811. Not recommended
    [3] This is WS2812 - combination of driver and RGB LED in single package. Recommended if You choose RGB verson.
    [4] RGBW version is called SK6812, My choice, more expensive than RGB but still compatible with standard.
    [5] WWA version - newest, expensive, but very nice version. Single package combines cold white, warm white and orange.
  • What's the best way to drive LEDs?
    - Digital soul.
    Simple resistors can be used to set light intensity of any number of LEDs. But static mix of colors is boring. Analog circuit allows dynamic color changes, but even simple fade in/out circuit is rather complex. Also any change in circuit require soldering. Not so user-friendly. Simple digital chips doesn't add much possibilities. That's why I choose microprocessor. Thrust me, these days microprocessors are small, powerful and easy to use. Especially after Arduino comes to mainstream.
  • One LED, two LEDs... but how much LEDs You really need?
    In basic version, You need 1, 2, 3 or 4 LEDs (depend light power You expect) to replace original original light module and something to drive them. However small hole located on top of the base makes me wonder, what would happened it I add more LEDs, shine at the atokirina' on an angle and move the light source around? Can I add a bit movement to that sad, frozen (static) atokirina'? Sure I can  8) Here's my idea (quick draft) and first test.

    Isn't that wonderful?
    So that's the real starting point for my project. Make my atokirina' moving a bit and add ability to choose any color I like, excluding illegal ones.
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Ertew

Bonus chapter.
I check viewing angle of that engraving technique. Turns out, it's directional!
With bottom illumination: https://forum.learnnavi.org/gallery/?sa=view;id=9098
With side illumination: https://forum.learnnavi.org/gallery/?sa=view;id=9099
Please note black strip painted on the surface.
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Toliman

It looks great on these videos :) I would like to have one piece too!

Ertew

Chapter 2. Hardware selection.



  • Dynamic light source.
    As a secondary light source I choose LED ring as shown below.

    Inner diameter is around 15mm (0.6in), outer diameter 32mm (1.3in). Typical voltage: 5V (works fine on 4V too). LEDs type: SK6812 (WS2811 and WS2812 compatible), RGBcW version, addressable by single pixel. Pixel count: 8.
    This ring is the soul of my project. It enables the possibility to dance light around atokirina'.
    Yes, this is RGBW with cool white. It's more expensive than regular RGB but worth it. All my code is designed for RGBW modules but I can rewrite it if anyone can only get standard RGB version.
    Notes:
    - white PCB reflects more light. That's why I paint my module (black PCB). You can choose any version
    - 6 pixels ring will work as well as 7 pixels (6 + one in the middle). Bigger won't work because hole on the top of the stand was too small to pass through light.
    - LED strip with 60 LEDs per meter (or more) folded in ring will work better and it's actually cheaper per LED, but minimum order quantity of 1m isn't that cheap.
  • Static light source.
    As primary light source (no dynamic effects) I choose 4 WWA LEDs connected as one pixel, as shown on photo above. You can choose any type of LEDs. Single RGBW pixel would be best in this application. 2 pixels gives twice as much light and still fit inside the ring. I use WWA because all other RGBW pixels I have are warm white, not suitable for atokirina' :facepalm: And yes, 4 pixels is overkill here. 2 produces enough light to shine under desk lamp.

    Notes:
    - feel free to use anything You can get, a long as it's not warm white.
    - same type as pixels on ring are recommended, but not mandatory
    - monochromatic LEDs without controller will fit here too. Arduino still have few spare PWM pins to drive them.
  • Brain! I mean micro controller.
    Because of nature of this tutorial I limit range of possible options to 3 Arduino-compatible boards. Left to right:

    - Arduino Nano (and clones) - AtMega328 based board that include everything we need. Not recommended. Squeezing it inside lamp base require removing a lot of plastic around. Easiest way to do it is sacrifice battery compartment but I choose to avoid that solution. Working battery compartment might be useful one day.
    - Sparkfun Pro Micro (compatible w Ardiono Leonardo and Arduino Micro) - AtMega32U4 based board. Should work and fit. Not as standard as AtMega328 and require custom USB extension cable. Choose it if You wish to have a fun.
    - Arduino Pro Mini (and clones) - another AtMega328 based board. This one lacks USB->Serial converter but have standard CPU and fits perfectly. Coupled with standard USB->Serial converter makes perfect choice for my project.
    So I choose last option. Best for me and probably for You.

  • Missing piece? Serial converter and connector for power supply.
    I have few different types of USB to Serial converters. None of them fits perfectly. Fortunately one base on CP2102 chip was closest match.

    Few minutes with sandpaper and it fits right where power switch should be. And to be fair I don't miss old power switch. It was cheap and doesn't look durable.

  • Remote controller? Meet the MiLight system.
    Why MiLight? Because I already use it in my room. Why I choose it few years ago? Because Arduino library exists and can mimic both remote and receiver.

    There are 3 most popular RGB/RGBW remotes from MiLight family. First two works exactly the same way and perfectly match existing Arduino library. Third one wasn't tested yet with anybody. That's the reason why I can only recommend first two.
    But remote controller isn't all You need. nRF24L01 transceiver (thing that can either emit or receive radio waves) is mandatory to use MiLight system.

    There are two revisions of genuine chips, nRF24L01 and nRF24L01+ both available as DIP or SMD module. For this project I can't find any differences between them, except required soldering skills and pin order for SMD one. Chinese market also offers Si24R1 chips (clones often sold as genuine nRF24L01) and noname chips hidden under epoxy blob. My knowledge shows that Si24R1 clones works fine. On the other hand, "epoxy blob" works but with drastically reduced range, please avoid these ones.

    Any alternatives to MiLight? Yes, infrared TV remote controllers or buttons placed somewhere around the base. Both should works but I don't recommend them for esthetic reason.
    Hackers can also use serial port and sent commands from PC (wanna see server load directly on Your atokirina'? ;D ). Regular users can program single animation that plays in loop as long as lamp receives power.
    At this moment it should be obvious that loop through white-blue-green-white isn't for me, but i'm ready to make software to display just that pattern on Your lamp.
  • That's all?
    To make the lamp You also need:
    - soldering iron
    - thin wires
    - glue (hot-melt-goo is recommended)
    - permanent white marker (optional)
    - cutting tools and sandpaper
    - one Schottky barrier diode (if You need battery operation and USB connection at the same time)
    - computer access
    - a lot of time

    Now the list is complete and You have time to order all parts required.
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Toliman

Looks like as a lot of work, I hope that you will be successfull and all will be work :)

Lynxcat

This looks very interesting and complicated! You are very intelligent!  :) Txantsan!
Uniltirantokx fmawn! :) :ikran: http://avatarsequels.com/


Ertew

Chapter 3. Software requirements



  • Arduino IDE
    There are plenty ways to write code and many ways to compile it for AVR micro controllers. I choose Arduino because it's easiest one, not best one.
    Most of the code was written and tested in version 1.6.6, however I switch to newest available 1.8.8 and actually check everything once again. This mean, most versions should work for You.
    I recommend You choose portable version and actually made it portable, but this step isn't important.

  • Library for WS2811 family.
    This is true Arduino library. Available on GitHub (here) as well as directly from lib manager in IDE, see picture below.


  • Library for nRF24L01 radio.
    Same as above. Available on GitHub (here) and from IDE. We don't use this lib directly. It's required MiLight library.
    When both libraries are installed, menu examples should looks like this:


  • Library for MiLight radio.
    This one isn't true library. Instead it's an example project that works as a demo and nice base for future improvements. Full project is available on GitHub (here) as well as on Hackaday (because it's a hack, PL1167 used by MiLight isn't compatible with nRF24, author manage to reorder bits and make things working).
    Once downloaded, Henryk's project need some tweaks. For my atokirina' project I have to change pin mapping on line 9/10 to pins I use
          #define CE_PIN 10
          #define CSN_PIN 9

    and enable receiving by default on line 27.
          static bool receiving = true;
    Next You can solder things (oops, this have to be explained later) and check if Arduino actually receive anything from remote controller. If everything works, open terminal and You should see one line for each button pressed, like this:


  • Last thing operating system ;D
    Just to clarify. I use Win7 however Arduino IDE have to work on Win XP and better (excluding CE, PE, ARM and Phone editions) as well as any GUI-based Linux (including raspbian on rPi) or even Android (required phone with host/OTG port).



Note to admins: CODE tag breaks LIST tag. Any ideas?
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Tirea Aean



QuoteNote to admins: CODE tag breaks LIST tag. Any ideas?

what if you did something like...

[list type=decimal]
[li][spoiler][tt]<?php echo 'hello world'; ?>[/tt][/spoiler][/li]
[li][spoiler][tt]System.out.println("Hello World");[/tt][/spoiler][/li]
[li][spoiler][tt]int main(){ std::cout << "Hello World" << std::endl; }[/tt][/spoiler][/li]
[/list]



or

[table]
[tr]
[td]1.[/td]
[td][spoiler][tt]<?php echo 'hello world'; ?>[/tt][/spoiler][/td]
[/tr]
[tr]
[td]2.[/td]
[td][spoiler][tt]System.out.println("Hello World");[/tt][/spoiler][/td]
[/tr]
[tr]
[td]3.[/td]
[td][spoiler][tt]int main(){ std::cout << "Hello World" << std::endl; }[/tt][/spoiler][/td]
[/tr]
[/table]





1.
2.
3.

the only downside to tt tag is the syntax highlighting is lost.

Tìtstewan

#10
Quote from: Tirea Aean on February 19, 2019, 10:06:22 PM
QuoteNote to admins: CODE tag breaks LIST tag. Any ideas?
Will check...

EDIT:

Apparentely, the list tag don't like the code tag. :-\
I'd try to use the [php][/php] tag instead the [tt][/tt] one to fix that issue.

-| Na'vi Vocab + Audio | Na'viteri as one HTML file | FAQ | Useful Links for Beginners |-
-| Kem si fu kem rä'ä si, ke lu tìfmi. |-

Toliman

Eltur tìtxen si...

Great work here, this project is really remarkable! :)

Ertew

Wou. Thanks for fast answer. Now I also see who read this thread and almost silently wait for finish.
PHP tag inside a spoiler looks nice. On the other hand CODE tag looks much nicer so I plan to use it and skip the list on future posts.



Chapter 4a. Wiring time



  • Le picture aka wiring diagram.



    Important notes:
    - nRF24 module require 3.3V supply (or something between 2V and 3.6V). Yes, I tested it and the module perfectly survive 5V. Survive doesn't mean it works. SPI commands works fine so arduino thinks the nRF24 works. Radio part doesn't work at all on 5V, module can't speak or hear anything. Reconnect to 3.3V supply and module works fine.
    - Where 3.3V is? My USB->serial module have it on pinhead. Arduino Nano (with USB connector and serial converter on board) have 3.3V output.
    - Pro micro (AtMega32U4 based) don't have 3.3V output so in this case You need separate 3.3V voltage stabilizer.
    - You have to remove plastic stabilizer from nRF24 pinhead then desolder all pins to make it fit inside lamp. SMD version is better, sadly I drop last piece and don't like to solder it without test.
    - Diode allows to power the lamp from USB (higher voltage) when batteries are inside. Schottky diode recommended.
    - LEDs controlled by pin 6 (Adafruit standard). You can use any pin except analog pins.
    - SPI interface (pins 11-13) is a hardware feature, strict mapping, do not mess with it.
    - CE and CSN pins can be remapped to any other pins.
    - IRQ signal is useless in most examples. I add it because I have spare pins and wires.

  • 3D model
    This is how I place all parts around the plastic base. LED ring missing.
    - Blue link - serial (and programming) interface (3 wires).
    - Yellow link - single 3.3V power wire for nRF24



  • RGB(W) ring
    Just to set things clear. Outer ring are joining point for power wires.


    My WWA LEDs that fits in the middle of the ring. Single/double RGBW pixel is recommended, but I don't have pixels with cold white.

    [1] Note where the first pin mark is located. I solder both GND and OUT pins to copper tape. This short circuit might destroy out pin, but these LEDs are last in chain so I don't need output from them.
    [2] Cutout in copper tape to avoid short circuit between power pins and conducting tape.
    [3] All pins connected via enamel wire.

    Inner LEDs bonded to the ring.

    [1] Top view.
    [2] Solder mask removed from the ring to make extra GND and 5V pads. Copper wire soldered to GND pads on two sides (outer PCB track). Enamel wires solders respectively to new 5V pad and existing OUT pad.
    [3] Two power wires soldered to USB board. Other 3 wires goes directly to Arduino board.

  • nRF24 radio module
    Sorry, no color coding for the wires. Feel free to feel uncomfortable with this.
    All 8 pads of the nRF24 module are used in my build however INT signal isn't required in normal use. Of them 6 signals and ground connected to the Arduino. Flying wire is the 3.3V supply for radio module.


  • Last soldering
    Here I add diode (cyan circle), solder LEDs to Arduino and forget about serial lines (yellow line).
    At this point all parts nicely fits around the lamp base.


  • ... and the end
    Everything soldered and fixed in place using a lot of hot glue. And everything still working.




Note: Do NOT solder things at this moment! It's better to measure things, mount all that can be mounted using screws before soldering and mark position for remaining parts. Next unscrew things, solder and mount directly on marked positions.
Stay tuned for mechanical assembly tutorial, if You need it.
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Ertew

Chapter 4b. Mechanical assembly.



  • Remove old light guide.
    Genuine lamp base have black tube that hold LEDs in place and cover everything around. This have to be removed if You like to have animated lamp.

    If single RGBW pixel satisfies You, this tube can stay in place.
  • New light source and light guide.
    There's a bit of space between top cover and top of battery compartment. To fill that gap I made simple cylinder of white tape and place it on top of LED ring. Turns out that matching piece of broken LED tape sits next to me. That's the only reason why I choose it. Feel free to choose any more appropriate material.

    Because that tape is white, I decided to paint LED ring white to conserve every bit of light. Black finishing will waste reflect light, making final effect sharper. Which color You chose?

    After temporary assembly I check alignment. Ring isn't visible from top but visible on an angle. Perfect.
  • LED ring placement.
    Place the LED ring in the middle of battery compartment and close the base. Look through the window to check ring alignment.
    Open base, move ring a bit in right direction, close again, check, repeat...
    When LED ring is centered, hold it in place and mark the position as shown below.

    I also mark where connecting cables are to avoid mistake and mark position of the holes in PCB to drill plastic and mount ring using screws.
    Hot goo isn't the best choice here, because LEDs can generate a bit of heat. 
  • USB connector and serial converter.
    As I describe earlier, only two solutions fits inside lamp's base:
    a) bare USB connector mounted on PCB, working as extension cable.


    b) USB cable with plug hidden inside lamp, making it non-removable like in USB mouse.

    However this board almost fit inside my lamp

    so I decide to choose option c) make it fit inside using hacksaw. I choose to remove power switch and enlarge the hole. Other possible location for the hole for USB socket is on the opposite side of the lamp. Which mean I have to destroy TORUK logo on the front. To make it fit I made few things:
    [1] Reduce size of the converter. I can get 2mm of extra space by removing half of the soldering pads. Make sure You leave some part of the metallization inside pads (picture above).
    [2] Also remove as much of the PCB as possible from the socket side (same picture).
    [3] Remove half of the plastic brackets that hold switch in place. See photo below. Yellow contours shows plastic that I removed. If You choose same converter as I do, trimmed bracket fits between components and directly supports PCB. This makes the PCB sits stable even without adding glue.

    [4] Trim top part of the lamp base to make space for USB socket which is wider than previous switch. Make sure You trim correct side of the slot. Otherwise there might be a problem screwing plastic parts back together.

    [5] Solder everything together.
    [6] Glue everything together.
  • Final assembly.
    Now it's time to solder things together. Then glue it all. I do it in following order. Feel free to improve this list.
    [1] LED ring with center pixels.
    [2] LED ring to serial converter.
    [3] Solder Arduino to nRF24 module and nRF to 3.3V source.
    [4] Screw (glue) LEDs in place.
    [5] Push USB board in place and glue it.
    [6] Solder all remaining wires - between LED ring and Arduino.
    [7] Plug power and make sure everything works. Upload example codes to test both LEDs and nRF24 module (if exists in Your project).
    [8] Glue remaining parts - Arduino and radio module.
    [9] Screw top and bottom parts together, then place atokirina' (glass ball) on top of the base.




    And that's all. Your lamp is ready to play with the software.
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Toliman


Ertew

Irayo ma Toliman. Now all I need is a soul (software/firmware) for this lamp. Any ideas?


So here is chapter 5: First run (test firmware)

[A] Test radio receiver and remote controller. For this test use openmili sketch "as is", just modify 3 lines as described few posts earlier.
QuoteFor my atokirina' project I have to change pin mapping on line 9/10 to pins I use
      #define CE_PIN 10
      #define CSN_PIN 9

and enable receiving by default on line 27.
      static bool receiving = true;
Once uploaded, open serial monitor, change connection speed (bitrate) to 115200 bps, wait for welcome message and push any key on remote.
If You see 7 groups of 2 characters (7 bytes in hexadecimal form) and trailing dots, one line for each pressed button, everything works fine. If receiver doesn't work, check supply voltage for nRF24 module (have to be between 3V and 3.6V) check wiring, and ask for help.
Last hint, remote controller have a LED that should shine with each button press.

[B] Play a bit with LED ring. Yes, You can use demo sketch provided by Adafruit and included in library. Make sure You use 'standtest' for RGB ring or 'RGBWstandtest' for RGBW ring. Wrong option will give You misalignment colors. All You might like to change in that code is PIN number (6 is standard and I solder wire right there) and number of LEDs
      #define NUM_LEDS 10
In my case 10 is fine and more is just a waste of memory/time.
For taking photos I prepare simple code, as shown below.
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif

#define PIN 6

#define NUM_LEDS 10

//#define BRIGHTNESS 50

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRBW + NEO_KHZ800);

byte neopix_gamma[] = {
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
    2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
    5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
   10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
   17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
   25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
   37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
   51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
   69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
   90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
  115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
  144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
  177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
  215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };


void setup() {
  // This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
  #if defined (__AVR_ATtiny85__)
    if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
  #endif
  // End of trinket special code
//  strip.setBrightness(BRIGHTNESS);
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
}

void loop() {
  // Some example procedures showing how to display to the pixels:

  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(255,0,0,0) ); }
  strip.setPixelColor(8, strip.Color(0,0,0,0) );
  strip.show(); // RED ring
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,255,0,0) ); }
  strip.setPixelColor(8, strip.Color(0,0,0,0) );
  strip.show(); // GREEN ring
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,0,255,0) ); }
  strip.setPixelColor(8, strip.Color(0,0,0,0) );
  strip.show(); // BLUE ring
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,0,0,255) ); }
  strip.setPixelColor(8, strip.Color(0,0,0,0) );
  strip.show(); // WHITE ring (from rgbW)
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,0,0,0) ); }
  strip.setPixelColor(8, strip.Color(255,0,0,0) );
  strip.show(); // middle dot - RED (RGBW) or amber (WWA)
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,0,0,0) ); }
  strip.setPixelColor(8, strip.Color(0,255,0,0) );
  strip.show(); // middle dot - GREEN (RGBW) or cool white (WWA)
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,0,0,0) ); }
  strip.setPixelColor(8, strip.Color(0,0,255,0) );
  strip.show(); // middle dot - BLUE (RGBW) or warm white (WWA)
  delay(2000);
 
 
  for(uint8_t i=0; i<8; i++) { strip.setPixelColor(i, strip.Color(0,0,0,0) ); }
  strip.setPixelColor(8, strip.Color(0,0,0,255) );
  strip.show(); // middle dot - WHITE (RGBW) or black (WWA)
  delay(2000);
 
 
 
  colorWipe(strip.Color(255, 0, 0), 50); // Red
  delay(500);
  colorWipe(strip.Color(0, 0, 0), 50); // black
  colorWipe(strip.Color(0, 255, 0), 50); // Green
  delay(500);
  colorWipe(strip.Color(0, 0, 0), 50); // black
  colorWipe(strip.Color(0, 0, 255), 50); // Blue
  delay(500);
  colorWipe(strip.Color(0, 0, 0), 50); // black
  colorWipe(strip.Color(0, 0, 0, 255), 50); // White
  delay(500);
  colorWipe(strip.Color(0, 0, 0), 50); // black

  pulseWhite(5);
}

// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void pulseWhite(uint8_t wait) {
  for(int j = 0; j < 256 ; j++){
      for(uint16_t i=0; i<strip.numPixels(); i++) {
          strip.setPixelColor(i, strip.Color(0,0,0, neopix_gamma[j] ) );
        }
        delay(wait);
        strip.show();
      }

  for(int j = 255; j >= 0 ; j--){
      for(uint16_t i=0; i<strip.numPixels(); i++) {
          strip.setPixelColor(i, strip.Color(0,0,0, neopix_gamma[j] ) );
        }
        delay(wait);
        strip.show();
      }
}


This code cycle through colors, separately for ring and middle dot. And that's how it looks:
RED, GREEN and BLUE - basic colors provided by RGBW ring.


Cool WHITE. Left - provided by the ring. All 8 pixels at full power. Right - provided by middle pixel. 4 LEDs gives same amount of light.
Note how the light spreads. Ring illuminates the outside surface more. Middle pixel illuminates middle part of atokirina' more. And I'm a bit disappointed here. The difference might be bigger than this.


And just for compare. I use WWA pixels in the middle. This gives me cool white, warm white (left) and amber (right). I should use RGBW pixels or even remove these LEDs completely, but that's all I have when I start soldering.


Stay tuned for final project and few videos  :toruk:
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Eìrä

Great and awesome and very interesting and beautiful work ma Ertew! :D :D :D
Awesomeeee!
This BEAUTIFUL Atokirina lamp is amazing and very beautiful! ;)
Oe tsun pivlltxe nìNa'vi nìteng ma eylan! Oel ayngati kameie nìwotx! 'Rrta lu oeyä kelku!

FÌTSENGE LU AWNGEYÄ!
LN Community - Second Family and Home!

Feel Free To Correct My Na'vi Writing If I Did Mistakes!
 Oe lu numeyu numtsengä alu LearnNa'vi! Irayo nìtxan ta ma oeyä Ayeylan ulte ta ma Tìtstewan!
Nìolo' Pxoeng Seykxel Ma Eylan!

KEMÌRI A NGARU PRRTE' KE LU, TSAKEM RÄ'Ä SIVI AYLAHERU!

Ta:Eìrä :-*

Ertew

Chapter 6. Fina soul. Final version of the firmware.

I like to mention that my project is finished.
Version 1.0 of the code is available on Github and ready to compile.
Video documentation is available on forum gallery and on Discord (5 videos).
Because so much LEDs gives me so much abilities, current version of the code requires it's own remote controller. I think I can reduce functionality a bit and let my code use only one of 4 channels available from MiLight remote controller, but at the moment it's still in beta (experimental) version.

Current code have 3 modes:
- Breathe (swing the light between middle pixel and ring). As You may see on "breathe" video, it isn't as spectacular as I expected. Either ring and middle pixel allows full RGBW control. This mode allows to compare white from W LEDs vs white from RGB LEDs.
- Ugly rotate. The light is created by 2 points on the ring and a middle pixel (each have it's own brightness, color and size controll). That 2 points are switched between LEDs and just turns around the LED ring. How ugly it is? Just look at "blinky" video. I leave the code as is because it's rather simple to analyze.
- Nice rotate. Same as above, but LEDs doesn't blink. Instead, first and last LED of each color is faded in and out as the lights turns around the ring. This mode is visible on "rot1" and "rot2" videos.
And there's also "halo" video, which shows that the glass ball as focus the light beam. This creates interesting effect on surface above, especially ceiling.

How You can interact with the lamp and why remote controller is so important?
Right now, each mode is controlled via 20 variables (not all are used in my modes, feel free to code something different). Remote controller allows to control 2 variables at a time. One in range 0-255 via color wheel, second in range 0-31 via brightness slider. That creates a bank of variables reachable by the remote. To switch the banks use on/off switches. Bank 0 is available via "main on", 1 via "main off", next ones via on/off for channel 1, 2, 3 and 4.
All 3 modes mentioned earlier can be switched by "S+" and "S-" buttons. And each mode have it's own independent set of variables, which mean the modes doesn't interact with each other. Also the variables are preloaded by me, which mean Atokirina' can work without remote, it's just limited to one of the animations.
And for advanced users, serial interface throws a lot of debugging information as well as allow to manipulate all of the variables.

If github die for any reason, here is the code, split into several parts to add a brief comment between them.

#include <SPI.h> // require Arduino
#include <nRF24L01.h>  // require https://github.com/nRF24/RF24
#include <RF24.h>
#include <printf.h>
#include <Adafruit_NeoPixel.h> // require https://github.com/adafruit/Adafruit_NeoPixel
#ifdef __AVR__
  #include <avr/power.h>
#endif

#include "PL1167_nRF24.h"
#include "MiLightRadio.h" // require https://github.com/henryk/openmili

#define CE_PIN 10
#define CSN_PIN 9
#define PIN 6
#define NUM_LEDS 10

RF24 radio(CE_PIN, CSN_PIN);
PL1167_nRF24 prf(radio);
MiLightRadio mlr(prf);

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRBW + NEO_KHZ800);

byte neopix_gamma[] = {
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
    2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
    5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
   10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
   17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
   25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
   37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
   51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
   69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
   90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
  115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
  144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
  177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
  215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };

#define modes 4
uint8_t curr_mode = 2;
uint8_t curr_bucket = 0;
uint8_t settings[modes][20] = {
            // bank 0
        8, 0, 0, 0, // main (speed)
        0, 0, 0, 0, // 1 (R, Y)
        0, 0, 31, 0, // 2 (G, cW)
        0, 0, 0, 0, // 3 (B, WW)
        31, 0, 0, 0, // 4 (W, spare)
            // bank 1
        16, 0, 0, 0, // main (speed)
        31, 0x90, 0, 64, // 1 (power, color, white power, LED count)
        0, 0, 20, 64, // 2 (power, color, white power, LED count)
        8, 0, 0, 0, // 3 (power, color, white power, LED count)
        0, 0, 0, 0, // 4 (power, color, white power, LED count)
            // bank 2
        24, 0, 0, 0, // main (speed)
        31, 0x90, 0, 140, // 1 (power, color, white power, LED count)
        0, 0, 31, 30, // 2 (power, color, white power, LED count)
        0, 0, 0, 0, // 3 (power, color, white power, LED count)
        0, 0, 0, 0, // 4 (power, color, white power, LED count)
       
        };
//uint8_t key[2] = {0x42, 0x75}; // work only with one remote
//uint8_t key[2] = {0xF3, 0x68}; // work only with one remote
uint8_t key[2] = {0x00, 0x00}; // work with any remote
uint8_t animation_counter=0;

void setup()
{
  Serial.begin(115200);
  printf_begin();
  delay(100);
  Serial.println("# It's ready! ");
  mlr.begin();
  //strip.setBrightness(60);
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  //[0]={0,0,3};

 
}


static int dupesPrinted = 0;
static uint8_t outgoingPacket[3];
static uint8_t outgoingPacketPos = 0;
static uint8_t nibble;
static enum {
  IDLE,
  HAVE_NIBBLE,
  COMPLETE,
} state;


uint8_t timer =0;
void loop()
{
  if (mlr.available()) {
    update_settings();
  }

  timer++;
  if (timer==0) {
    mlr.begin(); // a little hack - reinitialize nRF24 because it like to die from time to time
  }

  int dupesReceived = mlr.dupesReceived();
  for (; dupesPrinted < dupesReceived; dupesPrinted++) { // just for visualization on serial terminal
    printf(".");
  }

  while (Serial.available()) {
    char inChar = (char)Serial.read();
    uint8_t val = 0;
    bool have_val = true;

    if (inChar >= '0' && inChar <= '9') {
      val = inChar - '0';
    } else if (inChar >= 'a' && inChar <= 'f') {
      val = inChar - 'a' + 10;
    } else if (inChar >= 'A' && inChar <= 'F') {
      val = inChar - 'A' + 10;
    } else {
      have_val = false;
    }

    if (have_val) {
      switch (state) {
        case IDLE:
          nibble = val;
          state = HAVE_NIBBLE;
          break;
        case HAVE_NIBBLE:
          if (outgoingPacketPos < sizeof(outgoingPacket)) {
            outgoingPacket[outgoingPacketPos++] = (nibble << 4) | (val);
          } else {
            Serial.println("# Error: outgoing packet buffer full/packet too long");
          }
          if (outgoingPacketPos >= sizeof(outgoingPacket)) {
            state = COMPLETE;
          } else {
            state = IDLE;
          }
          break;
        case COMPLETE:
          Serial.println("# Error: outgoing packet complete. Press enter to send.");
          break;
      }
    } else {
      switch (inChar) {
        case ' ':
        case '\n':
        case '\r':
        case '.':
          if (state == COMPLETE) {
            printf("\n");
            for (int i = 0; i < sizeof(outgoingPacket); i++) {
              printf("-%02X-", outgoingPacket[i]);
            }
            write_settings();
            printf("\n");
          }
          if(inChar != ' ') {
            outgoingPacketPos = 0;
            state = IDLE;
          }
          break;
      }
    }
  }
  update_LED();
}

Here is the main loop which takes care of incoming data and plain template for the code. Loop checks if radio receiver hears anything, read and assembly commands received via serial port and reset the radio from time to time, just because this dirty trick fixes stability issues.


void update_settings(void) { // decode radio remote commands
  printf("\n");
  uint8_t packet[7];
  size_t packet_length = sizeof(packet);
  mlr.read(packet, packet_length);
 
  for (int i = 0; i < packet_length; i++) {
    printf("%02X ", packet[i]);
  }
  if ( ((!key[0]) && (!key[1])) || ((key[0]==packet[1]) && (key[1]==packet[2])) ) {
    printf(" ok ");

    if (packet[5]==0x0B) {
      curr_mode++;
      if (curr_mode>=modes) {curr_mode = 0;} //overflow
      printf("mode %02X ", curr_mode);
    }
    if (packet[5]==0x0C) {
      curr_mode--;
      if (curr_mode>=200) {curr_mode = modes-1;} //underflow
      printf("mode %02X ", curr_mode);
    }

    if (packet[5] && packet[5]<=0x0A) { // ALL on/off buttons as bucket selector
      curr_bucket = (packet[5]-1)*2;
      printf("bucket %02X ", curr_bucket);
    }

    if (packet[5]==0x0E) { // brightness slider, encoded as top 5 bits, zero in the middle, negative scale, a bit offset too
      uint8_t input = -packet[4]-108; // this negative value is experimental, may need some tweaks.
      input = input >>3;
      settings[curr_mode][curr_bucket] = input;
      printf("mode %02X, bucket %02X, value %02X ", curr_mode, curr_bucket, input);
    }
    if (packet[5]==0x0F) { // color wheel
      settings[curr_mode][curr_bucket+1] = packet[3];
      printf("mode %02X, bucket %02X, value %02X ", curr_mode, curr_bucket+1, packet[3]);
    }
   
  } else {
    printf(" wrong remote ");
  }
}

void write_settings(void){
  if (outgoingPacket[0]>= modes) { // mode have to be less than array size
    printf("\nERR mode have to be less than %02X \n", modes);
    return(0);
  }
  if (outgoingPacket[1]>= sizeof(settings[modes])) { // also buckets are limited
    printf("\nERR bucket have to be less than %02X \n", sizeof(settings[modes]));
    return(0);
  }
 
  curr_mode = outgoingPacket[0];
  curr_bucket = outgoingPacket[1];
  settings[curr_mode][curr_bucket] = outgoingPacket[2]; // note the brightness is in range 0-31 (hex 0x1f)
  printf("\n OK, saved! ");
}

These two functions update the settings. First one works with MiLights radio packets. More info about these packets, structure and keys mapping is available on Hackaday.
Important note, each remote have unique ID. This code can listen them all or just selected one.
Second function do similar thing with 3-bytes serial commands.

void update_LED(void) {
  if(curr_mode==0) {

    uint8_t brightness, r, g, b, w;
    if (animation_counter<128) {
      brightness = animation_counter;
    } else {
      brightness = -animation_counter;
    }
    r =  (settings[0][4]*brightness) >>4 ; // 5b brightness * 7b counter / 4b = 12b/4b = 8b input value for pixels
    g =  (settings[0][8]*brightness) >>4 ; // controll via 4 ON buttons and power slider
    b =  (settings[0][12]*brightness) >>4 ;
    w =  (settings[0][16]*brightness) >>4 ;
    for (uint8_t i=0; i<8;i++) { strip.setPixelColor(i, strip.Color(r,g,b,w) ); } // ring LEDs
    brightness = 128-brightness;
    r =  (settings[0][6]*brightness) >>4 ; // 5b brightness * 7b counter / 4b = 12b/4b = 8b input value for pixels
    g =  (settings[0][10]*brightness) >>4 ; // controll via 4 OFF buttons and power slider
    b =  (settings[0][14]*brightness) >>4 ;
    w =  (settings[0][18]*brightness) >>4 ;
    strip.setPixelColor(8, strip.Color(r,g,b,w) ); // center/core LED(s)

    delay(settings[0][0]); // controll speed via main main ON and color wheel

  }

  if(curr_mode==1) {
    uint32_t c;
    uint8_t start1, end1, start2, end2, gap;
    strip.clear();
   
    start1 = animation_counter;
    end1 = start1 + settings[1][7]; // 1 OFF ring
    gap = 127 -(settings[1][7]>>1) -(settings[1][11]>>1); // 2 OFF ring
    if (gap>127) { // overflow
      gap=0;
    }
    start2 = end1 + gap;
    end2 = start2 + settings[1][11]; // 2 OFF ring


    start1 = start1>>5; // convert 8b to 3b for LEDs index
    end1 = end1>>5;
    if(start1>end1) { end1+=8; } // fix overflow problem
    start2 = start2>>5;
    end2 = end2>>5;
    if(start2>end2) { end2+=8; }

   
    c=Wheel(settings[1][5],settings[1][6],settings[1][4]);
    //       1 ON wheel     1 OFF slider   1 ON slider
    for (uint8_t i=start1; i<end1;i++) { strip.setPixelColor(i%8, c ); } // ring LEDs

    c=Wheel(settings[1][9],settings[1][10],settings[1][8]);
    //       2 ON wheel     2 OFF slider   2 ON slider
    for (uint8_t i=start2; i<end2;i++) { strip.setPixelColor(i%8, c ); } // ring LEDs


    c=Wheel(settings[1][13],settings[1][14],settings[1][12]);
    strip.setPixelColor(8, c ); // center/core LED(s)

    delay(settings[1][0]);
   
  }

  if(curr_mode==2) {
    uint32_t c;
    uint8_t start1, end1, start2, end2, gap, power;
    strip.clear();
   
    start1 = animation_counter;
    end1 = start1 + settings[2][7]; // 1 OFF ring
    gap = 127 -(settings[2][7]>>1) -(settings[2][11]>>1); // 2 OFF ring
    if (gap>127) { // overflow
      gap=0;
    }
    start2 = end1 + gap;
    if (gap) {
      end2 = start2 + settings[2][11]; // 2 OFF ring
    } else { //to many LEDs, no gap
      end2 = start1;
    }
   

    start1 = start1>>1; // convert 8b to 7b
    end1 = end1>>1;
    if(start1>end1) { end1+=128; } // fix overflow problem
    start2 = start2>>1;
    end2 = end2>>1;
    if(start2>end2) { end2+=128; }


    // draw [1]
    if (end1>>4 == start1>>4) { //same/single pixel
      power = end1-start1;
      c=Wheel(settings[2][5],(settings[2][6]*power)>>4,(settings[2][4]*power)>>4);
      strip.setPixelColor((start1>>4)%8, c );
    } else { // at least 2 pixels
      c=0;
      if ((end2>>4)%8 == (start1>>4)%8) { // overlap with [2] tail
        power = (end2%16); // power for last pixel
        c=Wheel(settings[2][9],(settings[2][10]*power)>>4,(settings[2][8]*power)>>4);
      }
     
      power = 16-(start1%16); // power for first pixel [1]
      c+=Wheel(settings[2][5],(settings[2][6]*power)>>4,(settings[2][4]*power)>>4); //please no overflow here
      strip.setPixelColor((start1>>4)%8, c );

      c=Wheel(settings[2][5],settings[2][6],settings[2][4]); //middle pixels [1]
      //       1 ON wheel     1 OFF slider   1 ON slider
      for (uint8_t i=(start1>>4)+1; i<end1>>4;i++) { strip.setPixelColor(i%8, c ); } // ring LEDs

      power = (end1%16); // power for last pixel [1]
      c=Wheel(settings[2][5],(settings[2][6]*power)>>4,(settings[2][4]*power)>>4);
      strip.setPixelColor((end1>>4)%8, c );
     
    }
   

    if (end2>>4 == start2>>4) { //same/single pixel
      uint8_t power = end2-start2;
      c=Wheel(settings[2][9],(settings[2][10]*power)>>4,(settings[2][8]*power)>>4);
      strip.setPixelColor((start2>>4)%8, c );
    } else { // at least 2 pixels
      c=0;
      if ((end1>>4)%8 == (start2>>4)%8) {
        power = (end1%16); // power for last pixel [1]
        c=Wheel(settings[2][5],(settings[2][6]*power)>>4,(settings[2][4]*power)>>4);
      }

      power = 16-(start2%16); // power for first pixel [2]
      c+=Wheel(settings[2][9],(settings[2][10]*power)>>4,(settings[2][8]*power)>>4); //please no overflow here
      strip.setPixelColor((start2>>4)%8, c );

      c=Wheel(settings[2][9],settings[2][10],settings[2][8]); //middle pixels [2]
      //       2 ON wheel     2 OFF slider   2 ON slider
      for (uint8_t i=(start2>>4)+1; i<end2>>4;i++) { strip.setPixelColor(i%8, c ); } // ring LEDs

      power = (end2%16); // power for last pixel [2]
      c=Wheel(settings[2][9],(settings[2][10]*power)>>4,(settings[2][8]*power)>>4);
      if ((end2>>4)%8 != (start1>>4)%8) { //skip if overlap with first pixel [1]
        strip.setPixelColor((end2>>4)%8, c );
      }
     
    }
   
    c=Wheel(settings[2][13],settings[2][14],settings[2][12]);
    strip.setPixelColor(8, c ); // center/core LED(s)

    delay(settings[2][0]);
   
  }




  strip.show();
  animation_counter++;
  //delay(10);
}

Last is the animation function. Depend on selected mode, it redraw all LEDs (write to buffer in RAM), send the data do LEDs and wait a bit via simple delay instruction. Delay have to be improved to non-blocking one, but don't have a time to fix that.


uint32_t Wheel(uint8_t WheelPos, uint8_t white, uint8_t brightness) { // surprisingly, Adafruit color wheel match with MiLight standard
  WheelPos = 255 - WheelPos; // this function aslo blends in white component
  if(WheelPos < 85) {
    return strip.Color(((255-WheelPos*3)*brightness)>>5, 0, ((WheelPos * 3)*brightness)>>5, (white)<<3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color( 0, ((WheelPos * 3)*brightness)>>5, ((255 - WheelPos * 3)*brightness)>>5, (white)<<3);
  }
  WheelPos -= 170;
  return strip.Color(((WheelPos * 3)*brightness)>>5, ((255 - WheelPos * 3)*brightness)>>5, 0, (white)<<3);
}

And last one, this converts color format from something like HSL/HSV to RGB+W. Just because like to do it in single function. Adafruit instructions provides separate functions for RGB and white.

And that's all for the moment.
I plan to improve it, but You know, there are a lot more important stuff around. So feel free to the code and share Your results.

Eywa ngahu  :ikran:
Any errors in spelling, tact or fact are transmission errors.

Hi! I'm a signature virus. Copy me into your signature to help me spread.

Toliman

Great :) :toruk:
Nice to see that you successfully finished this project! Great work!