From: Dmitry Shalnoff Date: Mon, 9 Sep 2024 17:40:30 +0000 (+0200) Subject: initial commit X-Git-Url: https://git.shalnoff.com/Serial_RGB_Controller/commitdiff_plain/ce4e3b5ff71b888811a14e88f2490de7b647dc11 initial commit --- ce4e3b5ff71b888811a14e88f2490de7b647dc11 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dab8ca3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +memo +info +dev +todo +BKP/* +Serial_RGB_test/* + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.bin +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/README.md b/README.md new file mode 100644 index 0000000..89a21a1 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# Serial RGB LED driver, Interplay Medium dendrite module (for ESP8266) + +This is IM denrite module (remote wifi network device) created for [Interplay Medium™](https://interplaymedium.org) project. + +![Interplay Medium RGB Serial LED Dendrite](https://repository.interplaymedium.org/RGBW%20Controller/IM_RGBW_LED_dendrite.jpeg) + +## Pinouts + +For LEDs be sure to add MOSFETs and current limiting resistors appropriately. Wiring scheme will be added probably later. + +![ESP8266 Pinout](https://repository.interplaymedium.org/RGBW%20Controller/esppinout_.png) +![ESP8266 Programming](https://repository.interplaymedium.org/RGBW%20Controller/usbprogram_.png) + +## Preparing the building environment + +Make sure that you have the environment installed as described at + +1. [makeEspArduino.mk](https://github.com/plerup/makeEspArduino.git) +2. [esp8266 Arduino SDK](https://github.com/esp8266/Arduino) + +3. In the *make* script, change path for each variable approprately: + MAKE_FILE=.... + ESP_SDK_ROOT=.... + +## Change your IM AXOD microserver or AP (router) WIFI login and password + +create the file + vim ../info + +assign SSID and PASSWORD of your local IM AXOD microserver or Access Point in there + + WIFI_SSID="ssid" + WIFI_PASS="ssid password" + +You can change it later whenewer you want using HTTP interface + +## Building + +initial buildong and flashing firmware at once + + ./make Serial_RGB upload + +after that you may just build the binary and uload it using remote HTTP interface + + ./make Serial_RGB + curl -F image=@Serial_RGB.bin -s im_<....>.lan/update + +## Usage + + +By default dendrite can be reached "im_serial_[last 4 digits of MAC address]" doman + +Change it with + + curl "im_serial_[last 4 digits of MAC address]?rename=NEWNAME" + +Turn on the color and effect + + curl "im_serial_[last 4 digits of MAC address]?gradient=ffffffccff44&slidez=-1&delay=100" + +Other options + + curl "im_serial_[last 4 digits of MAC address]/help" + +## Todo + +The development of this firmware is in progress. Here is a brief list of upcoming changes: + +* features (save, reset....) +* state return in 2 variants + txt (default) + JSON + html UI +* add commands + dimm and soft state changing + rgbwdef -- save default values in EEPROM, which is returning on reset command + +* switching AP/slave, AP by defuault + remote access setup (host name, AP/slave, SSID, passw) + save in EEPROM + +## License + +Copyright © 2016 Dmitry Shalnov [interplaymedium.org] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this files except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + + + + + diff --git a/Serial_RGB/Serial_RGB.ino b/Serial_RGB/Serial_RGB.ino new file mode 100644 index 0000000..8db32e9 --- /dev/null +++ b/Serial_RGB/Serial_RGB.ino @@ -0,0 +1,900 @@ +/* + * Serial LED Controller, IM Dendrite module (for ESP8266) + * Created for Interplaymedium™ project (https://interplaymedium.org) + * Copyright © 2016 Dmitry Shalnov [interplaymedium.org] + * Licensed under the Apache License, Version 2.0 +*/ + +#define SERIAL 0 + +#include "../../info" +#include "version.c" + +#include + +#include +#include +#include +#include +#include + +#define LED_STRIPE WS2811 // WS2812B // APA106 // type of LEDs Supported LED chipsets: http://fastled.io/docs/3.1/md__r_e_a_d_m_e.html + +// -------------------- FastLED settings -------------- + +#define FASTLED_ESP8266_RAW_PIN_ORDER +#define FASTLED_INTERNAL // to remove #pragma diagnostics messages + +#include + // Serial // Switch 4 channel +#define DATA_PIN 3 // 3 // 2 // DIn controller pin (GPIO2) TODO: switch to GPIO0 + pull down (to avoid blink on reset) +#define ENABLE_GATE_PIN 2 // 2 // 3 // common ping for SN74HC08 AND gate (to prevent blink on power on + pull up the level) +#define MAX_NUM_LEDS 256 // maximum number of leds in stripe +#define DEF_NUM_LEDS 24 // default number of leds in stripe +#define DEF_MAIN_DELAY 1 // default FX delay (in loop) + +#define FULL_OUTPUT 2 // default quiet level + +CRGB LEDStripe_RGB[ MAX_NUM_LEDS ]; // This is an array of leds. One item for each led in your stripe. +CHSV color_HSV; +String param; + +uint8_t paramCnt = 0; +uint8_t JSONon = 0; + +uint8_t numberOfLEDs = DEF_NUM_LEDS; + +uint16_t mainDelay = 0, mainDelayNew = 0; + +unsigned char FX_PhaseXDelay = 0, FX_PhaseYDelay = 0, FX_PhaseZDelay = 0; + +// uint8_t EXP[ 256 ]; // lookup table + +unsigned long timeStamp = 0; +uint8_t changeColor = 0, quiet = FULL_OUTPUT, commandReboot = false; + +// -------------------- EEPROM addresing --------------- + +#define EEPROM_HOST 0 + +#define EEPROM_HOST_MAX_LEN 57 // Host name max length, can't be > 57 (could be 64, buth there is a bug in avahi https://github.com/lathiat/avahi/issues/273) +#define EEPROM_OTHER_MAX_LEN 13 // total size of other EEPROM parameters (summ of all EEPROM values below, included EEPROM_FLAG) + + +#define EEPROM_FLAG EEPROM_HOST_MAX_LEN + 1 // EEPROM has been changed (avoid overlap: EEPROM_HOST_MAX_LEN) + +#define EEPROM_numberOfLEDs EEPROM_HOST_MAX_LEN + 2 + +#define EEPROM_HgradBegin EEPROM_HOST_MAX_LEN + 3 // (the order of defines matter) +#define EEPROM_SgradBegin EEPROM_HOST_MAX_LEN + 4 +#define EEPROM_VgradBegin EEPROM_HOST_MAX_LEN + 5 + +#define EEPROM_HgradEnd EEPROM_HOST_MAX_LEN + 6 +#define EEPROM_SgradEnd EEPROM_HOST_MAX_LEN + 7 +#define EEPROM_VgradEnd EEPROM_HOST_MAX_LEN + 8 + +#define EEPROM_PERIOD EEPROM_HOST_MAX_LEN + 9 + +#define EEPROM_FX_PhaseX EEPROM_HOST_MAX_LEN + 10 +#define EEPROM_FX_PhaseY EEPROM_HOST_MAX_LEN + 11 +#define EEPROM_FX_PhaseZ EEPROM_HOST_MAX_LEN + 12 + +#define EEPROM_mainDelayNew EEPROM_HOST_MAX_LEN + 13 + + +// ------------------- array of parameters ------------- (the order of defines matter) + +#define NUMBER_OF_PARAM 10 + +#define H_GRAD_BEG 0 +#define S_GRAD_BEG 1 +#define V_GRAD_BEG 2 +#define H_GRAD_END 3 +#define S_GRAD_END 4 +#define V_GRAD_END 5 +#define PERIOD 6 +#define PHASE_X_INC 7 +#define PHASE_Y_INC 8 +#define PHASE_Z_INC 9 + +float FX_Arr[ NUMBER_OF_PARAM ] = {}; +float FX_ArrNew[ NUMBER_OF_PARAM ] = {}; +float FX_Coeff[ NUMBER_OF_PARAM ] = {}; +float Phase[ 3 ] = {}; + +float FX_CoeffMax = 1.0; + +#define PHASE_X 0 +#define PHASE_Y 1 +#define PHASE_Z 2 + + +// ------------------- server settings ----------------- + +#define HOST_DEFAULT "im_serial_" +#define NTP_SERVER "europe.pool.ntp.org" + +char host[ EEPROM_HOST_MAX_LEN ]; + +// const char* ssid = "axod_ap"; +// const char* password = "superstrongpassword" + +const char* ssid = IM_WIFI_SSID; +const char* password = IM_WIFI_PASS; + +ESP8266WebServer server(80); + +String hostDefURI; +String HTTPOut; +String HTTPErr; + +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, NTP_SERVER , 0, 60000); + +// ---------------- EEPROM String r/w ------------------ + +void EEPROMStrRead( uint8_t addr, char * str ){ + for (uint8_t a = addr; a < EEPROM_HOST_MAX_LEN; a++ ){ + str[ a ] = EEPROM.read( a ); + if (str[ a ] == 0) break; + } +} + +void EEPROMStrWrite( uint8_t addr, char * str ){ + uint8_t a = 0; + for (a = addr; a < EEPROM_HOST_MAX_LEN; a++ ){ + EEPROM.write( a, str[ a ] ); + if (str[ a ] == 0) break; + } + EEPROM.write( a, 0 ); +} + +// ---------------- misc ------------------------------- + +int str2HEX(const String str) { + return strtol( str.c_str(), 0, 16 ); +} + +String printNum(unsigned long num, int base, char sign) { + + char outbuf[12]; + + int i = 12; + int j = 0; + int n; + + do { + outbuf[i] = "0123456789ABCDEF"[num % base]; + i--; + num = num/base; + } while ( num > 0 ); + + if ( sign != ' ' ){ + outbuf[0] = sign; + ++j; + } + + while ( ++i < 13 ){ + outbuf[j++] = outbuf[i]; + } + + if ( j == 1 ) { outbuf[ 1 ] = outbuf[ 0 ]; outbuf[ 0 ] = '0'; ++j; } + + outbuf[j] = 0; + + return outbuf; + +} + +uint8_t is_number( const char *s ){ + + while (*s) { + Serial.printf("s: %c\n", *s); + if ( *s++ <48 || *s >57 ) return 0; + } + return true; +} + +// ----------- explode for selected substring ----------- + +String expld( String str, unsigned int numb, char delimiter ){ + + unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0; + + lng = str.length(); + + for ( a = 0; a < lng; a++ ){ + if ( str.charAt( a ) == delimiter ) { + p2 = p1; + p1 = a; + if ( cnt == numb ) break; + cnt ++; + } + } + + if ( cnt < numb ) return ""; + + if ( a == lng ) { + p2 = p1; + p1 = lng; + } + + if ( numb > 0 ) p2 ++; + + return str.substring(p2, p1); + +} + +// ---------------- FX --------------------------------- + +void FX_Gradient_RT( uint8_t HgradBegin, uint8_t SgradBegin, uint8_t VgradBegin, uint8_t HgradEnd, uint8_t SgradEnd, uint8_t VgradEnd, uint8_t period, uint8_t phaseX, uint8_t phaseY, uint8_t phaseZ ) { + + unsigned int i = 0; + CHSV color; + + #define Y_PHASE_SHIFT 128 // 128 // to avoid solid fill on phase = 0 + + // NOTE! phaseZ is for color component only since it is cyclic + + for (i = 0; i < numberOfLEDs; i++) { + +// newHueColor = pow( (float)sin( (float)i*PI / (period * 2) ) ,2 ) * (gradEnd - gradBegin) + gradBegin; // MEMO! floating point + + if ( HgradEnd != HgradBegin ) color.h = ( ((int)cos8( (i + phaseX) * 128 / period ) - 128) * ((int)sin8( (phaseY + Y_PHASE_SHIFT) * 128 / period ) - 128) / 128 + 128 ) / ( 256 / ( HgradEnd - HgradBegin ) ) + HgradBegin; else color.h = HgradEnd; + if ( SgradEnd != SgradBegin ) color.s = ( ((int)cos8( (i + phaseX) * 128 / period ) - 128) * ((int)sin8( (phaseY + Y_PHASE_SHIFT) * 128 / period ) - 128) / 128 + 128 ) / ( 256 / ( SgradEnd - SgradBegin ) ) + SgradBegin; else color.s = SgradEnd; + if ( VgradEnd != VgradBegin ) color.v = ( ((int)cos8( (i + phaseX) * 128 / period ) - 128) * ((int)sin8( (phaseY + Y_PHASE_SHIFT) * 128 / period ) - 128) / 128 + 128 ) / ( 256 / ( VgradEnd - VgradBegin ) ) + VgradBegin; else color.v = VgradEnd; + +// LEDStripe_RGB[ i ] = CHSV( color.h + phaseZ , color.s, color.v ); + + color.h += phaseZ; + hsv2rgb_spectrum(color, LEDStripe_RGB[ i ]); + + } +} + +// ----------------------------------------------------- +// --------------- Init -------------------------------- +// ----------------------------------------------------- + +void setup(void) { + + HTTPOut.reserve(800); // lenght of Help message generally + + hostDefURI.reserve( EEPROM_HOST_MAX_LEN ); + hostDefURI = HOST_DEFAULT + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00 + + // calculate lookup array + +// for (unsigned int a = 1; a <= 255; a++) EXP[ a ] = round( pow( 255, (double)a / (255) ) ); // calculate exponential lookup array (for PWM etc..) + + // reset FX_Coeff (maybe not required) + + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_Coeff[ a ] = 1.0; + +#if SERIAL == 1 + + // Serial init + + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch..."); +#endif + + // read parameters from EEPROM (if it has been saved early) + + EEPROM.begin( EEPROM_HOST_MAX_LEN + EEPROM_OTHER_MAX_LEN ); + + if ( EEPROM.read( EEPROM_FLAG ) == 1 ){ + + EEPROMStrRead( EEPROM_HOST, host ); + + numberOfLEDs = EEPROM.read( EEPROM_numberOfLEDs ); + mainDelayNew = EEPROM.read( EEPROM_mainDelayNew ); + + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_ArrNew[ a ] = EEPROM.read( EEPROM_HgradBegin + a ); + + changeColor = true; + + } else { + + FX_ArrNew[ PERIOD ] = DEF_NUM_LEDS; + strcpy( host, (char*)hostDefURI.c_str() ); // default URI and host name + } + + // Fast LED RGB init + + pinMode(ENABLE_GATE_PIN, OUTPUT); + digitalWrite( ENABLE_GATE_PIN, HIGH ); + + delay(1000); // 2000 // sanity check delay - allows reprogramming if accidently blowing power w/leds + + FastLED.addLeds( LEDStripe_RGB, numberOfLEDs ); + fill_solid( LEDStripe_RGB, numberOfLEDs, CRGB(0, 0, 0) ); + FastLED.show(); + + // WiFi init + + if ( host == "" ) strcpy( host, (char*)hostDefURI.c_str() ); // if it was changed without parameters by mistake + + WiFi.hostname( host ); + +// WiFi.softAP(APssid, APpassword); +// WiFi.mode(WIFI_AP); +// WiFi.mode(WIFI_AP_STA); + + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect( true ); + WiFi.begin(ssid, password); + + // main events handlers + + if (WiFi.waitForConnectResult() == WL_CONNECTED) { + + MDNS.begin( host ); + + // get time + +// timeClient.setTimeOffset(0); + timeClient.begin(); + timeClient.update(); + + // start server + + server.begin(); + MDNS.addService("http", "tcp", 80); + +#if SERIAL == 1 + Serial.printf("Ready. Try \"curl %s/help\" to see the list of commands and parameters.\n", host); +#endif + + server.onNotFound( []() { + + server.sendHeader("Connection", "close"); + + param = ""; +// HTTPOut = ""; + + // no command given (see handlers of commands below) + + if ( server.uri() != "/" )HTTPErr += "Command '" + server.uri() + "' not exist.\n"; + + + for ( uint8_t i=0; i < server.args(); i++ ){ + + paramCnt = 0; + + // ------------- specific parameters ----------------- + + // change number of leds in stripe + + if ( server.argName(i) == "number" ) { + + // TODO make it in MAIN with dimming + + fill_solid( LEDStripe_RGB, numberOfLEDs, CRGB(0, 0, 0) ); + FastLED.show(); + + numberOfLEDs = server.arg("number").toInt(); + FastLED.addLeds( LEDStripe_RGB, numberOfLEDs ); + + paramCnt++; + } + + // color gradient (hex) + + if ( server.argName(i) == "gradient" ) { + + param = server.arg("gradient"); + + for ( uint8_t a = 0; a < 6; a++ ) FX_ArrNew[ H_GRAD_BEG + a ] = str2HEX( param.substring( a*2, a*2+2) ); + + changeColor = true; + paramCnt++; + + } + + // sin/cos period (dec) + + if ( server.argName(i) == "period" ) { + + FX_ArrNew[ PERIOD ] = server.arg("period").toInt(); + + changeColor = true; + paramCnt++; + } + + + // pattern motion parameters (dec) + + if ( server.argName(i) == "slidex" ) { + FX_ArrNew [ PHASE_X_INC ] = (float)server.arg("slidex").toInt(); + changeColor = true; + paramCnt++; + } + + if ( server.argName(i) == "slidey" ) { + FX_ArrNew [ PHASE_Y_INC ] = (float)server.arg("slidey").toInt(); + changeColor = true; + paramCnt++; + } + + if ( server.argName(i) == "slidez" ) { + FX_ArrNew [ PHASE_Z_INC ] = (float)server.arg("slidez").toInt(); + changeColor = true; + paramCnt++; + } + + + // delay between frames + + if ( server.argName(i) == "delay" ) { + mainDelayNew = server.arg("delay").toInt(); +// changeColor = true; + paramCnt++; + } + + // ------------- common parameters ------------------- + + // debug data {Number R G B H S V} + + if ( server.argName(i) == "debug" ){ // || param != 0 + + HTTPOut += "Stripe [debug]: "; + + for (unsigned int i = 0; i < numberOfLEDs; i++) { + color_HSV = rgb2hsv_approximate( LEDStripe_RGB[ i ] ); + HTTPOut += String( i ) + "\t" + String( LEDStripe_RGB[ i ].r ) + "\t" + String( LEDStripe_RGB[ i ].g ) + "\t" + String( LEDStripe_RGB[ i ].b ) + "\t" + String( color_HSV.h ) + "\t" + String( color_HSV.s ) + "\t" + String( color_HSV.v ) + "\n"; + } + + paramCnt++; + } + + // set time + + if ( server.argName(i) == "time" ){ // || param != 0 + + if ( is_number( server.arg("time").c_str() ) ) { + timeClient.update(); + timeStamp = strtol( server.arg("time").c_str(), 0, 10 ); + } else { + HTTPErr += "Parameter 'time' must be specified in UNIX timestamp format.\n"; // 1631848103 + } + + paramCnt++; + } + + // rename host + + if ( server.argName(i) == "host" ) { + + if ( server.arg("host") == "" ) { + + HTTPErr += "Please specify new unit hostname.\n"; + + } else if ( server.arg("host").length() > EEPROM_HOST_MAX_LEN ) { + + HTTPErr += "Unit hostname can not exceed " + String( EEPROM_HOST_MAX_LEN ) + " symbols.\n"; + + } else { + + strcpy( host, (char*)server.arg("host").c_str() ); // host = (char *)server.arg("host").c_str(); + WiFi.hostname( host ); + MDNS.begin( host ); + + HTTPOut += "The host name has been changed to '" + String( host ) + "'. Do not forget to save current settings using the '/save' command.\n"; + } + + paramCnt++; + } + + // save all parameters to EEPROM + + if (server.argName(i) == "save" ){ + + EEPROM.write( EEPROM_numberOfLEDs, numberOfLEDs ); + EEPROM.write( EEPROM_mainDelayNew, mainDelayNew ); + + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) EEPROM.write( EEPROM_HgradBegin + a, (uint8_t)roundf( FX_Arr[ a ] ) ); + + EEPROMStrWrite( EEPROM_HOST, host ); + EEPROM.write( EEPROM_FLAG, 1 ); // EEPROM changed + EEPROM.commit(); + + HTTPOut += "The current settings are saved.\n"; + + paramCnt++; + } + + // reset to default parameters + + if ( server.argName(i) == "reset" ){ + + numberOfLEDs = DEF_NUM_LEDS; + mainDelayNew = DEF_MAIN_DELAY; + + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_ArrNew[ a ] = 0; + + FX_Arr[ PERIOD ] = DEF_NUM_LEDS; + FX_ArrNew[ PERIOD ] = DEF_NUM_LEDS; + + strcpy( host, (char*)hostDefURI.c_str() ); + WiFi.hostname( host ); + + server.begin(); + + if ( quiet > 1 ) HTTPOut += "All parameters are set to default values. Don't forget to /save them.\n"; + + paramCnt++; + + changeColor = true; + + } + + // reboot + + if ( server.argName(i) == "reboot" ){ + + FX_ArrNew[ V_GRAD_BEG ] = 0; + FX_ArrNew[ V_GRAD_END ] = 0; + + mainDelayNew = DEF_MAIN_DELAY; + + if ( quiet > 1 ) { + + server.send_P(200, "text/plain", PSTR("Unit rebooting...\n") ); + server.begin(); + } + + paramCnt++; + + changeColor = true; + commandReboot = true; + } + + // quiet, level of output + + if ( server.argName(i) == "quiet" ) { + + quiet = str2HEX( server.arg("quiet") ); + paramCnt++; + } else { + quiet = FULL_OUTPUT; + } + + // json on + + if ( server.argName(i) == "json" ) { + JSONon = true; + paramCnt++; + } + + if ( !paramCnt ) HTTPErr += "Parameter '" + server.argName(i) + "' not exist.\n"; + } + + + // ---------------- host default return ------------------------ + + if (HTTPErr != "" ) { + + // list of errors + + String Err = ""; + + paramCnt = 0; + + if (quiet > 0) { + + if ( JSONon ) { + + HTTPOut += "{["; + + while ( 1 ){ + Err = expld( HTTPErr, paramCnt, '\n' ); + if ( Err != "" ) HTTPOut += "\"" + Err + "\""; else break; + if ( expld( HTTPErr, paramCnt+1, '\n' ) != "" ) HTTPOut += ","; + paramCnt ++; + } + + HTTPOut += "]}"; + + } else { + + while ( 1 ){ + Err = expld( HTTPErr, paramCnt, '\n' ); + if ( Err != "" ) HTTPOut += "Error: " + Err + "\n"; else break; + paramCnt ++; + } +// HTTPOut += "See '/help' for details\n"; + } + } + + } else { + + // standart output + + if ( quiet > 1 && HTTPOut == "" ) { + + if ( JSONon ){ + + HTTPOut = "{"; + HTTPOut += "\"MAC\":\"" + String( WiFi.macAddress() ) + "\",\n"; + HTTPOut += "\"Host\":\"" + String( host ) + "\",\n"; + HTTPOut += "\"Timestamp\":\"" + String( timeClient.getEpochTime() ) + "\",\n"; + HTTPOut += "\"Target_timestamp\":\"" + String( timeStamp ) + "\",\n"; + + HTTPOut += "\"TNumber_of_LEDs\":\"" + String( numberOfLEDs ) + "\",\n"; + + HTTPOut += "\"SlideX:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_X_INC ] ) ) + "\",\n"; + HTTPOut += "\"SlideY:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_Y_INC ] ) ) + "\",\n"; + HTTPOut += "\"SlideZ:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_Z_INC ] ) ) + "\",\n"; + + HTTPOut += "\"Period:\":\"" + String( (int)roundf( FX_ArrNew[ PERIOD ] ) ) + "\",\n"; + HTTPOut += "\"Delay:\":\"" + String( mainDelayNew ) + "\",\n"; + + HTTPOut += "\"Gradient_HSV:\":\""; + + for ( uint8_t a = 0; a < 6 ; a++ ) HTTPOut += String( printNum( FX_ArrNew[ a ], 16, ' ') ); + + HTTPOut += "\",\n"; + + HTTPOut += "}"; + + } else { + + HTTPOut = "MAC:" + String( WiFi.macAddress() ) + "\n"; + HTTPOut += "Host:" + String( host ) + "\n"; + HTTPOut += "Timestamp:" + String( timeClient.getEpochTime() ) + "\n"; + HTTPOut += "Target timestamp:" + String( timeStamp ) + "\n"; + + HTTPOut += "Number of LEDs:" + String( numberOfLEDs ) + "\n"; + + HTTPOut += "Slide X:" + String( (int)roundf( FX_ArrNew[ PHASE_X_INC ] ) ) + "\n"; + HTTPOut += "Slide Y:" + String( (int)roundf( FX_ArrNew[ PHASE_Y_INC ] ) ) + "\n"; + HTTPOut += "Slide Z:" + String( (int)roundf( FX_ArrNew[ PHASE_Z_INC ] ) ) + "\n"; + + HTTPOut += "Period:" + String( (int)roundf( FX_ArrNew[ PERIOD ] ) ) + "\n"; + HTTPOut += "Delay:" + String( mainDelayNew ) + "\n"; + + HTTPOut += "Gradient (HSV):"; + + for ( uint8_t a = 0; a < 6 ; a++ ) HTTPOut += String( printNum( roundf( FX_ArrNew[ a ] ), 16, ' ') ) + " "; + + HTTPOut += "\n"; + + } + } + } + + server.send(200, "text/plain", HTTPOut ); + + HTTPOut = ""; + HTTPErr = ""; + JSONon = 0; + + }); + + // ui interface TODO xml inteface + + server.on("/ui", HTTP_GET, []() { + HTTPOut = "User interface. Not yet implemented.\n"; + server.send(200, "text/plain", HTTPOut); + }); + + // help + + server.on("/help", HTTP_GET, []() { + + HTTPOut = "Interplay Medium ESP8266 Serial LED Controller. Version: " + String(VERSION) + "\n"; + HTTPOut += "Created by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later . \n\n"; + + HTTPOut += "Parameters:\n"; + + HTTPOut += " ?number= Number of LEDs in stripe (max MAX_NUM_LEDS)\n"; + HTTPOut += " ?gradient= Start and end colors of gradient (hex, 24bit/color in HLS format, e.g. 001122FFEECC)\n"; + HTTPOut += " ?period= Sine period (number of leds)\n"; + HTTPOut += " ?delay= Delay of state changing, integer\n"; + + HTTPOut += " ?slidex= Step of X shift per cycle, signed integer\n"; + HTTPOut += " ?slidey= Step of Y shift per cycle, signed integer\n"; + HTTPOut += " ?slidez= Step of Z shift per cycle, signed integer\n"; + + HTTPOut += " ?time= Target timestamp (when the changes will take effect, good for synch), decimal UNIX Timestamp\n\n"; + + HTTPOut += " ?host= Rename the unit\n"; + HTTPOut += " ?quiet= Level of output (0/empty = no output, 1 = OK/Error)\n\n"; + + HTTPOut += "Commands:\n"; + + HTTPOut += " ?save Save current settings\n"; + HTTPOut += " ?reset Reset to initial settings\n"; + HTTPOut += " ?reboot Reboot unit\n"; + + HTTPOut += " \n"; + HTTPOut += " /ui Output in XML UI format\n"; + HTTPOut += " /json Output in JSON format\n"; + HTTPOut += " /update Wireless update of firmware (see example below)\n"; + HTTPOut += " /help This help\n\n"; + + HTTPOut += "Usage: curl " + String(host) + "?=\n"; + HTTPOut += " curl " + String(host) + "/\n"; + HTTPOut += "Examples: curl \"" + String(host) + "?gradient=ffffffddff00&period=30\" \n"; + HTTPOut += " curl -F image=@firmware.bin " + String(host) + "/update \n"; + + server.sendHeader("Connection", "close"); + server.send( 200, "text/plain", HTTPOut ); + + }); + + // firmware update + + server.on("/update", HTTP_POST, []() { + + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + + ESP.restart(); + + }, []() { + + HTTPUpload& upload = server.upload(); + + if (upload.status == UPLOAD_FILE_START) { + +#if SERIAL == 1 + Serial.setDebugOutput(true); +#endif + WiFiUDP::stopAll(); + +#if SERIAL == 1 + Serial.printf("Update: %s\n", upload.filename.c_str()); +#endif + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + + if (!Update.begin(maxSketchSpace)) { //start with max available size +#if SERIAL == 1 + Update.printError(Serial); +#endif + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { +#if SERIAL == 1 + Update.printError(Serial); +#endif + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + + server.sendHeader("Connection", "close"); + server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") ); +#if SERIAL == 1 + Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize); +#endif + } else { + server.sendHeader("Connection", "close"); + server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") ); +#if SERIAL == 1 + Update.printError(Serial); +#endif + } +#if SERIAL == 1 + Serial.setDebugOutput(false); +#endif + } + + yield(); + }); + + } else { +#if SERIAL == 1 + Serial.println("WiFi Failed"); +#endif + } +} + +// ----------------------------------------------------- +// ------------------- MAIN ---------------------------- +// ----------------------------------------------------- + +// uint8_t rebootColorsCnt = 0; + +void loop(void) { + + server.handleClient(); + MDNS.update(); + + // reconnect + + if ( WiFi.status() != WL_CONNECTED ) { +#if SERIAL == 1 + Serial.printf("."); +#endif + if (WiFi.waitForConnectResult() == WL_CONNECTED) { +#if SERIAL == 1 + Serial.println("Reconnected"); +#endif + MDNS.begin( host ); + + timeClient.begin(); + timeClient.update(); + } + + } + + if ( changeColor ) { + + // set float coefficents + + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) { + FX_Coeff[ H_GRAD_BEG + a ] = (float)FX_ArrNew[ H_GRAD_BEG + a ] - FX_Arr[ H_GRAD_BEG + a ]; + if ( abs( FX_Coeff[ H_GRAD_BEG + a ] ) > FX_CoeffMax ) FX_CoeffMax = abs( FX_Coeff[ H_GRAD_BEG + a ] ); + } + + for ( uint8_t a = 0; a < 6; a++ ) FX_Coeff[ H_GRAD_BEG + a ] = FX_Coeff[ H_GRAD_BEG + a ] / FX_CoeffMax; + + changeColor = false; + +#if SERIAL == 1 + Serial.printf("FX_ArrNew: "); + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_ArrNew[ a ] ); + Serial.printf("\n"); + Serial.printf("FX_Coeff: "); + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_Coeff[ a ] ); + Serial.printf("\n"); +#endif + + } else { + + if ( ++mainDelay == mainDelayNew /* && FX_Delay != 0 */ ) { + +#if SERIAL == 1 +// Serial.printf("FX_Arr: "); +// for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_Arr[ a ] ); +// Serial.printf("\n"); +// Serial.printf("Phase: %f %f %f\n", Phase[ PHASE_X ], Phase[ PHASE_Y ], Phase[ PHASE_Z ] ); +#endif + + mainDelay = 0; + + // fade out and reboot + + if ( commandReboot && roundf( FX_Arr[ V_GRAD_BEG ] ) == 0 && roundf( FX_Arr[ V_GRAD_END ]) == 0 ) { + commandReboot = false; +#if SERIAL == 1 + Serial.printf("Reboot......\n"); +#endif + ESP.restart(); +// Serial.printf("Reboot.2...\n"); + } + + if ( timeClient.getEpochTime() > timeStamp ) { + + // change color + + for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) { + if ( FX_ArrNew [ a ] != roundf( FX_Arr[ a ] ) ) { FX_Arr[ a ] += FX_Coeff[ a ]; } else FX_Arr[ a ] = (float)FX_ArrNew [ a ]; + } + + // change phase + + for ( uint8_t a = 0; a < 3; a++ ) { + + if ( FX_Arr[ PHASE_X_INC + a ] == 0 ) { + // shift phase to 0 after slide. = 0 + if ( (uint8_t)round( Phase[ a ] ) != 0 ) Phase[ a ] -= FX_Coeff[ PHASE_X_INC + a ]/100; else Phase[ a ] = 0; + } else { + // routine shift phase + Phase[ a ] += FX_Arr[ PHASE_X_INC + a ]/100; + } + } + + } + + if ( FX_Arr[ PERIOD ] == 0 ) FX_Arr[ PERIOD ] = 1; + + FX_Gradient_RT( roundf( FX_Arr[ H_GRAD_BEG ] ), roundf( FX_Arr[ S_GRAD_BEG ] ), roundf( FX_Arr[ V_GRAD_BEG ] ), roundf( FX_Arr[ H_GRAD_END ] ), roundf( FX_Arr[ S_GRAD_END ] ), roundf( FX_Arr[ V_GRAD_END ] ), roundf( FX_Arr[ PERIOD ] ), roundf( Phase[ PHASE_X ] ), roundf( Phase[ PHASE_Y ] ), roundf( Phase[ PHASE_Z ] ) ); + FastLED.show(); + delay(3); + + } + } +} diff --git a/Serial_RGB/version.c b/Serial_RGB/version.c new file mode 100644 index 0000000..1438826 --- /dev/null +++ b/Serial_RGB/version.c @@ -0,0 +1 @@ +#define VERSION "10.05.24" diff --git a/bidirectonal-mosfet-level-shifter.png b/bidirectonal-mosfet-level-shifter.png new file mode 100644 index 0000000..469ace3 Binary files /dev/null and b/bidirectonal-mosfet-level-shifter.png differ diff --git a/make b/make new file mode 100755 index 0000000..856ac1c --- /dev/null +++ b/make @@ -0,0 +1,45 @@ +#!/bin/bash + +if [ $# -lt 1 ]; then + echo "Usage: $0 [upload]" + exit 0 +fi + +# assign WIFI_SSID and WIFI_PASS in external file ../info +# source "../info" + +SKETCH="$1/$1.ino" + +TMP="/tmp/ESPcompile.tmp" + +# MAKE_FILE="/home/dmitry/Bin/SDK/ESP/makeEspArduino.21.02.22/makeEspArduino.mk" +MAKE_FILE="/home/dmitry/Bin/SDK/ESP/makeEspArduino/makeEspArduino.mk" +# ESP_SDK_ROOT=/home/dmitry/Bin/SDK/ESP/esp8266 # keep it without quotation marks + +# ESP_SDK_ROOT=~/Bin/SDK/ESP/Arduino +# ESP_SDK_ROOT=~/Bin/SDK/ESP/esp8266_3.1.2 +# ESP_SDK_ROOT=~/Bin/SDK/ESP/esp8266_3.0.2 +ESP_SDK_ROOT=~/Bin/SDK/ESP/esp8266_2.5.0 + + +# LIBS=/home/dmitry/Bin/SDK/ESP/esp8266/libraries +# USER_SRC_PATTERN="version.h" + +DATE=$(date +"%d.%m.%y") +echo "#define VERSION \"$DATE\"" > "$1/version.c" + +# nodemcuv2, generic, esp8285 +make -f "$MAKE_FILE" ESP_ROOT="$ESP_SDK_ROOT" F_CPU=160000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2 2>&1 | tee "$TMP" + +if [ -s "$TMP" ]; then + binSRC=$( cat "$TMP" | grep Linking | sed -e 's/Linking //g' ) + cp "$binSRC" . + + serialPort=$(cat "$TMP" | grep "opening port" | awk '{split($0,a," "); print a[3]}') +else + serialPort=/dev/ttyUSB0 +fi + + + + diff --git a/serial b/serial new file mode 100755 index 0000000..679915c --- /dev/null +++ b/serial @@ -0,0 +1,32 @@ +#!/bin/bash + +if [ -s "$TMP" ]; then + binSRC=$( cat "$TMP" | grep Linking | sed -e 's/Linking //g' ) + cp "$binSRC" . + + serialPort=$(cat "$TMP" | grep "opening port" | awk '{split($0,a," "); print a[3]}') +else + serialPort=/dev/ttyUSB0 +fi + +echo "Serial: $serialPort" + + +if cat "$TMP" | grep -q 'error'; then + echo "exit" +else + if [ "$1" != "noserial" ]; then + + echo "Connecting $serialPort" + + stty -F $serialPort cs8 cstopb -ixon raw speed 115200 + + while [ 1 ]; do + cat $serialPort + sleep 1 + done + else + echo "Serial terminal omited" + fi +fi +