Joster
Published

Sputnik Spotlight

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

AdvancedWork in progress3,217
Sputnik Spotlight

Things used in this project

Hardware components

ESP8266 ESP-01
Espressif ESP8266 ESP-01
×1
Arduino UNO
Arduino 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 17 Stepper Motor
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
Dual H-Bridge motor drivers L293D
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
Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
Set as AlexaPi device
×1

Software apps and online services

Arduino IDE
Arduino IDE
Alexa Voice Service
Amazon Alexa Alexa Voice Service
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
Heroku Dynos
Heroku Dynos
Multilanguage code platform, web interface with Alexa Skill
Freecad
Powerful and free 3D modeling program

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

.stl files on Github

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

Attiny85 wall device - WiFi version

WiFi connected using additional ESP8266-01
When every cable communication fails..

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

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

Schematics

Code

ATmega328P

Arduino
/* 
  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-01

Arduino
/* 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 device

Arduino
/* 
  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 ring

Arduino
/* 
  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.json

JSON
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"
  }
}

Code on Github

Credits

Joster

Joster

1 project • 2 followers

Comments