Initial commit with Arduino and client

This commit is contained in:
Tobias Blum 2016-05-12 23:05:38 +02:00
parent 1004bb979f
commit 740239b983
21 changed files with 1937 additions and 1 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
clients/web/node_modules/

View file

@ -0,0 +1,336 @@
#include "definitions.h"
// ***************************************************************************
// Load libraries for: WebServer / WiFiManager / WebSockets
// ***************************************************************************
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
// needed for library WiFiManager
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <WiFiClient.h>
#include <ESP8266mDNS.h>
#include <FS.h>
#include <WebSockets.h> //https://github.com/Links2004/arduinoWebSockets
#include <WebSocketsServer.h>
// ***************************************************************************
// Instanciate HTTP(80) / WebSockets(81) Server
// ***************************************************************************
ESP8266WebServer server ( 80 );
WebSocketsServer webSocket = WebSocketsServer(81);
// ***************************************************************************
// Load libraries / Instanciate Neopixel
// ***************************************************************************
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMLEDS, PIN, NEO_GRB + NEO_KHZ800);
// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across
// pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input
// and minimize distance between Arduino and first pixel. Avoid connecting
// on a live circuit...if you must, connect GND first.
// ***************************************************************************
// Load library "ticker" for blinking status led
// ***************************************************************************
#include <Ticker.h>
Ticker ticker;
void tick()
{
//toggle state
int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin
digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state
}
// ***************************************************************************
// Callback for WiFiManager library when config mode is entered
// ***************************************************************************
//gets called when WiFiManager enters configuration mode
void configModeCallback (WiFiManager *myWiFiManager) {
DBG_OUTPUT_PORT.println("Entered config mode");
DBG_OUTPUT_PORT.println(WiFi.softAPIP());
//if you used auto generated SSID, print it
DBG_OUTPUT_PORT.println(myWiFiManager->getConfigPortalSSID());
//entered config mode, make led toggle faster
ticker.attach(0.2, tick);
}
// ***************************************************************************
// Include: Webserver
// ***************************************************************************
#include "spiffs_webserver.h"
// ***************************************************************************
// Include: Request handlers
// ***************************************************************************
#include "request_handlers.h"
// ***************************************************************************
// Include: Color modes
// ***************************************************************************
#include "colormodes.h"
// ***************************************************************************
// MAIN
// ***************************************************************************
void setup() {
DBG_OUTPUT_PORT.begin(115200);
// set builtin led pin as output
pinMode(BUILTIN_LED, OUTPUT);
// start ticker with 0.5 because we start in AP mode and try to connect
ticker.attach(0.6, tick);
// ***************************************************************************
// Setup: WiFiManager
// ***************************************************************************
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//reset settings - for testing
//wifiManager.resetSettings();
//set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
wifiManager.setAPCallback(configModeCallback);
//fetches ssid and pass and tries to connect
//if it does not connect it starts an access point with the specified name
//here "AutoConnectAP"
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect()) {
DBG_OUTPUT_PORT.println("failed to connect and hit timeout");
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(1000);
}
//if you get here you have connected to the WiFi
DBG_OUTPUT_PORT.println("connected...yeey :)");
ticker.detach();
//keep LED on
digitalWrite(BUILTIN_LED, LOW);
// ***************************************************************************
// Setup: Neopixel
// ***************************************************************************
strip.begin();
strip.setBrightness(brightness);
strip.show(); // Initialize all pixels to 'off'
// ***************************************************************************
// Setup: MDNS responder
// ***************************************************************************
MDNS.begin(HOSTNAME);
DBG_OUTPUT_PORT.print("Open http://");
DBG_OUTPUT_PORT.print(HOSTNAME);
DBG_OUTPUT_PORT.println(".local/edit to see the file browser");
// ***************************************************************************
// Setup: WebSocket server
// ***************************************************************************
webSocket.begin();
webSocket.onEvent(webSocketEvent);
// ***************************************************************************
// Setup: SPIFFS
// ***************************************************************************
SPIFFS.begin();
{
Dir dir = SPIFFS.openDir("/");
while (dir.next()) {
String fileName = dir.fileName();
size_t fileSize = dir.fileSize();
DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
}
DBG_OUTPUT_PORT.printf("\n");
}
// ***************************************************************************
// Setup: SPIFFS Webserver handler
// ***************************************************************************
//list directory
server.on("/list", HTTP_GET, handleFileList);
//load editor
server.on("/edit", HTTP_GET, [](){
if(!handleFileRead("/edit.htm")) server.send(404, "text/plain", "FileNotFound");
});
//create file
server.on("/edit", HTTP_PUT, handleFileCreate);
//delete file
server.on("/edit", HTTP_DELETE, handleFileDelete);
//first callback is called after the request has ended with all parsed arguments
//second callback handles file uploads at that location
server.on("/edit", HTTP_POST, [](){ server.send(200, "text/plain", ""); }, handleFileUpload);
//get heap status, analog input value and all GPIO statuses in one json call
server.on("/status", HTTP_GET, [](){
String json = "{";
json += "\"heap\":"+String(ESP.getFreeHeap());
json += ", \"analog\":"+String(analogRead(A0));
json += ", \"gpio\":"+String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));
json += "}";
server.send(200, "text/json", json);
json = String();
});
//called when the url is not defined here
//use it to load content from SPIFFS
server.onNotFound([](){
if(!handleFileRead(server.uri()))
handleNotFound();
});
server.on("/upload", handleMinimalUpload);
// ***************************************************************************
// Setup: SPIFFS Webserver handler
// ***************************************************************************
server.on("/brightness", []() {
brightness = server.arg("p").toInt();
if (brightness > 255) {
brightness = 255;
}
if (brightness < 0) {
brightness = 0;
}
strip.setBrightness(brightness);
if (mode == HOLD) {
mode = ALL;
}
getStatusJSON();
});
server.on("/status", []() {
getStatusJSON();
});
server.on("/off", []() {
exit_func = true;
mode = OFF;
getArgs();
getStatusJSON();
});
server.on("/all", []() {
exit_func = true;
mode = ALL;
getArgs();
getStatusJSON();
});
server.on("/wipe", []() {
exit_func = true;
mode = WIPE;
getArgs();
getStatusJSON();
});
server.on("/rainbow", []() {
exit_func = true;
mode = RAINBOW;
getArgs();
getStatusJSON();
});
server.on("/rainbowCycle", []() {
exit_func = true;
mode = RAINBOWCYCLE;
getArgs();
getStatusJSON();
});
server.on("/theaterchase", []() {
exit_func = true;
mode = THEATERCHASE;
getArgs();
getStatusJSON();
});
server.on("/theaterchaseRainbow", []() {
exit_func = true;
mode = THEATERCHASERAINBOW;
getArgs();
getStatusJSON();
});
server.on("/tv", []() {
exit_func = true;
mode = TV;
getArgs();
getStatusJSON();
});
server.begin();
}
void loop() {
server.handleClient();
webSocket.loop();
// Simple statemachine that handles the different modes
if (mode == OFF) {
colorWipe(strip.Color(0, 0, 0), 50);
mode = HOLD;
}
if (mode == ALL) {
uint16_t i;
for (i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, main_color.red, main_color.green, main_color.blue);
}
strip.show();
mode = HOLD;
}
if (mode == WIPE) {
colorWipe(strip.Color(main_color.red, main_color.green, main_color.blue), delay_ms);
}
if (mode == RAINBOW) {
rainbow(delay_ms);
}
if (mode == RAINBOWCYCLE) {
rainbowCycle(delay_ms);
}
if (mode == THEATERCHASE) {
theaterChase(strip.Color(main_color.red, main_color.green, main_color.blue), delay_ms);
}
if (mode == THEATERCHASERAINBOW) {
theaterChaseRainbow(delay_ms);
}
if (mode == HOLD) {
if (exit_func) {
exit_func = false;
}
}
if (mode == TV) {
tv();
}
}

View file

@ -0,0 +1,218 @@
// ***************************************************************************
// Color modes
// ***************************************************************************
int dipInterval = 10;
int darkTime = 250;
unsigned long currentDipTime;
unsigned long dipStartTime;
unsigned long currentMillis;
int ledState = LOW;
long previousMillis = 0;
int led = 5;
int interval = 2000;
int twitch = 50;
int dipCount = 0;
int analogLevel = 100;
boolean timeToDip = false;
int ledStates[12];
void hsb2rgbAN1(uint16_t index, uint8_t sat, uint8_t bright, uint8_t myled) {
// Source: https://blog.adafruit.com/2012/03/14/constant-brightness-hsb-to-rgb-algorithm/
uint8_t temp[5], n = (index >> 8) % 3;
temp[0] = temp[3] = (uint8_t)(( (sat ^ 255) * bright) / 255);
temp[1] = temp[4] = (uint8_t)((((( (index & 255) * sat) / 255) + (sat ^ 255)) * bright) / 255);
temp[2] = (uint8_t)(((((((index & 255) ^ 255) * sat) / 255) + (sat ^ 255)) * bright) / 255);
strip.setPixelColor(myled, temp[n + 2], temp[n + 1], temp[n]);
}
void updateLed (int led, int brightness) {
ledStates[led] = brightness;
for (int i=0;i<12;i++)
{
uint16_t index = (i%3 == 0) ? 400 : random(0,767);
hsb2rgbAN1(index, 200, ledStates[i], i);
}
strip.show();
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
// See: http://forum.mysensors.org/topic/85/phoneytv-for-vera-is-here/13
void tv() {
checkForRequests();
if (exit_func) {
exit_func = false;
return;
}
if (timeToDip == false)
{
currentMillis = millis();
if(currentMillis-previousMillis > interval)
{
previousMillis = currentMillis;
interval = random(750,4001);//Adjusts the interval for more/less frequent random light changes
twitch = random(40,100);// Twitch provides motion effect but can be a bit much if too high
dipCount = dipCount++;
}
if(currentMillis-previousMillis<twitch)
{
led=random(0,11);
analogLevel=random(50,255);// set the range of the 3 pwm leds
ledState = ledState == LOW ? HIGH: LOW; // if the LED is off turn it on and vice-versa:
updateLed(led, (ledState) ? 255 : 0);
if (dipCount > dipInterval)
{
DBG_OUTPUT_PORT.println("dip");
timeToDip = true;
dipCount = 0;
dipStartTime = millis();
darkTime = random(50,150);
dipInterval = random(5,250);// cycles of flicker
}
//strip.show();
}
}
else
{
DBG_OUTPUT_PORT.println("Dip Time");
currentDipTime = millis();
if (currentDipTime - dipStartTime < darkTime)
{
for (int i=3;i<9;i++)
{
updateLed (i, 0);
}
}
else
{
timeToDip = false;
}
strip.show();
}
}
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
checkForRequests();
if (exit_func) {
exit_func = false;
return;
}
delay(wait);
}
mode = HOLD;
}
void rainbow(uint8_t wait) {
uint16_t i, j;
for (j = 0; j < 256; j++) {
for (i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel((i + j) & 255));
}
strip.show();
checkForRequests();
if (exit_func) {
exit_func = false;
return;
}
delay(wait);
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for (j = 0; j < 256; j++) { // 1 cycle of all colors on wheel
for (i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
checkForRequests();
if (exit_func) {
exit_func = false;
return;
}
delay(wait);
}
}
// Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
for (int q = 0; q < 3; q++) {
for (int i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, c); //turn every third pixel on
}
strip.show();
checkForRequests();
if (exit_func) {
exit_func = false;
return;
}
delay(wait);
for (int i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, 0); //turn every third pixel off
}
}
}
// Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
for (int j = 0; j < 256; j++) { // cycle all 256 colors in the wheel
for (int q = 0; q < 3; q++) {
for (int i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, Wheel( (i + j) % 255)); //turn every third pixel on
}
strip.show();
checkForRequests();
if (exit_func) {
exit_func = false;
return;
}
delay(wait);
for (int i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, 0); //turn every third pixel off
}
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

View file

@ -0,0 +1,97 @@
<!--
FSWebServer - Example Index Page
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the ESP8266WebServer library for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP Monitor</title>
<script type="text/javascript" src="graphs.js"></script>
<script type="text/javascript">
var heap,temp,digi;
var reloadPeriod = 1000;
var running = false;
function loadValues(){
if(!running) return;
var xh = new XMLHttpRequest();
xh.onreadystatechange = function(){
if (xh.readyState == 4){
if(xh.status == 200) {
var res = JSON.parse(xh.responseText);
heap.add(res.heap);
temp.add(res.analog);
digi.add(res.gpio);
if(running) setTimeout(loadValues, reloadPeriod);
} else running = false;
}
};
xh.open("GET", "/status", true);
xh.send(null);
};
function run(){
if(!running){
running = true;
loadValues();
}
}
function onBodyLoad(){
var refreshInput = document.getElementById("refresh-rate");
refreshInput.value = reloadPeriod;
refreshInput.onchange = function(e){
var value = parseInt(e.target.value);
reloadPeriod = (value > 0)?value:0;
e.target.value = reloadPeriod;
}
var stopButton = document.getElementById("stop-button");
stopButton.onclick = function(e){
running = false;
}
var startButton = document.getElementById("start-button");
startButton.onclick = function(e){
run();
}
// Example with 10K thermistor
//function calcThermistor(v) {
// var t = Math.log(((10230000 / v) - 10000));
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
// return (t>120)?0:Math.round(t*10)/10;
//}
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 30000, true, "orange");
digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
run();
}
</script>
</head>
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
<label>Period (ms):</label>
<input type="number" id="refresh-rate"/>
<input type="button" id="start-button" value="Start"/>
<input type="button" id="stop-button" value="Stop"/>
</div>
<div id="heap"></div>
<div id="analog"></div>
<div id="digital"></div>
</body>
</html>

View file

@ -0,0 +1,33 @@
// Neopixel
#define PIN 5 // PIN where neopixel / WS2811 strip is attached
#define NUMLEDS 12 // Number of leds in the strip
#define HOSTNAME "ESP8266_01" // Friedly hostname
// ***************************************************************************
// Global variables / definitions
// ***************************************************************************
#define DBG_OUTPUT_PORT Serial // Set debug output port
// List of all color modes
enum MODE { HOLD, OFF, ALL, WIPE, RAINBOW, RAINBOWCYCLE, THEATERCHASE, THEATERCHASERAINBOW, TV };
MODE mode = RAINBOWCYCLE; // Standard mode that is active when software starts
int delay_ms = 50; // Global variable for storing the delay between color changes --> smaller == faster
int brightness = 128; // Global variable for storing the brightness (255 == 100%)
bool exit_func = false; // Global helper variable to get out of the color modes when mode changes
struct ledstate // Data structure to store a state of a single led
{
uint8_t red;
uint8_t green;
uint8_t blue;
};
typedef struct ledstate LEDState; // Define the datatype LEDState
LEDState ledstates[NUMLEDS]; // Get an array of led states to store the state of the whole strip
LEDState main_color; // Store the "main color" of the strip used in single color modes

View file

@ -0,0 +1,205 @@
// ***************************************************************************
// Request handlers
// ***************************************************************************
void getArgs() {
main_color.red = server.arg("r").toInt();
main_color.green = server.arg("g").toInt();
main_color.blue = server.arg("b").toInt();
delay_ms = server.arg("d").toInt();
if (main_color.red > 255) {
main_color.red = 255;
}
if (main_color.green > 255) {
main_color.green = 255;
}
if (main_color.blue > 255) {
main_color.blue = 255;
}
if (main_color.red < 0) {
main_color.red = 0;
}
if (main_color.green < 0) {
main_color.green = 0;
}
if (main_color.blue < 0) {
main_color.blue = 0;
}
if (server.arg("d") == "") {
delay_ms = 20;
}
DBG_OUTPUT_PORT.print("Mode: ");
DBG_OUTPUT_PORT.print(mode);
DBG_OUTPUT_PORT.print(", Color: ");
DBG_OUTPUT_PORT.print(main_color.red);
DBG_OUTPUT_PORT.print(", ");
DBG_OUTPUT_PORT.print(main_color.green);
DBG_OUTPUT_PORT.print(", ");
DBG_OUTPUT_PORT.print(main_color.blue);
DBG_OUTPUT_PORT.print(", Delay:");
DBG_OUTPUT_PORT.print(delay_ms);
DBG_OUTPUT_PORT.print(", Brightness:");
DBG_OUTPUT_PORT.println(brightness);
}
void handleMinimalUpload() {
char temp[1500];
int sec = millis() / 1000;
int min = sec / 60;
int hr = min / 60;
snprintf ( temp, 1500,
"<!DOCTYPE html>\
<html>\
<head>\
<title>ESP8266 Upload</title>\
<meta charset=\"utf-8\">\
<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
</head>\
<body>\
<form action=\"/edit\" method=\"post\" enctype=\"multipart/form-data\">\
<input type=\"file\" name=\"data\">\
<input type=\"text\" name=\"path\" value=\"/\">\
<button>Upload</button>\
</form>\
</body>\
</html>",
hr, min % 60, sec % 60
);
server.send ( 200, "text/html", temp );
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for ( uint8_t i = 0; i < server.args(); i++ ) {
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
}
server.send ( 404, "text/plain", message );
}
void getStatusJSON() {
char json[255];
snprintf(json, sizeof(json), "{\"mode\":%d, \"delay_ms\":%d, \"brightness\":%d, \"color\":[%d, %d, %d]}", mode, delay_ms, brightness, main_color.red, main_color.green, main_color.blue);
server.send ( 200, "application/json", json );
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
switch(type) {
case WStype_DISCONNECTED:
DBG_OUTPUT_PORT.printf("WS: [%u] Disconnected!\n", num);
break;
case WStype_CONNECTED: {
IPAddress ip = webSocket.remoteIP(num);
DBG_OUTPUT_PORT.printf("WS: [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
// send message to client
webSocket.sendTXT(num, "Connected");
}
break;
case WStype_TEXT:
DBG_OUTPUT_PORT.printf("WS: [%u] get Text: %s\n", num, payload);
// # ==> Set main color
if(payload[0] == '#') {
// decode rgb data
uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16);
main_color.red = ((rgb >> 16) & 0xFF);
main_color.green = ((rgb >> 8) & 0xFF);
main_color.blue = ((rgb >> 0) & 0xFF);
DBG_OUTPUT_PORT.printf("Set main color to: [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue);
}
// # ==> Set delay
if(payload[0] == '?') {
// decode rgb data
uint8_t d = (uint8_t) strtol((const char *) &payload[1], NULL, 16);
delay_ms = ((d >> 0) & 0xFF);
DBG_OUTPUT_PORT.printf("WS: Set delay to: [%u]\n", delay_ms);
}
// * ==> Set main color and light all LEDs (Shortcut)
if(payload[0] == '*') {
// decode rgb data
uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16);
main_color.red = ((rgb >> 16) & 0xFF);
main_color.green = ((rgb >> 8) & 0xFF);
main_color.blue = ((rgb >> 0) & 0xFF);
for (int i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, main_color.red, main_color.green, main_color.blue);
}
strip.show();
DBG_OUTPUT_PORT.printf("WS: Set all leds to main color: [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue);
mode = HOLD;
}
// ! ==> Set single LED in given color
if(payload[0] == '!') {
// decode led index
uint64_t rgb = (uint64_t) strtol((const char *) &payload[1], NULL, 16);
uint8_t led = ((rgb >> 24) & 0xFF);
if (led < strip.numPixels()) {
ledstates[led].red = ((rgb >> 16) & 0xFF);
ledstates[led].green = ((rgb >> 8) & 0xFF);
ledstates[led].blue = ((rgb >> 0) & 0xFF);
DBG_OUTPUT_PORT.printf("WS: Set single led [%u] to [%u] [%u] [%u]!\n", led, ledstates[led].red, ledstates[led].green, ledstates[led].blue);
for (uint8_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, ledstates[i].red, ledstates[i].green, ledstates[i].blue);
//DBG_OUTPUT_PORT.printf("[%u]--[%u] [%u] [%u] [%u] LED index!\n", rgb, i, ledstates[i].red, ledstates[i].green, ledstates[i].blue);
}
strip.show();
}
mode = HOLD;
}
// ! ==> Activate mode
if(payload[0] == '=') {
// we get mode data
String str_mode = String((char *) &payload[0]);
exit_func = true;
if (str_mode.startsWith("=wipe")) {
mode = WIPE;
}
if (str_mode.startsWith("=rainbow")) {
mode = RAINBOW;
}
if (str_mode.startsWith("=rainbowCycle")) {
mode = RAINBOWCYCLE;
}
if (str_mode.startsWith("=theaterchase")) {
mode = THEATERCHASE;
}
if (str_mode.startsWith("=theaterchaseRainbow")) {
mode = THEATERCHASERAINBOW;
}
if (str_mode.startsWith("=tv")) {
mode = TV;
}
DBG_OUTPUT_PORT.printf("Activated mode [%u]!\n", mode);
}
break;
}
}
void checkForRequests() {
webSocket.loop();
server.handleClient();
}

View file

@ -0,0 +1,158 @@
// ***************************************************************************
// SPIFFS Webserver
// Source: https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/examples/FSBrowser
// ***************************************************************************
/*
FSWebServer - Example WebServer with SPIFFS backend for esp8266
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the ESP8266WebServer library for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE)
or you can upload the contents of a folder if you CD in that folder and run the following command:
for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done
access the sample web page at http://esp8266fs.local
edit the page by going to http://esp8266fs.local/edit
*/
File fsUploadFile;
//format bytes
String formatBytes(size_t bytes) {
if (bytes < 1024) {
return String(bytes) + "B";
} else if (bytes < (1024 * 1024)) {
return String(bytes / 1024.0) + "KB";
} else if (bytes < (1024 * 1024 * 1024)) {
return String(bytes / 1024.0 / 1024.0) + "MB";
} else {
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
}
}
String getContentType(String filename) {
if (server.hasArg("download")) return "application/octet-stream";
else if (filename.endsWith(".htm")) return "text/html";
else if (filename.endsWith(".html")) return "text/html";
else if (filename.endsWith(".css")) return "text/css";
else if (filename.endsWith(".js")) return "application/javascript";
else if (filename.endsWith(".png")) return "image/png";
else if (filename.endsWith(".gif")) return "image/gif";
else if (filename.endsWith(".jpg")) return "image/jpeg";
else if (filename.endsWith(".ico")) return "image/x-icon";
else if (filename.endsWith(".xml")) return "text/xml";
else if (filename.endsWith(".pdf")) return "application/x-pdf";
else if (filename.endsWith(".zip")) return "application/x-zip";
else if (filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain";
}
bool handleFileRead(String path) {
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
if (path.endsWith("/")) path += "index.htm";
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) {
if (SPIFFS.exists(pathWithGz))
path += ".gz";
File file = SPIFFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
void handleFileUpload() {
if (server.uri() != "/edit") return;
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
if (!filename.startsWith("/")) filename = "/" + filename;
DBG_OUTPUT_PORT.print("handleFileUpload Name: ");
DBG_OUTPUT_PORT.println(filename);
fsUploadFile = SPIFFS.open(filename, "w");
filename = String();
} else if (upload.status == UPLOAD_FILE_WRITE) {
//DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
if (fsUploadFile)
fsUploadFile.write(upload.buf, upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
if (fsUploadFile)
fsUploadFile.close();
DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
}
}
void handleFileDelete() {
if (server.args() == 0) return server.send(500, "text/plain", "BAD ARGS");
String path = server.arg(0);
DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
if (path == "/")
return server.send(500, "text/plain", "BAD PATH");
if (!SPIFFS.exists(path))
return server.send(404, "text/plain", "FileNotFound");
SPIFFS.remove(path);
server.send(200, "text/plain", "");
path = String();
}
void handleFileCreate() {
if (server.args() == 0)
return server.send(500, "text/plain", "BAD ARGS");
String path = server.arg(0);
DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
if (path == "/")
return server.send(500, "text/plain", "BAD PATH");
if (SPIFFS.exists(path))
return server.send(500, "text/plain", "FILE EXISTS");
File file = SPIFFS.open(path, "w");
if (file)
file.close();
else
return server.send(500, "text/plain", "CREATE FAILED");
server.send(200, "text/plain", "");
path = String();
}
void handleFileList() {
if (!server.hasArg("dir")) {
server.send(500, "text/plain", "BAD ARGS");
return;
}
String path = server.arg("dir");
DBG_OUTPUT_PORT.println("handleFileList: " + path);
Dir dir = SPIFFS.openDir(path);
path = String();
String output = "[";
while (dir.next()) {
File entry = dir.openFile("r");
if (output != "[") output += ',';
bool isDir = false;
output += "{\"type\":\"";
output += (isDir) ? "dir" : "file";
output += "\",\"name\":\"";
output += String(entry.name()).substring(1);
output += "\"}";
entry.close();
}
output += "]";
server.send(200, "text/json", output);
}

View file

@ -9,6 +9,69 @@
- A WS2811 or WS2812 led strip that you can get in many sizes and forms. I'm using a ring of 12 leds. When you use more than about 15-20 leds you may have to use a dedicated 5V power source.
- Power via USB
## Wiring
Fritzing:
Parts via:
- https://github.com/squix78/esp8266-fritzing-parts
- https://github.com/adafruit/Fritzing-Library/
## Software installation
You need to complete the following steps to build your development environment that enables you to flash the Mc Lighting software to the ESP8622.
### Arduino Software (tested with 1.6.8)
Download and install the arduino software (IDE) at https://www.arduino.cc/en/Main/Software
### ESP8266 board support for arduino IDE
In the Arduino IDE open the preferences dialog and enter the following URL as "Additional Boards Manger URL":\
http://arduino.esp8266.com/stable/package_esp8266com_index.json
--> arduino_preferences.png
Go to "Tools" > "Board: <some board>" > "Boards Manager ...", search for "esp" and install the "esp8266 by ESP8266 Community" in version 2.2.0 (https://github.com/esp8266/Arduino):
--> arduino_boards_manager.png
Now go to "Tools" > "Board: <some board>" and choose "NodeMCU 1.0 (ESP-12E Module)", set CPU frequency to 80 MHz, and Flash size to "4M (1M SPIFFS)"leave upload spped at 115200. Select the right COM port.
### Used Libraries
Go to "Sketch" > "Include Library" > "Manage LIbraries ..." and install the following libraries by searching for them and installing:
- WiFiManager by @tzapu (tested with version 0.11.0)
https://github.com/tzapu/WiFiManager
- WebSockets by @Links2004 (tested with version 2.0.2)
https://github.com/Links2004/arduinoWebSockets
- Adafruit NeoPixel by @adafruit (tested with 1.0.5)
https://github.com/adafruit/Adafruit_NeoPixel
The sketch also uses the following built-in library:
- Ticker by @igrr
Parts of the code were taken or inspired by the following sources:
- HSB3RGB conversion
https://blog.adafruit.com/2012/03/14/constant-brightness-hsb-to-rgb-algorithm/
- TV simulator logic inspired by @BulldogLowell
https://github.com/BulldogLowell/PhoneyTV
- SPIFS Webserver by Hristo Gochkov
https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/examples/FSBrowser
Thank you to all the authors for distributing their software that way.
I hope I didn't miss any sources and mentioned every author. In case I forgot someone please let me know and I will fix it.
### Compiling and upload
Now open the MC Lighting Arduino sketch in the IDE via "File" > "Open ...". Have a look at the "definitions.h" and change the values here to fit your setup:
```c
// Neopixel
#define PIN 5 // PIN where neopixel / WS2811 strip is attached
#define NUMLEDS 12 // Number of leds in the strip
#define HOSTNAME "ESP8266_01" // Friedly hostname
```
Now you have done everything to get all the dependencies. You should now be able to build the software by choosing "Sketch" > "Verify / Compile" (or clicking the tick mark in the tool bar).
Please verify that you have connected the ESP board correctly to your computer via USB and that the correct COM port is chosen.
Now you should be able to upload the compiled sketch to the board via "Sketch" > "Upload" (or by clicking the right arrow in the tool bar).
## Todos
- [x] Fix issue with websockets connection problems
- [ ] Switch to the [NeoPixelBus library](https://github.com/Makuna/NeoPixelBus/wiki)
@ -23,4 +86,4 @@
*More information will be added as soon as I clean up the code.*
*More information will be added as soon as I clean up the code and complete documentation.*

View file

@ -0,0 +1,180 @@
<!DOCTYPE html>
<html>
<head>
<!--Import Google Icon Font-->
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!--Import materialize.css-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css" media="screen,projection" />
<!--Let browser know website is optimized for mobile-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
</head>
<body>
<nav class="light-blueXXX lighten-1XXX" role="navigation" id="mc-nav">
<div class="nav-wrapper container"><a id="logo-container" href="#" class="brand-logo">Mc Lighting</a>
<ul class="right hide-on-med-and-down">
<li><a href="#" class="mc-navlink" data-pane="pane1">Wheel</a></li>
<li><a href="#" class="mc-navlink" data-pane="pane2">Modes</a></li>
</ul>
<ul id="nav-mobile" class="side-nav">
<li><a href="#" class="mc-navlink" data-pane="pane1">Wheel</a></li>
<li><a href="#" class="mc-navlink" data-pane="pane2">Modes</a></li>
</ul>
<a href="#" data-activates="nav-mobile" class="button-collapse"><i class="material-icons">menu</i></a>
</div>
</nav>
<div class="container mc_pane" id="pane0">
<div class="section">
<div class="row" id="mc-wsloader">
<div class="col">
<div class="preloader-wrapper active">
<div class="spinner-layer spinner-red-only">
<div class="circle-clipper left">
<div class="circle"></div>
</div><div class="gap-patch">
<div class="circle"></div>
</div><div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row hide" id="mc-wserror">
<div class="col">
<div>Fehler beim Herstellen der WebSocket-Verbindung.</div>
</div>
</div>
</div>
</div>
<div class="container mc_pane hide" id="pane1">
<div class="section">
<div class="row">
<div class="col s12 m6">
<div style="height: 330px; width: 330px;">
<canvas id="myCanvas" width="330" height="330" style="-webkit-user-select: none;-webkit-tap-highlight-color: rgba(0,0,0,0);-moz-user-select:none;"></canvas>
</div>
</div>
<div class="col s12 m6">
<div class="card-panel" id="status">
<div id="status_pos">pick a color</div>
<div id="status_color"></div>
</div>
</div>
</div>
</div>
</div>
<div class="container mc_pane hide" id="pane2">
<div class="section">
<div class="row">
<div class="col s12 m6 l3 btn_grid">
<button class="btn waves-effect waves-light btn_mode" name="action" data-mode="off">Off
<i class="material-icons right">send</i>
</button>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="all">All
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="wipe">Wipe
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="rainbow">Rainbow
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="rainbowCycle">Rainbow cycle
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="theaterchase">Theaterchase
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="theaterchaseRainbow">Theaterchase rainbow
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="tv">TV
<i class="material-icons right">send</i>
</a>
</div>
</div>
<div class="row">
<form class="col s12">
<div class="row">
<div class="input-field col s12 l4">
<label for="txt_red">Red</label><br/>
<p class="range-field"><input type="range" id="rng_red" min="0" max="255" class="update_changes" /></p>
</div>
<div class="input-field col s12 l4">
<label for="txt_green">Green</label><br/>
<p class="range-field"><input type="range" id="rng_green" min="0" max="255" class="update_changes" /></p>
</div>
<div class="input-field col s12 l4">
<label for="txt_blue">Blue</label><br/>
<p class="range-field"><input type="range" id="rng_blue" min="0" max="255" class="update_changes" /></p>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<label for="txt_delay">Delay</label><br/>
<p class="range-field"><input type="range" id="rng_delay" min="0" max="500" value="50" class="update_changes" /></p>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<label for="txt_delay">Brightness</label><br/>
<p class="range-field"><input type="range" id="rng_brightness" min="0" max="255" value="255" /></p>
</div>
</div>
</form>
</div>
</div>
</div>
<footer class="page-footer">
<div class="footer-copyright">
<div class="container">
© 2016 Copyright Tobias Blum
<a class="grey-text text-lighten-4 right" href="#!">More Links</a>
</div>
</div>
</footer>
<style type="text/css">
.btn_grid {
margin: 7px 0;
}
</style>
<!--Import jQuery before materialize.js-->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js"></script>
<script src="js/script.js"></script>
</body>
</html>

View file

@ -0,0 +1,211 @@
(function($){
$(function(){
var host = "192.168.0.24";
$('.button-collapse').sideNav();
// Navlinks
$('#mc-nav').on('click', '.mc-navlink', function(){
console.log("Nav to: ", $(this).data("pane"));
showPane($(this).data("pane"));
});
function showPane(pane) {
$('.mc_pane').addClass('hide');
$('#' + pane).removeClass('hide');
$('.button-collapse').sideNav('hide');
}
// ******************************************************************
// init()
// ******************************************************************
var connection = new WebSocket('ws://' + host + ':81', ['arduino']);
// When the connection is open, send some data to the server
connection.onopen = function () {
//connection.send('Ping'); // Send the message 'Ping' to the server
console.log('WebSocket Open');
showPane('pane1');
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
$('#mc-wsloader').addClass('hide');
$('#mc-wserror').removeClass('hide');
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
// ******************************************************************
// Modes
// ******************************************************************
$("#pane2").on("click", ".btn_mode", function() {
var mode = $(this).attr("data-mode");
last_mode = mode;
var btn = $(this);
setMode(mode, function() {
$(".btn_mode").removeClass("blue");
btn.addClass("blue");
});
});
function setMode(mode, finish_funtion) {
var url = "http://" + host + "/" + mode;
console.log("Mode: ", mode);
var red = $("#rng_red").val();
var green = $("#rng_green").val();
var blue = $("#rng_blue").val();
var delay = $("#rng_delay").val();
var params = {"r":red, "g":green, "b":blue, "d":delay};
connection.send("=" + mode);
/*
$.getJSON(url, params, function(data) {
updateStatus(data);
finish_funtion();
});
*/
}
function updateStatus(data) {
console.log("Returned: ", data);
$("#result").val("Mode: " + data.mode + "\nColor: "+ data.color[0] + "," + data.color[1] + "," + data.color[2] + "\nDelay:" + data.delay_ms + "\nBrightness:" + data.brightness);
$('#result').trigger('autoresize');
}
// ******************************************************************
// Colorwheel
// ******************************************************************
// this is supposed to work on mobiles (touch) as well as on a desktop (click)
// since we couldn't find a decent one .. this try of writing one by myself
// + google. swiping would be really nice - I will possibly implement it with
// jquery later - or never.
var canvas = document.getElementById("myCanvas");
// FIX: Cancel touch end event and handle click via touchstart
// canvas.addEventListener("touchend", function(e) { e.preventDefault(); }, false);
canvas.addEventListener("touchmove", doTouch, false);
canvas.addEventListener("click", doClick, false);
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var innerRadius = canvas.width / 4.5;
var outerRadius = (canvas.width - 10) / 2
//outer border
context.beginPath();
//outer circle
context.arc(centerX, centerY, outerRadius, 0, 2 * Math.PI, false);
//draw the outer border: (gets drawn around the circle!)
context.lineWidth = 4;
context.strokeStyle = '#000000';
context.stroke();
context.closePath();
//fill with beautiful colors
//taken from here: http://stackoverflow.com/questions/18265804/building-a-color-wheel-in-html5
for(var angle=0; angle<=360; angle+=1) {
var startAngle = (angle-2)*Math.PI/180;
var endAngle = angle * Math.PI/180;
context.beginPath();
context.moveTo(centerX, centerY);
context.arc(centerX, centerY, outerRadius, startAngle, endAngle, false);
context.closePath();
context.fillStyle = 'hsl('+angle+', 100%, 50%)';
context.fill();
context.closePath();
}
//inner border
context.beginPath();
//context.arc(centerX, centerY, radius, startAngle, endAngle, counterClockwise);
context.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI, false);
//fill the center
var my_gradient=context.createLinearGradient(0,0,170,0);
my_gradient.addColorStop(0,"black");
my_gradient.addColorStop(1,"white");
context.fillStyle = my_gradient;
context.fillStyle = "white";
context.fill();
//draw the inner line
context.lineWidth = 2;
context.strokeStyle = '#000000';
context.stroke();
context.closePath();
//get Mouse x/y canvas position
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//comp to Hex
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
//rgb/rgba to Hex
function rgbToHex(rgb) {
return "#" + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
}
//display the touch/click position and color info
function showStatus(pos, color) {
var hexColor = rgbToHex(color);
connection.send("*" + componentToHex(color[0]) + componentToHex(color[1]) + componentToHex(color[2]));
$('#status').css("backgroundColor", hexColor);
$('#status_color').text(hexColor + " - R=" + color[0] + ", G=" + color[1] + ", B=" + color[2]);
$('#status_pos').text("x: " + pos.x + " - y: " + pos.y);
document.getElementById('status').style.backgroundColor=hexColor;
}
//handle the touch event
function doTouch(event) {
//to not also fire on click
event.preventDefault();
var el = event.target;
//touch position
var pos = {x: Math.round(event.targetTouches[0].pageX - el.offsetLeft),
y: Math.round(event.targetTouches[0].pageY - el.offsetTop)};
//color
var color = context.getImageData(pos.x, pos.y, 1, 1).data;
showStatus(pos, color);
}
function doClick(event) {
//click position
var pos = getMousePos(canvas, event);
//color
var color = context.getImageData(pos.x, pos.y, 1, 1).data;
console.log("click", pos.x, pos.y, color);
showStatus(pos, color);
//now do sth with the color rgbToHex(color);
//don't do stuff when #000000 (outside circle and lines
}
}); // end of document ready
})(jQuery); // end of jQuery name space

28
clients/web/gulpfile.js Normal file
View file

@ -0,0 +1,28 @@
var gulp = require('gulp'),
connect = require('gulp-connect');
gulp.task('html', function() {
gulp.src('*.html')
.pipe(gulp.dest('build'))
.pipe(connect.reload());
});
gulp.task('js', function() {
gulp.src('js/*.js')
.pipe(gulp.dest('build/js'))
.pipe(connect.reload());
});
gulp.task('connect', function() {
connect.server({
root: 'build',
livereload: true
});
});
gulp.task('watch', function() {
gulp.watch('*.html', ['html']);
gulp.watch('js/*.js', ['js']);
});
gulp.task('default', ['watch', 'connect']);

180
clients/web/index.html Normal file
View file

@ -0,0 +1,180 @@
<!DOCTYPE html>
<html>
<head>
<!--Import Google Icon Font-->
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!--Import materialize.css-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css" media="screen,projection" />
<!--Let browser know website is optimized for mobile-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
</head>
<body>
<nav class="light-blueXXX lighten-1XXX" role="navigation" id="mc-nav">
<div class="nav-wrapper container"><a id="logo-container" href="#" class="brand-logo">Mc Lighting</a>
<ul class="right hide-on-med-and-down">
<li><a href="#" class="mc-navlink" data-pane="pane1">Wheel</a></li>
<li><a href="#" class="mc-navlink" data-pane="pane2">Modes</a></li>
</ul>
<ul id="nav-mobile" class="side-nav">
<li><a href="#" class="mc-navlink" data-pane="pane1">Wheel</a></li>
<li><a href="#" class="mc-navlink" data-pane="pane2">Modes</a></li>
</ul>
<a href="#" data-activates="nav-mobile" class="button-collapse"><i class="material-icons">menu</i></a>
</div>
</nav>
<div class="container mc_pane" id="pane0">
<div class="section">
<div class="row" id="mc-wsloader">
<div class="col">
<div class="preloader-wrapper active">
<div class="spinner-layer spinner-red-only">
<div class="circle-clipper left">
<div class="circle"></div>
</div><div class="gap-patch">
<div class="circle"></div>
</div><div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row hide" id="mc-wserror">
<div class="col">
<div>Fehler beim Herstellen der WebSocket-Verbindung.</div>
</div>
</div>
</div>
</div>
<div class="container mc_pane hide" id="pane1">
<div class="section">
<div class="row">
<div class="col s12 m6">
<div style="height: 330px; width: 330px;">
<canvas id="myCanvas" width="330" height="330" style="-webkit-user-select: none;-webkit-tap-highlight-color: rgba(0,0,0,0);-moz-user-select:none;"></canvas>
</div>
</div>
<div class="col s12 m6">
<div class="card-panel" id="status">
<div id="status_pos">pick a color</div>
<div id="status_color"></div>
</div>
</div>
</div>
</div>
</div>
<div class="container mc_pane hide" id="pane2">
<div class="section">
<div class="row">
<div class="col s12 m6 l3 btn_grid">
<button class="btn waves-effect waves-light btn_mode" name="action" data-mode="off">Off
<i class="material-icons right">send</i>
</button>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="all">All
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="wipe">Wipe
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="rainbow">Rainbow
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="rainbowCycle">Rainbow cycle
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="theaterchase">Theaterchase
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="theaterchaseRainbow">Theaterchase rainbow
<i class="material-icons right">send</i>
</a>
</div>
<div class="col s12 m6 l3 btn_grid">
<a class="btn waves-effect waves-light btn_mode" name="action" data-mode="tv">TV
<i class="material-icons right">send</i>
</a>
</div>
</div>
<div class="row">
<form class="col s12">
<div class="row">
<div class="input-field col s12 l4">
<label for="txt_red">Red</label><br/>
<p class="range-field"><input type="range" id="rng_red" min="0" max="255" class="update_changes" /></p>
</div>
<div class="input-field col s12 l4">
<label for="txt_green">Green</label><br/>
<p class="range-field"><input type="range" id="rng_green" min="0" max="255" class="update_changes" /></p>
</div>
<div class="input-field col s12 l4">
<label for="txt_blue">Blue</label><br/>
<p class="range-field"><input type="range" id="rng_blue" min="0" max="255" class="update_changes" /></p>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<label for="txt_delay">Delay</label><br/>
<p class="range-field"><input type="range" id="rng_delay" min="0" max="500" value="50" class="update_changes" /></p>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<label for="txt_delay">Brightness</label><br/>
<p class="range-field"><input type="range" id="rng_brightness" min="0" max="255" value="255" /></p>
</div>
</div>
</form>
</div>
</div>
</div>
<footer class="page-footer">
<div class="footer-copyright">
<div class="container">
© 2016 Copyright Tobias Blum
<a class="grey-text text-lighten-4 right" href="#!">More Links</a>
</div>
</div>
</footer>
<style type="text/css">
.btn_grid {
margin: 7px 0;
}
</style>
<!--Import jQuery before materialize.js-->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js"></script>
<script src="js/script.js"></script>
</body>
</html>

211
clients/web/js/script.js Normal file
View file

@ -0,0 +1,211 @@
(function($){
$(function(){
var host = "192.168.0.24";
$('.button-collapse').sideNav();
// Navlinks
$('#mc-nav').on('click', '.mc-navlink', function(){
console.log("Nav to: ", $(this).data("pane"));
showPane($(this).data("pane"));
});
function showPane(pane) {
$('.mc_pane').addClass('hide');
$('#' + pane).removeClass('hide');
$('.button-collapse').sideNav('hide');
}
// ******************************************************************
// init()
// ******************************************************************
var connection = new WebSocket('ws://' + host + ':81', ['arduino']);
// When the connection is open, send some data to the server
connection.onopen = function () {
//connection.send('Ping'); // Send the message 'Ping' to the server
console.log('WebSocket Open');
showPane('pane1');
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
$('#mc-wsloader').addClass('hide');
$('#mc-wserror').removeClass('hide');
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
// ******************************************************************
// Modes
// ******************************************************************
$("#pane2").on("click", ".btn_mode", function() {
var mode = $(this).attr("data-mode");
last_mode = mode;
var btn = $(this);
setMode(mode, function() {
$(".btn_mode").removeClass("blue");
btn.addClass("blue");
});
});
function setMode(mode, finish_funtion) {
var url = "http://" + host + "/" + mode;
console.log("Mode: ", mode);
var red = $("#rng_red").val();
var green = $("#rng_green").val();
var blue = $("#rng_blue").val();
var delay = $("#rng_delay").val();
var params = {"r":red, "g":green, "b":blue, "d":delay};
connection.send("=" + mode);
/*
$.getJSON(url, params, function(data) {
updateStatus(data);
finish_funtion();
});
*/
}
function updateStatus(data) {
console.log("Returned: ", data);
$("#result").val("Mode: " + data.mode + "\nColor: "+ data.color[0] + "," + data.color[1] + "," + data.color[2] + "\nDelay:" + data.delay_ms + "\nBrightness:" + data.brightness);
$('#result').trigger('autoresize');
}
// ******************************************************************
// Colorwheel
// ******************************************************************
// this is supposed to work on mobiles (touch) as well as on a desktop (click)
// since we couldn't find a decent one .. this try of writing one by myself
// + google. swiping would be really nice - I will possibly implement it with
// jquery later - or never.
var canvas = document.getElementById("myCanvas");
// FIX: Cancel touch end event and handle click via touchstart
// canvas.addEventListener("touchend", function(e) { e.preventDefault(); }, false);
canvas.addEventListener("touchmove", doTouch, false);
canvas.addEventListener("click", doClick, false);
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var innerRadius = canvas.width / 4.5;
var outerRadius = (canvas.width - 10) / 2
//outer border
context.beginPath();
//outer circle
context.arc(centerX, centerY, outerRadius, 0, 2 * Math.PI, false);
//draw the outer border: (gets drawn around the circle!)
context.lineWidth = 4;
context.strokeStyle = '#000000';
context.stroke();
context.closePath();
//fill with beautiful colors
//taken from here: http://stackoverflow.com/questions/18265804/building-a-color-wheel-in-html5
for(var angle=0; angle<=360; angle+=1) {
var startAngle = (angle-2)*Math.PI/180;
var endAngle = angle * Math.PI/180;
context.beginPath();
context.moveTo(centerX, centerY);
context.arc(centerX, centerY, outerRadius, startAngle, endAngle, false);
context.closePath();
context.fillStyle = 'hsl('+angle+', 100%, 50%)';
context.fill();
context.closePath();
}
//inner border
context.beginPath();
//context.arc(centerX, centerY, radius, startAngle, endAngle, counterClockwise);
context.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI, false);
//fill the center
var my_gradient=context.createLinearGradient(0,0,170,0);
my_gradient.addColorStop(0,"black");
my_gradient.addColorStop(1,"white");
context.fillStyle = my_gradient;
context.fillStyle = "white";
context.fill();
//draw the inner line
context.lineWidth = 2;
context.strokeStyle = '#000000';
context.stroke();
context.closePath();
//get Mouse x/y canvas position
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//comp to Hex
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
//rgb/rgba to Hex
function rgbToHex(rgb) {
return "#" + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
}
//display the touch/click position and color info
function showStatus(pos, color) {
var hexColor = rgbToHex(color);
connection.send("*" + componentToHex(color[0]) + componentToHex(color[1]) + componentToHex(color[2]));
$('#status').css("backgroundColor", hexColor);
$('#status_color').text(hexColor + " - R=" + color[0] + ", G=" + color[1] + ", B=" + color[2]);
$('#status_pos').text("x: " + pos.x + " - y: " + pos.y);
document.getElementById('status').style.backgroundColor=hexColor;
}
//handle the touch event
function doTouch(event) {
//to not also fire on click
event.preventDefault();
var el = event.target;
//touch position
var pos = {x: Math.round(event.targetTouches[0].pageX - el.offsetLeft),
y: Math.round(event.targetTouches[0].pageY - el.offsetTop)};
//color
var color = context.getImageData(pos.x, pos.y, 1, 1).data;
showStatus(pos, color);
}
function doClick(event) {
//click position
var pos = getMousePos(canvas, event);
//color
var color = context.getImageData(pos.x, pos.y, 1, 1).data;
console.log("click", pos.x, pos.y, color);
showStatus(pos, color);
//now do sth with the color rgbToHex(color);
//don't do stuff when #000000 (outside circle and lines
}
}); // end of document ready
})(jQuery); // end of jQuery name space

15
clients/web/package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "mc_lighting",
"version": "1.0.0",
"description": "Web client for Mc Lighting",
"main": "index.html",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Tobias Blum",
"license": "MIT",
"dependencies": {
"gulp-connect": "^4.0.0",
"materialize-css": "^0.97.6"
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB