Project in progress
Sputnik Spotlight

Sputnik Spotlight

A remotely voice-controlled spotlight lamp that can change position (x, y and z axes), rotation (360°), light focus amplitude and intensity

  • 1,411 views
  • 2 comments
  • 7 respects

Components and supplies

Esp01
Espressif ESP8266 ESP-01
×1
A000066 iso both
Arduino UNO & Genuino UNO
Arduino ISP programmer
×1
Atmega328P
IC of Arduino Board, as standalone 8mhz internal clock; with or without bootloader
×1
Attiny85
Microchip ATtiny85
×4
Nema%2017 2
OpenBuilds NEMA 17 Stepper Motor
NEMA 17-42 Hybrid Stepper Motor 5mm round shaft, 2 phase 4 line
×3
Mini Stepper Motor
×2
Micro mini-slider stepper
×1
Texas instruments l293dne image
Texas Instruments Dual H-Bridge motor drivers L293D
(up to 600mA)
×2
L293E
(up to 1A current) more expensive and maybe over-required for not heavy designs. Need larger socket (20 pin)
×1
475267 240424 01 front zoom kankcmvqdh
Raspberry Pi Zero Wireless
Set as AlexaPi device
×1

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

Ide web
Arduino IDE
Avs med 3 22
Amazon Alexa Alexa Voice Service
Dp image kit 02
Amazon Alexa Alexa Skills Kit
Dyno configurations
Heroku Dynos
Multilanguage code platform, web interface with Alexa Skill
Freecad
Powerful and free 3D modeling program

About this project

Thinking about home enlightment system, I often come to realize how many potential light spots I'd like in a single room:

  • diffuse soft light, maybe reflected onto the roof or a wall, to watch a movie
  • couchside lamp for reading
  • direct focused light on a workstation desktop

and so on!

Across the day and when needed I actually use different lamps for different atmospheres. So I had the idea to make a single device capable of moving and rotating in a range of positions, capable of focus narrow and wide light circle: and dimmerable!

Then, it should become even smarter being controlled by voice commands with Alexa. There can be a lot of possible practical applications.

..Imagine a dress shop and a customer that can appreciate all the shades, colors and reflexes on his new suit or on her new shining night dress, by a moving lamp that perform a revolution around customer's position; at the clerk's voice command.

Even trivial tasks, like having a light not only when you open the fridge at night but also when open any sideboard's door.

As a spotlight, light up a person making a terrible joke - like he was on a stage; would be at least comical.

Or a discrete lamp to search for keys or slippers when coming home late, trying not to wake up everyone by turning on full lights.

Or rotating fast 180° like a disco globe or an alarm.

Then explore potentially very complex tasks ...Imagine asking it to point the current position of the Moon (connected to the web for time and date, should be possible); or point the position of a fixed object ("where we keep sugar?", "where was that book?"); even a moving object ("where is my cellphone?!") - but it should be able to find it before me in the first place.

Back to earth and feet on the ground,

first goal of Sputnik project is to build a functioning prototype.

Next, I imagine a potentially professional smart-home device that everyone would have in their living room, office, shop or laboratory.

VIDEO LOG

Here a video with the actual state of the project.

STRUCTURAL CONCEPT

Moving in xy space can be performed via a plotter structure - but for an entire room seems to be a too big structure.

I thought about cables fixed on top of the walls: a roller can wind the cable as a string. For stability cables should be 3; these have to be controlled by 3 different motors. For weight reduction I prefer to place them in wall devices instead of inside the central lamp device.

Central lamp's first concept was a sphere: hence the name Sputnik, for resembling the aspect of the satellite with its antennas.

The sphere concept is cool but complex; it indeed fits requirement of 360° moving on each direction without any blind spot. Problem is that wires' joints must remain fixed. A possible solution for this concept could be a fixed transparent material like a plastic or glass orb.

For prototyping I decided to explore a simpler design of 2 circular rings with an inner part which I tried to maintain as spherical as possible.

..hence the prototype resembles more a planet with two orbital rings and I'm calling it "Saturn prototype". (Ok, it also looks like a little Dark Star.)

In time, my intention is to build quite a heavy lamp - but this multiplies the challenges, not to make it dangerous (e.g. mains AC powering in cables, steel wires strings to sustain it). These issues could be dealt with, but later in a more refined device. So I'll keep this lamp prototype as light (as in: not heavy!) as possible.

Powering and connecting these 4 devices (the central one and three wall-fixed) could result in a mess of wild wires. I decided to connect them through the sustaining wires: an elegant solution but quite a maker challenge for my skills.

This is a scheme explaining structure and 12v power line (and of course Gnd line, not shown, follows the same route). The plug is connected through one of the wall devices, runs through one of the sustaining cables and reach the lamp. Then follows its structure as shown; to reach the inner part through 4-pole pair of male-female jack plugs.

The scheme underlines mounting points (gray circles) and movement/rotation directions.

The wire chosen is flat (better torsion control, I hope), with four isolated lines of quite a small diameter (0.12 mmq) to reduce overall weight (60-80g per wire at full lenght). This could issue a problem with power current and heating. But it should improve data signals because of low capacitance.

Planned need of a minimum of 4 channels:

  • power Vcc: 12v
  • Gnd
  • Signal (SCL or Rx)
  • Signal (SDA or Tx)

Immediaty I took note of the risk of short-circuit of 12v on a max-5.5v AVR-pin line.

Rotational continuos movement put this issue: how to connect four rotating lines?

I decided o try a dirty solution: a 4-pole 3.5'' jack plug permanently connected that would rotate to maintain contacts. Immediately the risk of short circuit between 12v and an AVR has risen..

Device structural design of the prototype is:

  • The external ring has mainly structural stability purposes; it also connects the 4 channels inside the sustaining wires.
  • The intermediate ring must have room for a little stepper for x-axis and its driver circuit.
  • The internal little element is where the bulb lamp accomodates, with a little stepper for y-axis and the central IC parts.
  • 3 wall-fixed devices contain a big stepper motor, a reel - bobbin cylinder, a driver circuit with possibly a movement sensor.

When the lamp is turned off, another issue is probably the stepper rest situation: if it doesn't have enough inertial resistance it will let fall the lamp.

A solution for this issue could be adding a solenoid stopper (like this or this ) or something more DIY like a dented gear normally closed and that can be lifted by an electromagnet.

BUILDING NOTES

Here are some photos of the making process.

Wall devices are very raw prototype; there are many ways to make them better, in the structure and in a possibly nice enclosure: I thought of a half-cylinders concept. They also seem too big and heavy to me. In a professional product they could be enclosed inside a box in the wall and be invisible. There's lots of room for improvement!

The 3D-printed lamp structure needed some minor adjustments: holes too small, some part required to be cut, some "filet" parts detached and had to be glued together, some simply broke. The "Sfera" part gives the lamp a better aspect but 1) it is restricting space for circuit and wires and 2) it does not fulfill right the light cone-cylinder.

Surely there's work to do on the .fcstd (Freecad) files; I will not post them because they're huge (15-60 Mb) but I'll be glad to send them if someone asks me. Below are the .stl files to be printed as they are.

ELECTRONIC DESIGN

I intend to power this with 12v tension for the big actuators, 3 NEMA-17 type stepper motors and for the light bulb. Microcontrollers can be at 5v as much as the 2 mini steppers and the micro-slider. So voltage regulators are needed (7805) and some circuitry.

Two classics default tension values, but always hail to Keep It Simple, Stupid! philosophy.

L293 Dual-H-Bridge Drivers are needed; I choose (probably over-needed) L293E for the bigger steppers as they can manage up to 1A; for the smaller L293D. They are controlled by Attiny85 in a 2-wire configuration - this requires some circuitry but it frees 2 pins. See Arduino scheme here.

Watch out L293E has 4 more pins (hence the 20-pin package) that in the datasheet weren't clear to me; reading this forum thread explains that the chip offers possibility of measuring current consumptions (as far as I understand!). Doesn't need these, must be connected to Gnd.

PROTECTION CIRCUIT

A 6,3A fuse is placed on the 1st wall device, between the 12v power plug and everything else.

A pair of zener diodes (with reverse voltage in range 6v-11v) is placed near every connection joint (3 pairs on wall devices, 2 in the lamp rings) with cathode to data lines and anode to Gnd, to prevent short-circuit between 12v line.

[One of the] Worst Case Scenario can be this: I'm soldering at evening with the lamp over my head. One of the strings breaks, the lamp falls with a pendulum and crashes against a vase breaking it. The lamp structure (3D printed..) also breaks. Darkness falls.

Then some jack moves and it gets short-circuited with an I2c pin line. ALL the ICs start to smoke and catch fire. A sinister red light returns as I watch the remaining pendulum hitting my head, and setting fires in several places around the room!

At least this minimum protection circuit should prevent the latter events.

PROGRAMMING MICROCONTROLLERS - NOTES ON FUSE SETTING

First, fuse settings of the microcontrollers used.

I use an Arduino Board with ArduinoAsISP sketch (available by default in File > Examples > 11.ArduinoISP > ArduinoISP ) and a custom programming board or by breadboard and wires (see here or here). Connections for Attiny here.

So, after normally upload the ArduinoISP sketch to an Arduino Board, you will have to choose the right microcontroller (Tools > Board > ... ) and Upload Using Programmer ( Sketch > Upload Using Programmer).

ISP (In-System Programming) is a powerful tool for makers, letting you to permanently install chips on the building device and reprogramming them with only a 6 pins header.

I made my programmer board: it's usable by Arduino IDE and by avrdude. Avrdude ( here a tutorial ) is a very powerful machine-level program for AVR microcontrollers; it can do everything IDE is afraid to do.

There's a way to make and upload directly programs ( .hex not .ino ) of course. Arduino IDE can save directly .hex file, ready to be uploaded. When building programs outside this easier editor you need some more hard-coding C skills and also IC driver files (makefile); check out these tutorials if interested.

An online fuses calculator may be useful.

Atmega328P (core of Arduino Board) will be used without external crystal oscillator and (optional) with brown-out detector (BOD) disabled.

Fuses: Low E2 High DF Ext 07

Setting fuses in a linux terminal with avrdude:

[Atmega328P]

sudo avrdude -P /dev/ttyACM0 -b 19200 -c avrisp -p m328p -U lfuse:w:0xE2:m -U hfuse:w:0xDF:m -U efuse:w:0x06:m

Maybe you need to change these options:

  • /dev/ttyACM0 = USB port of Arduino Board (check in IDE: Tools > Port )
  • -b 19200 = baud rate
  • -c avrisp = Arduino Board used as ISP
  • -p 328p = Atmega328P is the target

Attiny85 will be used with internal 8mhz clock.

Fuses:

Low 62 High DF Ext FF

sudo avrdude -P /dev/ttyACM0 -b 19200 -c avrisp -p t85 -U lfuse:w:0x62:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m 
  • -p t85 = Attiny85 is the target

or, after loaded the sketch, when ready for closing the project and disable Reset pin:

sudo avrdude -P /dev/ttyACM0 -b 19200 -c avrisp -p t85 -U lfuse:w:0x62:m -U hfuse:w:0x5f:m -U efuse:w:0xff:m 

When I'll put the stopper, I will need an extra pin an so with the reset disabled

Fuses: Low 62 High 5F Ext FF

This needs to be done with avrdude because Arduino IDE isn't brave enough. Avrdude is much more machine-level!

WARNING: after this setting, it will NOT be easily reprogrammed; you will need use a High Voltage Serial Programmer to reset defaults. I managed to build a circuit with ad Arduino for bricked Atmega (blocked at 128Khz clock!) following this tutorial. If happens with Attiny85, I think I'll give a try with this tutorial.

ESP8266-01 has to be programmed without an external programmer but with an USB adapter and in a special way (grounding pin 0 to enter in programming mode) like in this tutorial, this or this.

As a dirty and quick solution, I built a custom derivative board for a common breadboard and used pins rx and tx of an Arduino Board after removing the Atmega328P micro (I think in this way I used only Atmega16u2 usb driver present on the board). In my experience it's even a dirtier method if you think that in this way I don't use any protection to switch 5v of Arduino to 3.3v level suggested and needed by ESP device. I leave in other places ( cfr here and here ) and to other minds the debate if ESP8266 can be 5v tolerant for long time; surely, it has been for me in the programming time.

All these ICs can be programmed in Arduino IDE environment by downloading support libraries in the Boards Manager (Tools > Board > Boards Manager).

Board libraries used in this project:

  • esp8266 by ESP8266 Community version 2.3.0

Don't forget to select the right microcontroller chip (e.g. Atmega328P and not Atmega328) and clock.

There you can upload bootloader ( Tools > Burn Bootloader ).

CODE DESIGN

I'm expecting the code will be simple and complex at the same time. Actuator tasks should be quite easy: move steppers - turn on/off a light - maybe dimmer it. But this job is done by 5 different devices plus a wi-fi device! And, I expect some math to calculate an horizontal movement with 3 strings attached in a pyramid-shaped point.

ESP8266-01 is connected to Atmega328P through UART (universal asynchronous receiver-transmitter) using Rx Tx pins. More important it's connected with my home router and acts as a web server; from my laptop I can open a web page at the static address 192.168.1.116 with simple interface to move the lamp.

A direct control by a linux terminal can be achieved by similar commands:

curl -s 'http://192.168.1.116/saved_position_1' >> /dev/null

Or longer (not much more complex, only longer) ones:

curl -s --max-time 30 --retry 20 --retry-delay 10 --retry-max-time 60 'http://192.168.1.116/change?length1=300&length2=300&length3=0&rotationX=90&rotationY=10&focus=10&dimmer=50' >> /dev/null || echo "Error Connection!"

Other "pages" (server requests) can be done via software - from my Raspberry Zero set up with Alexa.

For Attinys, I'm expecting to give orders but also to read sensors; not only, to give different orders each and to expect they move (almost) simultaneously for the lamp to move gracefully.

Why should I need sensors? I would like the device to know in which position currently is, because I don't like always-on devices and I know that last (and start) position can't be saved each time I turn off due to EEPROM limitations in write mode. So, sensors. Again I tried some dirty solutions..

  • A variable resistor to measure rotation of the bobbin and so the lenght of cable.
  • A reed sensor to detect matching rings position by rotating the lamp when starts (or, better, when it's asked to change position).

These are inexpensive solutions, require few pins (at the end of the day, actually a pin for each of the 5 microcontrollers), and I think I can manage them (see: K.I.S.S.).

Otherwise, you could use rotative sensor, accelerometers, gyro sensors, maybe a magnetic compass (this would be cool!); but for this time I said to myself: more pins, less fun.

A simple way to connect them is through I2C (Inter-Integrated Circuits), a serial bus communication using 2 wires. A well explained series of tutorials is here.

Atmega would be the "master" device and 4 attiny would be "slaves". Although we can only perform one I2C communication at a time, I found out that it's fast enough to move them (almost) at the same time.

To use I2C, libraries are needed; for ATmega328 it's a default library (Wire). For ATtiny85 you need to download a dedicated library (TinyWireS).

In first not-working tests I was very frustrated with a possible total design error: I2c is thought to be reliably working with devices in a range of meters, depending on the cable ( cfr capacitance limits at pages 36 and 57 in the protocol manual). I intended to connect 5 devices in a star configuration with three 4.5m long cables! And it seem too late to rethink it all. A big issue: I2c communication is intended for near devices. Adapting it for 4-5m distance can be tricky and unstable due to signal noise. Don't forget that in the same cable there are 12v power tension and ground, aside 2 signal wires. In fact, if the network of devices worked fine on a breadboard, when tested on the long wires didn't work reliably.

After some other tests although it partially worked, I think because of:

  • more 4.7Kohm pull-up resistors on every line end
  • soldered contacts and not flying jumpers on breadboard
  • code hacks like repeating commands and check routines

However, these can result in slower response and put everything in risk of communication instability. Other solutions can obviously be found but for now I stick with KISS (if it works). In an initial quest for better solutions (cfr here):

- I2c at a slower baud rate (less than default 100Khz) would be better; but for my skills it requires a lot of code knowledge and work because it isn't supported and I couldn't find working solutions on the web (it's more about faster communications, like already designed 400Khz option, than slower ones). Reason for using another library rather than the original can be found here.

- an existing protocol like Manchester Encoding, thought to work on a self-clocking single wire.

- a secure but more expensive solution would be adding hardware ICs like RS485 driver or similar, or P82B96 to improve voltage level.

My (dirty) solution has been to use Rx-Tx UART configuration at a low baud rate, that maybe could be more reliable; but it isn't intended with multiple devices and therefore needs a complex configuration of commands and priorities (an entire protocol), like sending text "SET 120 040 xxx 020", and a disciplinated series of answers: "OK", "OK OK", "OK OK NO", "OK OK NO OK". This indeed would permit a "to all" command possibility like "GO". In this case for Attiny you need to use SoftwareSerial library; and also for ATmega328P because default UART bus is needed for communication with ESP8266-01. So you need to think of a double serial (cfr here ).

That's the option I'm working on: UART, 2400 baud rate, a set of communication rules. Almost the same hardware could support different communication protocols, so I could change it.

Here, a scheme of the communication concept. Essentially ATmega328P is the center (the "master") of a star-structure scheme for a UART SoftwareSerial setup. It also receives request from ESP8266-01 by another UART line (primary hardware default) and from remote controller.

Attinies occasionally use their Tx line when requested readings: other Attiny simply ignore this and ATmega328P listens.

ALEXA & HEROKU SETUP

First, I had my Alexa device in a Raspberry Pi Zero, following this tutorial. But to ensure starting at boot, and possibly avoid the graphic OS (using Stretch-Lite), follow AlexaPi instructions.

It also has as default configuration of a push button (GPIO 18) for waking up Alexa and of a dual colour LED (or 2 single LEDs) on GPIO 24 and 25. Of course these are configurable in file

/etc/opt/AlexaPi/config.yaml

Took me some time; in particular be careful with Raspberry settings, it has to be already update and upgrade, libraries:

sudo apt-get install python-pip oracle-java8-jdk --yes

And set up an external USB speaker as default (not HDMI audio output, not 3.5 jack) -- after lots of terminal time I came up with a simple click on the GUI sound settings, choosing the Audio Adapter as "default".

AlexaPi install mainly it's very easy, few lines in terminal:

cd /opt
sudo git clone https://github.com/alexa-pi/AlexaPi.git
sudo ./AlexaPi/src/scripts/setup.sh

After 45-60 minutes of install scripts,

sudo reboot

And it's done.

Now, there are a LOT of methods to control a custom device with Alexa, involving different services - some of them are not free of charge. After some research I came across Heroku multi-language platform following this project. Official tutorial for setting up a web app in Python can be helpful.

Heroku works with a git repository method to upload app. Open an account and download the terminal program. Then, every time you need to change code, you follow this passages:

cd projectDirectory
heroku login
nano Procfile
nano requirements.txt
git add .
git commit -am "and beyond"
git push heroku master

Then you activate a Dyno as a web app or a worker:

heroku ps:scale worker=1

or

heroku ps:scale web=1

VOICE USER INTERFACE (VUI) DIAGRAM

Here the voice user interface flow diagram I'm using in my Alexa Skill Sputnik-Control, in pair with the python app on Heroku.

ACTUAL STATE OF THE PROJECT

2-23-2018 At the moment, as you can see in the video log, I'm at the work-in-progress phase. And I'm waiting for two big stepper delivery (aaargh!).

To do next:

  • mounting and make move freely the devices
  • soldering circuitry + extras (e.g. stopper solenoids, add a sensor for slider's end-of-run)
  • test structure and code on the mounted wall prototype
  • rectifying constant variables (e.g. steppers speed and steps-per-revolution, cable length maximum and minimum)
  • testing again the code communication protocol (find a low-speed I2c protocol!)
  • improving code: introducing some math to permit xyz movements (e.g. "Alexa, ask Sputnik to move 10cm on the left")

Code

ATmega328PArduino
/* 
  23-2-2018
  Sputnik Spotlight

  ATmega328P @ 8mhz internal clock

  Webpage from ESP8266-01
  Controlling 4 Attinys on long wire

Get values from ESP8266-01 via Serial:

1) 90     1st big stepper position, cm
2) 80     2nd big stepper position, cm
3) 180    3rd big stepper position, cm
4) 0      4th Attiny, x position (0-359), degrees
5) 0      Atmega,  y position (0-359), degrees
6) 7      focus microstepper (0-100)
7) 0      dimmer (0-255)  , PWM pin to lamp ground

Stored in array:
data[device]
lastData[device]

Send commands to Attinys via UART code:
1:90-2:80-3:180-4:255

to do:
- check minimum and maximum
- ask reading position

*/

//---------------------------------
// Libraries
//---------------------------------

#include <SoftwareSerial.h>  //To give tasks to Attinys
#include <Stepper.h>         //For 2 steppers control (Y-rotation and focus-slider)
#include <stdio.h>           //for Serial string splitting
#include <string.h>

// Define and start sputnikSerial communication with distant Attinies
#define rxPin 2
#define txPin 3
SoftwareSerial sputnikSerial(rxPin, txPin);
#define INPUT_SIZE 10  //reserve Serial memory


// Ministepper, 5v, is be controlled by L293D driver
#define MINI_STEPSREVOLUTION    200
#define MINI_SPEED               50     //(RPM)
#define Ystepper_COILS_1  17   //17 = A3
#define Ystepper_COILS_2   2
#define Ystepper_COILS_3   3
#define Ystepper_COILS_4   4
int stepAngle  = 1;     // Adjust this: single steps
float minAngle = 360 / MINI_STEPSREVOLUTION;

Stepper YStepper(MINI_STEPSREVOLUTION, Ystepper_COILS_1, Ystepper_COILS_2, Ystepper_COILS_3, Ystepper_COILS_4);

// Microstepper slider could be controlled directly by pins because of low operation current (15-25mA per coil)
#define SLIDER_STEPSREVOLUTION  120   //test this
#define SLIDER_SPEED             20
#define SLIDER_COIL_1       8
#define SLIDER_COIL_2       7
#define SLIDER_COIL_3       6
#define SLIDER_COIL_4       5

int stepFocus = 10;    // Adjust this
Stepper focusStepper(SLIDER_STEPSREVOLUTION, SLIDER_COIL_1, SLIDER_COIL_2, SLIDER_COIL_3, SLIDER_COIL_4);


//---------------------------------
// Settings
//---------------------------------
int rythm = 300;   //cycle frequency

const int reedSensorPin = A2;
const int sliderEndPin = 10;    //simple end-of-run switch
const int dimmerPin = 9;        //PWM
const int maxNumberDevices = 7;

//                             Wire1 Wire2 Wire1 Rotation XY  Focus  Dimmer
//                               cm    cm    cm     degrees, width, luminosity
volatile int data[] =      { 0, 300,  300,  300,    0,   90,   10,    40 };
volatile int lastData[] =  { 0, 300,  300,  300,    0,   90,   10,    40 };
volatile int minLength[] = { 0,  40,   40,   40,    0,    0,    0,    0  };
volatile int maxLength[] = { 0, 450,  450,  450,  359,  359,  100,  255  };

//---------------------------------
// Counters and routine variables
//---------------------------------
String message;
int i, device;

volatile byte msg;

bool newDataHasArrived;
bool jobsToBeDone[] = { false, false, false, false, false, false, false };
bool angleIsKnown;
bool focusIsKnown;
int unknownAngle = 0;
int unknownFocus = 0;
int slowDimmer = 1;     // To change light intensity slowly.
                        /* If > 1, insert a tolerance code because otherwise
                           it will never stop adjusting!
                         */
bool reedSensorReading;
bool sliderEndReading;

volatile float nowMillis, thenMillis;

void setup() {
  Serial.begin(9600);           //Communication with ESP8266-01
  sputnikSerial.begin(2400);    //Communication with Attiny85s

  pinMode(reedSensorPin, INPUT);
  pinMode(sliderEndPin, OUTPUT);
  pinMode(dimmerPin, OUTPUT);

  YStepper.setSpeed(MINI_SPEED);
  focusStepper.setSpeed(SLIDER_SPEED);
}

void loop()
{
  nowMillis=millis();
  
  //every 300ms
  if (nowMillis - thenMillis > rythm)
  {
    //Update memory when new request arrives, and send
    //1:100-2:180-3:300-4:360-
    while (newDataHasArrived)
    {
      // Prepare and send message for Attinies
      message = "";
      for (i = 1; i<=4; i++)
      {
        lastData[i] = data[i];
        message = message + String(i)+":"+String(data[i])+"-";
      }
      sendMessage(message);

      // Respond to ATmega328P's tasks: Y rotation, focus, dimmer
      jobsToBeDone[5] = true;
      jobsToBeDone[6] = true;
      jobsToBeDone[7] = true;
      // Data arrived is now done
      newDataHasArrived = false;
    }    
    
    thenMillis = millis();    
  } // end of every 300ms loop

  for (i = 5; i<=7; i++)
  {
    if (jobsToBeDone[i])
    {
      if (lastData[i] == data[i])   // Check every job
      {
        jobsToBeDone[i] = false;
      }
      else                          // Change!
      {
        switch (i)
        {
          case 5: // RotateY
            if (angleIsKnown == false)
            {
              findYzero();
            }
            if (data[i] < lastData[i])   // Rotate clockwise
            {
              YStepper.step(stepAngle);
              lastData[i] = lastData[i] - minAngle;
            }
            else                         // Rotate counterclockwise
            {
              YStepper.step(-stepAngle);
              lastData[i] = lastData[i] + minAngle;
            }
            delay(10);
            break;
          case 6: // Focus amplitude
            if (focusIsKnown == false)
            {
              findFocusZero();
            }
            if (data[i] < lastData[i])   // Move +
            {
              focusStepper.step(stepFocus);
              lastData[i] = lastData[i] - stepFocus;
            }
            else                         // Move -
            {
              focusStepper.step(-stepFocus);
              lastData[i] = lastData[i] + stepFocus;
            }
            delay(10);
            break;
          case 7: // Dimmer
            if (data[i] < lastData[i])   // More
            {
              data[i] = data[i] + slowDimmer;
            }
            else                         // Less
            {
              data[i] = data[i] - slowDimmer;
            }
            analogWrite(dimmerPin, data[i]);
            delay(10);
            break;
         }
       }
    }
  }
}

/*
  SerialEvent default function is checking every loop() cycle
  if new data comes in Rx
 */
void serialEvent()
{
  while (Serial.available())
  {
    // command ex. 1:190-2:180-3:180-4:250-
    newDataHasArrived = true;
    // from https://arduino.stackexchange.com/questions/1013/how-do-i-split-an-incoming-string
    char input[INPUT_SIZE + 1];
    byte size = Serial.readBytes(input, INPUT_SIZE);
    input[size] = 0;
    char* command = strtok(input, "-");
  
    while (command != 0)
    {
      char* separator = strchr(command, ':');
      if (separator != 0)
      {
        *separator = 0;
        device = atoi(command);
        ++separator;
        data[device] = atoi(separator);
      }
      command = strtok(0, "-");
    }
  }
}

// Function to send message to Attinies
void sendMessage(String msg)
{
  sputnikSerial.println(msg);
}

// Rotate clockwise until reed sensor reveals zero position
void findYzero()
{
  while (angleIsKnown == false)
  {
    reedSensorReading = digitalRead(reedSensorPin);
    if (reedSensorReading == true)
    {
      angleIsKnown = true;
      lastData[5] = unknownAngle;
    }
    else
    {
      unknownAngle++;
      YStepper.step(stepAngle);
      delay(10);
    }
  }
}

void findFocusZero()
{
  while (focusIsKnown == false)
  {
    sliderEndReading = digitalRead(sliderEndPin);
    if (sliderEndReading == true)
    {
      focusIsKnown = true;
      lastData[6] = unknownFocus;
    }
    else
    {
      unknownFocus++;
      focusStepper.step(stepFocus);
      delay(10);
    }
  }
}
ESP8266-01Arduino
/* 23-2-2018

 Sputnik Spotlight - ESP8266-01

Interactions:

1) Alexa device by voice commands:
  "Alexa, ask Sputnik to turn on full light"
  "Alexa set Sputnik to light diffuse"
  "Alexa, ask Sputnik number two"
  "Alexa, ask Sputnik to set lights  on the table"


2) Web page with simple inputs to control Lamp on 192.168.1.116
 Sends commands to ATmega328P via UART in format:
1:80-2:40-3:180-4:200-

- length1     1st big stepper position, cm
- length2     2nd big stepper position, cm
- length3     3rd big stepper position, cm
- rotationX   4th Attiny, x position (0-359), degrees
- rotationY   Atmega,  y position (0-359), degrees
- focus       focus microstepper (0-100)
- dimmer      dimmer (0-255)  , PWM pin to lamp ground


3) Terminal bash command to change position:

curl -s 'http://192.168.1.116/saved_position_1 >> /dev/null
curl -s --max-time 30 --retry 20 --retry-delay 10 --retry-max-time 60 'http://192.168.1.116/change?length1=300&length2=300&length3=0&rotationX=90&rotationY=10&focus=10&dimmer=100' >> /dev/null || echo "Error Connection!"



TO DO:
read_sensors() function
EEPROM saved settings and saved position,
save actual position function


*/

//---------------------------------
// Libraries
//---------------------------------

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h> 
#include <ESP8266WiFiMulti.h>
#include <WebSocketsClient.h>
#include <Hash.h>

//---------------------------------
// Settings
//---------------------------------

int delayRepeat = 100;    // Time (ms) between repeated commands
int refreshRate = 1000;   // Loop speed: how fast server client update

const int numberOfDevices = 7;
//                             Wire1 Wire2 Wire1 Rotation XY  Focus  Dimmer
//                               cm    cm    cm     degrees, width, luminosity
volatile int data[] =      { 0, 300,  300,  300,    0,   90,   10,    40 };
volatile int minLength[] = { 0,  40,   40,   40,    0,    0,    0,    0 };
volatile int maxLength[] = { 0, 450,  450,  450,  359,  359,  100,  255 };


//---------------------------------
// Server settings
//---------------------------------

// INSERT YOUR OWN ROUTER SSID and PASSWORD HERE!

const char* ssid = "";
const char* password = "";

// Usual settings, change for specific router (e.g. 192.168.0.x)
IPAddress ip(192,168,1,116);    // Request of static IP: 192.168.1.116
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80);    // Setup of a webserver, port 80 listening

//-------------------------------------------------
// Heroku settings
char host[] = "sputnik-controller.herokuapp.com";
int port = 80;
char path[] = "/ws"; 
ESP8266WiFiMulti WiFiMulti;
WebSocketsClient webSocket;

//---------------------------------
// Counters and routine variables
//---------------------------------

int device, value;
int lastData[numberOfDevices];
bool WiFiIsGood, WiFiWasGood;
String webhtml, answer, endAnswer, message;
DynamicJsonBuffer jsonBuffer; 
String currState;
int pingCount = 0;
String triggerName ="";
String triggerVal ="";
int triggerEnabled = 0;

unsigned long nowMillis, thenMillis;



//---------------------------------
// Setup
//---------------------------------
void setup()
{
  Serial.begin(9600);           // This establish UART communication with ATmega328P

  WiFi.begin(ssid, password);   // This establish WiFi communication with router
  WiFi.config(ip, gateway, subnet);

  WiFiMulti.addAP(ssid, password);
  delay(700);
  
  webSocket.begin(host, port, path);  // This establish Wifi communication with Heroku
  webSocket.onEvent(webSocketEvent);


// These are the functions that will be called
// as requests to this webserver

// Open a browser and look for: 192.168.1.116
  
  server.on("/", webPage);
  server.on("/change", manual_change);
  server.on("/read", read_sensors);
  server.on("/save", save_position);
  server.on("/stop", manual_stop);
  server.on("/saved_position_1", saved_position_1);
  server.on("/saved_position_2", saved_position_2);
  server.on("/saved_position_3", saved_position_3);
  server.on("/saved_position_4", saved_position_4);

  server.begin();

}

//---------------------------------
// Loop
//---------------------------------
void loop(void)
{
  checkWiFi();                 // Checks connection and, if changed, tells ATmega
  server.handleClient();       // Manage the webserver
  delay(refreshRate);          // Refresh rate (every second)
}

//---------------------------------
// Web page
//---------------------------------
void webPage()
{

  /*
   This is a simple web page in html code! Code needs it as
   a String variable I'm calling "webhtml", and sends it by command
      server.send(200, "text/html", webhtml);
   Carriage return \n\ is used for easy reading; blank lines won't work.
   &nbsp; is an extra space, because html won't read multiple spaces.
   Arduino IDE won't help in formatting or highlighting commands!
   For better html pages use an external editor and embed it here.
   Note how to insert a variable:   " + String(variable)+ "
   CSS style is embedded.
   Other "pages" are called to perform actions like change position.
  */
  
  webhtml = "                                          \n\
             <!DOCTYPE html>                           \n\
            <html>                                     \n\
              <head>                                   \n\
                <title>Sputnik Lamp Control</title>    \n\
              </head>                                  \n\
                                                       \n\
              <style>                                  \n\
                body {  margin:0; padding:0; border:0; width:100%; font-family: 'Verdana', Verdana, serif; font-size: 0.75em; color: #111111; }    \n\
                h1   {  display: block; text-align: center;  margin-top: 0.3em; margin-bottom: 0.3em; margin-left: 0; margin-right: 0;  padding-left: 50pt; padding-right: 50pt;  font-family: 'Verdana', Verdana, serif; font-size: 1.25em; color: #1111CC; }    \n\
                p    {  display: block; width: 65%;  margin-top: 0.3em; margin-bottom: 0.3em; margin-right: 0; margin-left: 30%;  padding-left: 10pt; padding-right: 10pt;  }    \n\
                input[type=text]   {  text-align: right; font-size: 1em; color: #555; margin-left: 10pt;  }    \n\
                input[type=submit] {  position: relative; left: 10%; text-align: right; margin-left: 5%;  font-size: 1em; color: #777;  }      \n\
              </style>                                 \n\
                                                       \n\              
              <body>                                   \n\
                <h1>  Sputnik Lamp web-control  </h1>  \n\
                <p> Data transmitted from router to devices by UART \n\
                <hr>                                   \n\
                <form action='/change'>                \n\
                  <p>Length 1 &nbsp;&nbsp;= <input type='text' size='1' name='length1' value='"+String(data[1])+"'> (cm)    \n\
                  <p>Length 2 &nbsp;&nbsp;= <input type='text' size='1' name='length2' value='"+String(data[2])+"'> (cm)   \n\
                  <p>Length 3 &nbsp;&nbsp;= <input type='text' size='1' name='length3' value='"+String(data[3])+"'> (cm)    \n\
                  <p>Rotation X = <input type='text' size='1' name='rotationX' value='"+String(data[4])+"'> (degrees)    \n\
                  <p>Rotation Y = <input type='text' size='1' name='rotationY' value='"+String(data[5])+"'> (degrees)    \n\
                  <p>Focus &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <input type='text' size='1' name='focus' value='"+String(data[6])+"'> (0-100%)    \n\
                  <p>Dimmer &nbsp;&nbsp;&nbsp;= <input type='text' size='1' name='dimmer' value='"+String(data[7])+"'> (0-255)    \n\
                  <input type='submit' formaction='/change' value='Change'>     \n\
                </form>                                 \n\
                <hr>                                    \n\
                <form>                                  \n\
                Commands (work in progress):            \n\
                  <input type='submit' formaction='/read' value='Read'>         \n\
                  <input type='submit' formaction='/save' value='Save'>         \n\
                  <input type='submit' formaction='/stop' value='STOP'>         \n\
                </form>                                \n\
              <hr>                                     \n\
              <form>                                   \n\
                  Saved positions:                     \n\
                  <input type='submit' formaction='/saved_position_1' value='Full'>    \n\
                  <input type='submit' formaction='/saved_position_2' value='Diffuse'>    \n\
                  <input type='submit' formaction='/saved_position_3' value='Angle'>    \n\
                  <input type='submit' formaction='/saved_position_4' value='On the table'>    \n\
              </form>                                  \n\
              <hr>                                     \n\
              </body>                                  \n\
            </html>                                    \n\
    ";
    
    //Note this "; that ends webhtml String. Every " inside it is an inserted variable.

  server.send(200, "text/html", webhtml);  //Send html code!
}

//---------------------------------
// Request: manual change
//---------------------------------
void manual_change()
{
  // Transport data from server (html) to array (C)
  data[1] = server.arg("length1").toInt();
  data[2] = server.arg("length2").toInt();
  data[3] = server.arg("length3").toInt();
  data[4] = server.arg("rotationX").toInt();
  data[5] = server.arg("rotationY").toInt();
  data[6] = server.arg("focus").toInt();
  data[7] = server.arg("dimmer").toInt();
  
  checkSendData(); // Send commands to ATmega328P
  webPage();  // Go back into web front page
}

//---------------------------------
// Request: emergency stop...
//---------------------------------

void manual_stop()
{
  message = "*:STOP";
  Serial.println(message);
  delay(delayRepeat);
  Serial.println(message);
  
  // Should now ask for actual position, by sensors reading

  webPage();
}

//---------------------------------
// Request: read sensors
//---------------------------------
void read_sensors()
{
  message = "*:READ";
  Serial.println(message);
  delay(delayRepeat);
  Serial.println(message);
  
  //Ask for position
  webPage();
}

//---------------------------------
// Request: save position
//---------------------------------
void save_position()
{
  //save position in EEPROM
  
  webPage();
}



//---------------------------------
// Request: fixed position change
//---------------------------------
void saved_position_1()    //Full
{
  data[1] = 240;
  data[2] = 180;
  data[3] = 120;
  data[4] = 0;
  data[5] = 270;
  data[6] = 100;
  data[7] = 254;
  
  checkSendData();  
  webPage();  // Go back into web front page
}

void saved_position_2()    //Diffuse
{
  data[1] = 240;
  data[2] = 180;
  data[3] = 120;
  data[4] = 0;
  data[5] = 90;
  data[6] = 20;
  data[7] = 150;
  
  checkSendData();  
  webPage();
}

void saved_position_3()   //Angle
{
  data[1] = 100;
  data[2] = 350;
  data[3] = 200;
  data[4] = 135;
  data[5] = 45;
  data[6] = 80;
  data[7] = 200;
    
  checkSendData();
  webPage();
}

void saved_position_4()    //On the table
{
  data[1] = 200;
  data[2] = 200;
  data[3] = 200;
  data[4] = 270;
  data[5] = 90;
  data[6] = 50;
  data[7] = 254;

  checkSendData();
  webPage();
}


//--------------------------------
// Tools
//--------------------------------






//---------------------------------------
// Web communication
//---------------------------------------
void checkWiFi()
{
  if ( WiFi.status() == WL_CONNECTED )  {    WiFiIsGood = true;  }
  else                                  {    WiFiIsGood = false; }
  if ( WiFiIsGood != WiFiWasGood)
  {
  answer = String((WiFiIsGood)?"ON":"Off");
  Serial.println("Wifi is "+ answer);
  WiFiWasGood = WiFiIsGood;
  }
}


void webSocketEvent(WStype_t type, uint8_t * payload, size_t length)
{
  // Thanks to: Ruchir Sharma
  // https://www.hackster.io/ruchir1674/voice-controlled-switch-using-arduino-and-alexa-0669a5
  switch(type) {
    case WStype_DISCONNECTED:
      webSocket.begin(host, port, path);
      webSocket.onEvent(webSocketEvent);
      break;
    case WStype_CONNECTED:
      // Tell Heroku server we're connected
      webSocket.sendTXT("Connected");
      break;
    case WStype_TEXT:
      // Receive data from Heroku
      processWebSocketRequest((char*)payload);
      break;
    case WStype_BIN:
      hexdump(payload, length);
      // Send data to Heroku
      webSocket.sendBIN(payload, length);
      break;
  }
}

void processWebSocketRequest(String data)
{
  // Again, thanks to: Ruchir Sharma
  // https://www.hackster.io/ruchir1674/voice-controlled-switch-using-arduino-and-alexa-0669a5

   String jsonResponse = "{\"version\": \"1.0\",\"sessionAttributes\": {},\"response\": {\"outputSpeech\": {\"type\": \"PlainText\",\"text\": \"<text>\"},\"shouldEndSession\": true}}";
   JsonObject& root = jsonBuffer.parseObject(data);
   String query = root["query"];
   String message="";
   Serial.println(data);
            
   if(query == "light")
   { //if query check state
     String value = root["value"];  
     Serial.println("Received command!");
     if(value=="full")
     {
       message = "{\"full\":\"light\"}";
       saved_position_1();
     }
     else if (value=="diffuse")
     {
       message = "{\"diffuse\":\"light\"}";
       saved_position_2();
     }
     else if (value=="angle")
     {
       message = "{\"angle\":\"light\"}";
       saved_position_3();
     }
     else if (value=="table")
     {
       message = "{\"table\":\"light\"}";
       saved_position_4();
     }
     else
     {
       String object = root["object"];
     }
     jsonResponse.replace("<text>", "It is done");

   }else if(query == "help")
   {
     message = "Sputnik spotlight is a remote controlled lamp. Can change position, rotate and dimmer.";
   }
   else
   {//can not recognized the command
     Serial.println("Command is not recognized!");
   }
   // send message to server
   webSocket.sendTXT(jsonResponse);
   if(query == "cmd" || query == "?")
   {
     webSocket.sendTXT(jsonResponse);
   }
}



//---------------------------------------
// Lamp communication
//---------------------------------------
void checkSendData()
{
  // This check if the request are inside default range
  for (int i = 1; i<numberOfDevices; i++)
  {
    if (data[i] < minLength[i]) { data[i] = minLength[i]; }
    if (data[i] > maxLength[i]) { data[i] = maxLength[i]; }
  }
  
  // Here should be a better check,
  // e.g. controlling if wire1 + wire2 perform an impossible position.
  
  message = "";
  message = message + "1:"+ data[1]+"-";
  message = message + "2:"+ data[2]+"-";
  message = message + "3:"+ data[3]+"-";
  message = message + "4:"+ data[4]+"-";
  message = message + "5:"+ data[5]+"-";
  message = message + "6:"+ data[6]+"-";
  message = message + "7:"+ data[7]+"-";
  Serial.println(message);
  delay(delayRepeat);
  Serial.println(message);
}
Attiny85 - Wall deviceArduino
/* 
  23-2-2018
  Sputnik Spotlight

  Attiny85 @ internal 8mhz clock

  Wall device - UART
  
  To adapt code on the 3 wall devices, change only variable thisDevice


------------Pinout map--------------------
              ____
Reset      5-|   |-Vcc
Sensor  A3=3-|    |-2   Tx       [SCK ]
Stepper2   4-|    |-1   Stepper1 [MISO]
         Gnd-|____|-0   Rx       [MOSI]


to do:
- calibrate reading position
- stopper

*/

#define F_CPU 8000000UL  // 8 MHz

#include <Stepper.h>
#include <SoftwareSerial.h>

// Device identity
int thisDevice = 1;


// Define pins for Serial communication
#define rxPin 0
#define txPin 2

// Start serial
SoftwareSerial sputnikSerial(rxPin, txPin);


// Define steppers outputs
//NEMA-17 does 200 steps per revolution, with 1.8 per step
#define stepper_COILS_1_2    1   //pin controlling 2 coils
#define stepper_COILS_3_4    4
#define STEPSREVOLUTION    200
#define STEPPER_SPEED       80     //(RPM)

// Start stepper control
Stepper stepper(STEPSREVOLUTION, stepper_COILS_1_2, stepper_COILS_3_4);


const int stepperOutput1Pin = stepper_COILS_1_2;
const int stepperOutput2Pin = stepper_COILS_3_4;
const int sensorPin = 3;
//const int stopperPin = 5;  //by disabling reset pin
                             //Be aware of consequences!

int steps = 20;  // Adjust this
                 //20 = 1/10 revolution every loop
                 
int i, j, device, readLength;

const int maxNumberDevices = 7;
volatile int data[maxNumberDevices];

const int tinyBufferLength = 35;  //7 devices * 5 char ( "n:xxx" command )
char tinyBuffer[tinyBufferLength];
String message;

int rythm = 300;
bool newDataHasArrived;
bool messageIsMine;
bool jobToBeDone;

unsigned long nowMillis, thenMillis;

void setup()
{
  sputnikSerial.begin(2400);
  pinMode(stepperOutput1Pin, OUTPUT);
  pinMode(stepperOutput2Pin, OUTPUT);
  pinMode(sensorPin, INPUT);

  stepper.setSpeed(STEPPER_SPEED);
// First reading
// Estimate wire length from variable resistor value
// check gears 20:1 reduction and correct
  readLength = map(analogRead(sensorPin), 0, 1023, 0, 450);
}

void loop()
{
  nowMillis = millis();
  // Check new requests
  if (sputnikSerial.available())
  { 
    tinyBuffer[i] = sputnikSerial.read();
    if (int(tinyBuffer[i])==13 || int(tinyBuffer[i])==10 )
    { //If Carriage return has been reached
       i= 0;
      for (j=0; j<=tinyBufferLength; j++)
      {
        //  Check if the message is for this device
        // if (int(tinyBuffer[j]) == 48 and int(tinyBuffer[j+1]) == 58)
        // 49 = "1" 58 = ":"
        if (int(tinyBuffer[j]) == (48+thisDevice) and int(tinyBuffer[j+1]) == 58);
        {
          // Ok, this is for me
          messageIsMine = true;
          j++;  // To jump device number
          j++;  // To jump ":" character
        }
        if (messageIsMine)
        {          
          if (int(tinyBuffer[j]) == 45     // 45 = "-"
              or j == 16                   // end of buffer
              or tinyBuffer[j] == (char) 0 // empty char
              or tinyBuffer[j] == ' '      // emptied char
              or int(tinyBuffer[j])==13    // carriage
              or int(tinyBuffer[j])==10)   // carriage, eol
          {
            messageIsMine = false;
            newDataHasArrived = true;
          }
          else
          {
            message.concat(String(tinyBuffer[j]));
          }
        }
        tinyBuffer[j] = ' ';
      }      
    }
    i++;
  }  //end new request

  // Execute commands
  if (newDataHasArrived)
  {
    jobToBeDone = true;
    message = "";
    data[thisDevice] = message.toInt();
    newDataHasArrived = false;
  }

  if (jobToBeDone)
  {
    // Estimate wire length from variable resistor value
    // check gears 20:1 reduction and correct
    readLength = map(analogRead(sensorPin), 0, 1023, 0, 450);
    if (readLength == data[thisDevice])
    {
      jobToBeDone = false;
    }
    else
    {
      if (data[thisDevice] < readLength)   // Less wire, rotate counterclockwise
      {
        stepper.step(steps);
      }
      else   // More wire, rotate clockwise
      {
        stepper.step(-steps);
      }
    }
  }
}
Attiny85 - Intermediate ringArduino
/* 
  23-2-2018
  Sputnik Spotlight

  Attiny85 @ internal 8mhz clock

  X Stepper UART
  Intermediate ring, controlling rotation


------------Pinout map--------------------
              ____
Reset      5-|   |-Vcc
Reed    A3=3-|    |-2   Tx       [SCK ]
Stepper2   4-|    |-1   Stepper1 [MISO]
         Gnd-|____|-0   Rx       [MOSI]


*/

#define F_CPU 8000000UL  // 8 MHz

#include <Stepper.h>
#include <SoftwareSerial.h>

// Device identity
int thisDevice = 4;


// Define pins for Serial communication
#define rxPin 0
#define txPin 2

// Start serial
SoftwareSerial sputnikSerial(rxPin, txPin);


// Define steppers outputs
// Ministepper, 5v, is be controlled by L293D driver
#define stepper_COILS_1_2    1   //pin controlling 2 coils of X rotation
#define stepper_COILS_3_4    4
#define MINI_STEPSREVOLUTION    200
#define MINI_SPEED       50     //(RPM)
int stepAngle = 1;     // Adjust this: single steps
float minAngle = 360 / MINI_STEPSREVOLUTION;     

// Start stepper control
Stepper XStepper(MINI_STEPSREVOLUTION, stepper_COILS_1_2, stepper_COILS_3_4);


const int stepperOutput1Pin = stepper_COILS_1_2;
const int stepperOutput2Pin = stepper_COILS_3_4;
const int reedSensorPin = 3;
                 
int i, j, device, lastAngle;

const int maxNumberDevices = 7;
volatile int data[maxNumberDevices];

const int tinyBufferLength = 35;  //7 devices * 5 char ( "n:xxx" command )
char tinyBuffer[tinyBufferLength];
String message;

int rythm = 300;
bool newDataHasArrived;
bool messageIsMine;
bool jobToBeDone;
bool angleIsKnown;
int unknownAngle = 0;
bool reedSensorReading;

unsigned long nowMillis, thenMillis;

void setup()
{
  sputnikSerial.begin(2400);
  
  pinMode(stepperOutput1Pin, OUTPUT);
  pinMode(stepperOutput2Pin, OUTPUT);
  pinMode(reedSensorPin, INPUT);

  XStepper.setSpeed(MINI_SPEED);

}

void loop()
{
  nowMillis = millis();
  // Check new requests
  if (sputnikSerial.available())
  { 
    tinyBuffer[i] = sputnikSerial.read();
    if (int(tinyBuffer[i])==13 || int(tinyBuffer[i])==10 )
    { //If Carriage return has been reached
       i= 0;
      for (j=0; j<=tinyBufferLength; j++)
      {
        //  Check if the message is for this device
        // if (int(tinyBuffer[j]) == 48 and int(tinyBuffer[j+1]) == 58)
        // 49 = "1" 58 = ":"
        if (int(tinyBuffer[j]) == (48+thisDevice) and int(tinyBuffer[j+1]) == 58);
        {
          // Ok, this is for me
          messageIsMine = true;
          j++;  // To jump device number
          j++;  // To jump ":" character
        }
        if (messageIsMine)
        {          
          if (int(tinyBuffer[j]) == 45     // 45 = "-"
              or j == 16                   // end of buffer
              or tinyBuffer[j] == (char) 0 // empty char
              or tinyBuffer[j] == ' '      // emptied char
              or int(tinyBuffer[j])==13    // carriage
              or int(tinyBuffer[j])==10)   // carriage, eol
          {
            messageIsMine = false;
            newDataHasArrived = true;
          }
          else
          {
            message.concat(String(tinyBuffer[j]));
          }
        }
        tinyBuffer[j] = ' ';
      }      
    }
    i++;
  }  //end new request

  // Execute commands
  if (newDataHasArrived)
  {
    jobToBeDone = true;
    message = "";
    data[thisDevice] = message.toInt();
    newDataHasArrived = false;
  }

  if (jobToBeDone)
  {
    if (angleIsKnown == false)
    {
      findXzero();
    }
    if (data[i] < lastAngle)   // Rotate clockwise
    {
      XStepper.step(stepAngle);
      lastAngle = lastAngle - minAngle;
    }
    else                         // Rotate counterclockwise
    {
      XStepper.step(-stepAngle);
      lastAngle = lastAngle + minAngle;
    }
    delay(10); 
  }
}


// Rotate clockwise until reed sensor reveals zero position
void findXzero()
{
  while (angleIsKnown == false)
  {
    reedSensorReading = digitalRead(reedSensorPin);
    if (reedSensorReading == true)
    {
      angleIsKnown = true;
      lastAngle = unknownAngle;
    }
    else
    {
      unknownAngle++;
      XStepper.step(stepAngle);
      delay(10);
    }
  }
}
intent_schema.jsonJSON
In Heroku git
{
  "languageModel": {
    "types": [
      {
        "name": "configuration",
        "values": [
          {
            "id": null,
            "name": {
              "value": "full",
              "synonyms": [
                "full light",
                "direct",
                "one"
              ]
            }
          },
          {
            "id": null,
            "name": {
              "value": "diffuse",
              "synonyms": [
                "soft",
                "soft light",
                "atmosphere",
                "two"
              ]
            }
          },
          {
            "id": null,
            "name": {
              "value": "angle",
              "synonyms": [
                "reading",
                "stage",
                "three"
              ]
            }
          },
          {
            "id": null,
            "name": {
              "value": "table",
              "synonyms": [
                "desktop",
                "on the table",
                "four"
              ]
            }
          }
        ]
      }
    ],
    "intents": [
      {
        "name": "AMAZON.CancelIntent",
        "samples": [
          "cancel",
          "nothing"
        ]
      },
      {
        "name": "AMAZON.HelpIntent",
        "samples": [
          "help",
          "options"
        ]
      },
      {
        "name": "AMAZON.StopIntent",
        "samples": [
          "stop",
          "stop now"
        ]
      },
      {
        "name": "light",
        "samples": [
          "to light {configuration}",
          "turn on {configuration}",
          "set lights {configuration}",
          "light {configuration}",
          "number {Numbers}"
        ],
        "slots": [
          {
            "name": "Numbers",
            "type": "AMAZON.NUMBER"
          },
          {
            "name": "configuration",
            "type": "configuration"
          }
        ]
      }
    ],
    "invocationName": "sputnik"
  }
}

Custom parts and enclosures

Structure
3D made with Freecad; file .stl
Some minor adjustment known:
- holes too little
- in Internal, bug on a cut
- mini stepper doesn't fit, need some smoothing
- external ring's mini stepper attach is too small
Lamp Sphere
Esthetic enclosure

Schematics

ATmega328P internal ring
Fritzing diagram on breadboard.
Should be inside internal ring in a very small place!
Hard to solde, better on a PCB
atmega328p_internal_breadboard_TqP7F2saAH.fzz
Attiny85 wall device - WiFi version
WiFi connected using additional ESP8266-01
When every cable communication fails..
attiny_stepper_(esp8266-01)_breadboard_h6hgZMfNGQ.fzz
Attiny85 intermediate ring
Small PCB between the two mobile rings; controlling one axis of rotation through mini stepper (called "x"); checking 0° through reed sensor
attiny_intermediate_breadboard_ipUma4nqP0.fzz
Attiny85 wall device, UART version
Circuit of the wall device, controlling big stepper motor that pull one of the three strings. It also recognizes position (absolute) through a worm-geared variable resistor and trigger a stopping solenoid (optional, disabling reset pin to be not programmable).
UART method
attiny_stepper_(uart)_breadboard_KCUungAQGS.fzz

Comments

Similar projects you might like

Eye Lock

Project tutorial by Team Nsci

  • 1,958 views
  • 0 comments
  • 17 respects

Rainometer

Project tutorial by Team King Bros

  • 2,165 views
  • 0 comments
  • 6 respects

Raspberry Pi - Powered Candy Dispenser

Project tutorial by Arduino “having11” Guy

  • 5,283 views
  • 1 comment
  • 12 respects

Secure Package Delivery Trunk for Your Front Porch

Project tutorial by Team Castle Locker

  • 3,105 views
  • 1 comment
  • 19 respects

Alexa Based Smart Home Monitoring

Project tutorial by Adithya TG

  • 16,639 views
  • 19 comments
  • 47 respects

Alexa BBQ/Kitchen Thermometer with IoT Arduino and e-Paper

Project tutorial by Roger Theriault

  • 2,385 views
  • 0 comments
  • 9 respects
Add projectSign up / Login