ws2812-achterbahn/src/main.cpp

685 lines
20 KiB
C++

//flash as Wemos D1 R2 & mini
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#include <EEPROM.h>
#define EEPROMSTARTADDRESS_HEIGHTMAP 8
#include <vector>
#include "wagon.h"
#include "effect.h"
#include "fx_scanner.h"
#include "fx_flash.h"
//#include "fx_shootingstar.h"
void resetHeightmap();
void printHeightmapRaw();
void interpolateHeightValues();
void previewHeightmap(int waittime);
void allBlack(int waittime);
void spawnWagon();
void spawnWagon(float pos, float wagonlength,float startvel, float startacc, float mass, uint8_t wheelcolor);
void checkSerial();
void loop_configmode();
void loop_achterbahn();
void removeAllWagons();
uint32_t Wheel(byte WheelPos);
void loadHeightmapRaw();
void saveHeightmapRaw();
#define PIN_LDR A0 //comment if not used
#define PIN D4
#define NUMPIXELS 600
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
long lastPixelUpdate=0;
#define PIXELUPDATETIME 20
long lastRoutineUpdate=0;
#define ROUTINEUPDATETIME 20
long lastCheckspawn=0;
#define CHECKSPAWNDELAY 2000 //delay in ms to check random spawn
#define SPAWNCHANCE 10 //1 out of x times wagon will spawn
#define SPAWNCHANCEDOUBLE 5 //change of spawning a two trains simultaneously
long lastCheckspawnEffect=0;
#define CHECKSPAWNDELAY_EFFECT 5000 //delay in ms to check random effect
#define SPAWNCHANCE_EFFECT_SCANNER 1000 //1 out of x times spawn effect
#define SPAWNCHANCE_EFFECT_FLASH 1000 //1 out of x times spawn effect
#define SPAWNCHANCE_EFFECT_SHOOTINGSTAR 100 //1 out of x times spawn effect
#define BRIGHTNESS_RUN 200 //max brightness
//#define BRIGHTNESS_RUN_MIN 20
#define BRIGHTNESS_RUN_MIN 200
#define BRIGHTNESS_DEBUG 150
#define LDR_MIN 700
#define LDR_MAX 150
unsigned long last_ldrread=0;
long loopmillis=0;
uint8_t height[NUMPIXELS];
uint8_t heightraw[NUMPIXELS]; //uninterpolated values
#define MAXHEIGHT 254
std::vector <Wagon> wagon_arr;
uint8_t maxid=0;
bool configmode=false;
int selectedpixel=-1; //-1 = none
uint8_t selectpixelmode=0;
uint8_t wagoncount=0;
Effect* effect = NULL;
//Persistance effects
#define PERSMODE_BLACK 0
#define PERSMODE_FADEPERCENT 1
#define NUM_PERSMODE 2 //number of available persistancemodes
uint8_t persistancemode=PERSMODE_BLACK;
long last_changePersistancemode=0;
#define PERSISTANCEMODECHANGE_DELAY 180000
//define config
//#define RESPAWNWAGON
#define MAXWAGONS 5 //maximum number of wagons
void setup() {
Serial.begin(115200);
#ifdef PIN_LDR
pinMode(PIN_LDR, INPUT);
#endif
EEPROM.begin(4096); //set eeprom size, between 4 and 4096 bytes.
strip.begin();
strip.setBrightness(BRIGHTNESS_RUN); //150
strip.show(); // Initialize all pixels to 'off'
Serial.println("Started");
resetHeightmap();
//fixed heightmap
loadHeightmapRaw();
interpolateHeightValues();
/*
Serial.println();
for (int i=0;i<NUMPIXELS;i++){
Serial.print(i);
Serial.print(": ");
Serial.println(height[i]);
}*/
//previewHeightmap(2000);
//spawnWagon();
spawnWagon();
}
void resetHeightmap(){
for (int i=0;i<NUMPIXELS;i++){
heightraw[i]=255; //255 means value need to be interpolated
}
heightraw[0]=0;
heightraw[NUMPIXELS-1]=0;
}
void printHeightmapRaw() {
Serial.println();
for (int i=0;i<NUMPIXELS;i++){
if (heightraw[i]!=255){
Serial.print("heightraw[");
Serial.print(i);
Serial.print("]=");
Serial.print(heightraw[i]);
Serial.println(";");
delay(10);
}
}
}
void interpolateHeightValues(){
for (int i=0;i<NUMPIXELS;i++){ //copy heightraw to height
height[i]=heightraw[i];
}
//interpolate every part with height value 255
for (int interpolateStartpos=0;interpolateStartpos<NUMPIXELS-1;interpolateStartpos++){
if (height[interpolateStartpos]==255){ //interpolation part starts
int interpolateEndpos=interpolateStartpos+1;
while (interpolateEndpos<NUMPIXELS && height[interpolateEndpos]==255){
interpolateEndpos++;
}
interpolateEndpos--;
//interpolateStartpos index of first 255 value
//interpolateEndpos index of last 255 value
uint8_t interpolateStartvalue=height[interpolateStartpos-1];
uint8_t interpolateEndvalue=height[interpolateEndpos+1];
int interpolateLength=interpolateEndpos-interpolateStartpos+1; //one 255 element -> length 1
float interpolateStep= ((int)(interpolateEndvalue)-(int)(interpolateStartvalue))*1.0 /(interpolateLength+1);
Serial.println();
Serial.print("interpolateStep=");
Serial.print("(");
Serial.print(interpolateEndvalue);
Serial.print("-");
Serial.print(interpolateStartvalue);
Serial.print(")/");
Serial.print(interpolateLength+1);
Serial.print("=");
Serial.println(interpolateStep);
int interpolateStepCounter=1;
Serial.println();
Serial.print("interpolateStartpos=");
Serial.println(interpolateStartpos);
Serial.print("interpolateEndpos=");
Serial.println(interpolateEndpos);
Serial.print("interpolateStartvalue=");
Serial.println(interpolateStartvalue);
Serial.print("interpolateEndvalue=");
Serial.println(interpolateEndvalue);
Serial.print("interpolateLength=");
Serial.println(interpolateLength);
Serial.print("interpolateStep=");
Serial.println(interpolateStep,6);
for (int setinti=interpolateStartpos;setinti<=interpolateEndpos;setinti++) { //for all coherent elements to interpolate
height[setinti]=height[interpolateStartpos-1]+(int)(interpolateStep*interpolateStepCounter);
/*Serial.print(height[interpolateStartpos-1]);
Serial.print("+(");
Serial.print(interpolateStep);
Serial.print("*");
Serial.print(interpolateStepCounter);
Serial.print(")=");
Serial.println(height[setinti]);*/
interpolateStepCounter++;
}
interpolateStartpos=interpolateEndpos;
}
}
}
void previewHeightmap(int waittime){
for (int i=0;i<NUMPIXELS;i++){
//uint32_t c=Wheel(height[i]*255/45);
uint8_t b=height[i]*255.0/MAXHEIGHT;
//uint32_t c=strip.Color(255-b,b,0);
uint32_t c=Wheel(b/1.2);
if (height[i]==255){
c=strip.Color(0,0,0);
}
strip.setPixelColor(i,c);
}
if (waittime>0){
strip.show();
delay(waittime);
}
}
void allBlack(int waittime){
for (int i=0;i<NUMPIXELS;i++){
uint32_t c=strip.Color(0,0,0);
strip.setPixelColor(i,c);
}
if (waittime>0){
strip.show();
delay(waittime);
}
}
void spawnWagon(){
//Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, 35, 6, 0.5,0); //spawn new wagon
// pos, wagonlength, startvel, startacc, trainmass, wagoncolor
//Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, random(0, 20), random(3,20), random(0.2, 50)/10.0, 0 , random(5,100) , Wheel(random(0,256))); //spawn new wagon
int _randomlength=random(3,40); //Zelt: 3-> minimum vel 10, 40 -> minium vel 30
// pos, wagonlength, startvel , startacc, trainmass, wagoncolor
//Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, random(0, 20), _randomlength, random(map(_randomlength,3,40,1,1), map(_randomlength,3,40, 13,40))/10.0 , 0 , 5 , Wheel((uint8_t)random(0,255))); //spawn new wagon
int side_startpos=0; //start at beginning of strip
int side_multi=1; //start in forward direction
/*if (random(0,2)==0){ //spawn from other side
side_startpos=NUMPIXELS+_randomlength;
side_multi=-1;
}*/
//Force start from end of strip
//side_startpos=NUMPIXELS+_randomlength; //start at end of strip
//side_multi=-1; //start in backward direction
//Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, side_startpos, _randomlength, side_multi*random(map(_randomlength,3,30,0,40)/10.0, map(_randomlength,3,30, 5,60))/10.0 , 0 , random(3.0,7.0) , Wheel((uint8_t)random(0,255))); //spawn new wagon
Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, side_startpos, _randomlength, side_multi*random(map(_randomlength,3,30,30,100)/10.0, map(_randomlength,3,30, 5,60))/10.0 , 0 , 1.8 , Wheel((uint8_t)random(0,255))); //spawn new wagon
//Mass: the lighter the faster is changes speed
//special spawns
if (random(0,50)==0){
tmpr.setType(1); //make rainbow
tmpr.setLength(_randomlength*random(1,3));
}
wagon_arr.push_back(tmpr);
Serial.println("Spawned Wagon");
}
void spawnWagon(float pos, float wagonlength,float startvel, float startacc, float mass, uint8_t wheelcolor){
//Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, 35, 6, 0.5,0); //spawn new wagon
// pos, wagonlength, startvel, startacc, wagonmass, wagoncolor
Wagon tmpr = Wagon(maxid++,NUMPIXELS,&strip, height, pos, wagonlength, startvel, startacc , mass , Wheel(wheelcolor)); //spawn new wagon
wagon_arr.push_back(tmpr);
Serial.println("Spawned Custom Wagon");
}
void loop() {
loopmillis=millis();
#ifdef PIN_LDR
if (millis()-last_ldrread > 10000) {
int _light=analogRead(PIN_LDR);
uint8_t _brightness = constrain(map(_light, LDR_MIN, LDR_MAX, BRIGHTNESS_RUN_MIN, BRIGHTNESS_RUN), BRIGHTNESS_RUN_MIN, BRIGHTNESS_RUN);
//Serial.print("kilian="); Serial.println(_light);
//Serial.print("ergbebinis="); Serial.println(_brightness);
strip.setBrightness(_brightness);
last_ldrread=millis();
}
#endif
checkSerial();
if (configmode){
loop_configmode();
}else{
loop_achterbahn();
}
}
void checkSerial(){
static String serialstring_temp="";
String serialstring="";
while(Serial.available()){
if (Serial.available()>0){
char c = Serial.read();
if (c!='\n') {
serialstring_temp+=c;
Serial.print(c); //echo
}else{
Serial.println(""); //new line
serialstring=serialstring_temp;
serialstring_temp="";
}
}
}
if (serialstring.length()>0) {
Serial.println("String:"+serialstring);
if (serialstring.equals("run")){
strip.setBrightness(BRIGHTNESS_RUN);
configmode=false;
}else if (serialstring.equals("debug")){
strip.setBrightness(BRIGHTNESS_DEBUG);
configmode=true;
selectpixelmode=0;
}else if (serialstring.equals("debug_dark")){
strip.setBrightness(BRIGHTNESS_DEBUG);
configmode=true;
selectpixelmode=1; //show only selected color, everything else off
}else if (serialstring.equals("print")){
printHeightmapRaw();
}else if (serialstring.equals("save")){
saveHeightmapRaw(); //save to eeprom
}else if (serialstring.equals("load")){
loadHeightmapRaw(); //load from eeprom
}else if (serialstring.equals("remove")){
removeAllWagons();
}else if (serialstring.equals("clear")){
resetHeightmap();
interpolateHeightValues();
previewHeightmap(0); //show heightmap
strip.show();
}else if (serialstring.startsWith("spawn=")){
String rest=serialstring.substring(serialstring.indexOf('=')+1); //part after =
int spawnpos=rest.substring(0,rest.indexOf(',')).toInt(); //part to next ,
rest=rest.substring(rest.indexOf(',')+1); //part after ,
int spawnlength=rest.substring(0,rest.indexOf(',')).toInt(); //part to next ,
rest=rest.substring(rest.indexOf(',')+1); //part after ,
int spawnstartvel=rest.substring(0,rest.indexOf(',')).toInt(); //part to next ,
rest=rest.substring(rest.indexOf(',')+1); //part after ,
int spawnstartacc=rest.substring(0,rest.indexOf(',')).toInt(); //part to next ,
rest=rest.substring(rest.indexOf(',')+1); //part after ,
float spawnmass=rest.substring(0,rest.indexOf(',')).toInt()/1000.0; //part to next , //mass in gramm
rest=rest.substring(rest.indexOf(',')+1); //part after ,
int spawncolor=rest.substring(0).toInt(); //part to next ,
Serial.print("spawning ");
Serial.print(spawnpos);
Serial.print(",");
Serial.println(spawnlength);
Serial.print(",");
Serial.println(spawnstartvel); //startvel will be /10
Serial.print(",");
Serial.println(spawnstartacc); //startacc will be /10
Serial.print(",");
Serial.println(spawnmass);
Serial.print(",");
Serial.println(spawncolor);
spawnWagon(spawnpos,spawnlength,spawnstartvel/10.0,spawnstartacc/10.0,spawnmass,spawncolor);
}else if (serialstring.equals("spawn")){
spawnWagon(); //random
}else if (serialstring.startsWith("setpx=")){
String pixelnumberstring=serialstring.substring(serialstring.indexOf('=')+1,serialstring.indexOf(',')); //part between = and ,
String pixelvaluestring=serialstring.substring(serialstring.indexOf(',')+1); //part after ,
int pixelnumber=pixelnumberstring.toInt();
int pixelvalue=pixelvaluestring.toInt();
Serial.print("set pixel ");
Serial.print(pixelnumber);
Serial.print("=");
Serial.println(pixelvalue);
if (pixelnumber>=0 && pixelnumber<NUMPIXELS && pixelvalue>=0 && pixelvalue<=255){
heightraw[pixelnumber]=pixelvalue;
}else{
Serial.println("Error: Value out of range!");
}
interpolateHeightValues();
Serial.println();
for (int i=0;i<NUMPIXELS;i++){
Serial.print(i);
Serial.print(": ");
Serial.print(height[i]);
Serial.print(" (");
Serial.print(heightraw[i]);
Serial.println(")");
}
previewHeightmap(0); //show heightmap
strip.show();
}else if (serialstring.startsWith("px=")){
String pixelnumberstring=serialstring.substring(serialstring.indexOf('=')+1); //part between = and ,
int pixelnumber=pixelnumberstring.toInt();
Serial.print("show pixel ");
Serial.print(pixelnumber);
selectedpixel=pixelnumber;
/* //ignore max and min. all black with px=-1
if (pixelnumber<NUMPIXELS){
selectedpixel=pixelnumber;
}else{
Serial.println("Error: Value too high!");
}*/
}else if (serialstring.startsWith("fx_")){
Serial.println("Effect Scanner");
if (effect!=NULL){
delete effect;
}
if (serialstring.equals("fx_scanner")){
Serial.println("Effect Scanner");
effect=new FX_Scanner(NUMPIXELS,&strip,height,255,-200,strip.Color(100,0,0));
}else if (serialstring.equals("fx_flash")){
Serial.println("Effect Flash");
effect=new FX_Flash(NUMPIXELS,&strip,height,strip.Color(200,200,200));
}/*else if (serialstring.equals("fx_shootingstar")){
Serial.println("Effect Shooting Star");
effect=new FX_ShootingStar(NUMPIXELS,&strip,height);
}*/
}
}
}
void loop_configmode(){
if (lastPixelUpdate+PIXELUPDATETIME<loopmillis){
lastPixelUpdate=loopmillis;
if (selectpixelmode==0) {
//show heightmap as color and selected pixel white
previewHeightmap(0);
if (selectedpixel>=0){
uint32_t c=strip.Color(255,255,255);
strip.setPixelColor(selectedpixel,c);
if (selectedpixel>=1){
uint32_t c=strip.Color(0,0,0);
strip.setPixelColor(selectedpixel-1,c);
}
if (selectedpixel<NUMPIXELS-1){
uint32_t c=strip.Color(0,0,0);
strip.setPixelColor(selectedpixel+1,c);
}
}
}else if(selectpixelmode==1) {
//show only selected pixel as white, everything else off
allBlack(0);
if (selectedpixel>=0 && selectedpixel<NUMPIXELS){
uint32_t c=strip.Color(255,255,255);
strip.setPixelColor(selectedpixel,c);
}
}
strip.show();
}
}
void loop_achterbahn(){
//######################### Update LED Output
if (lastPixelUpdate+PIXELUPDATETIME<loopmillis){
lastPixelUpdate=loopmillis;
for (int i=0;i<NUMPIXELS;i++){ //all black
switch (persistancemode){
case PERSMODE_FADEPERCENT:
{
uint32_t _pxcolor=strip.getPixelColor(i); //get current color of that pixel
uint8_t _pxr = _pxcolor >> 16;
uint8_t _pxg = _pxcolor >> 8;
uint8_t _pxb = _pxcolor;
uint32_t c=strip.Color(_pxr*0.99,_pxg*0.99,_pxb*0.99);
strip.setPixelColor(i,c);
break;
}
case PERSMODE_BLACK: default:
{
uint32_t c=strip.Color(0,0,0);
strip.setPixelColor(i,c);
break;
}
}
}
//possible persistancemode change
if (millis()>last_changePersistancemode+PERSISTANCEMODECHANGE_DELAY) {
if (random(0,10)!=0){
persistancemode = PERSMODE_BLACK;
}else{
persistancemode=random(1,NUM_PERSMODE);
}
last_changePersistancemode = millis();
}
//Wagons
for (std::vector<Wagon>::iterator it = wagon_arr.begin(); it != wagon_arr.end(); ++it) //all wagons
{
Wagon & w = *it;
w.updateGraphics();
}
//Effects
if (effect != NULL ){
if (effect->active()){
effect->updateGraphics();
}
}
strip.show();
}
if (lastRoutineUpdate+ROUTINEUPDATETIME<loopmillis-ROUTINEUPDATETIME){
Serial.println("Behind!");
}
//######################### Update Physics
if (lastRoutineUpdate+ROUTINEUPDATETIME<loopmillis){
lastRoutineUpdate=loopmillis;
wagoncount=0;
for (std::vector<Wagon>::iterator it = wagon_arr.begin(); it != wagon_arr.end(); ++it) //all wagons
{
Wagon & w = *it;
w.updatePhysics(ROUTINEUPDATETIME);
if (!w.alive())
{
it = wagon_arr.erase(it); // After erasing, it is now pointing the next element.
--it;
Serial.println("Killed train");
#ifdef RESPAWNWAGON
spawnWagon(); //spawn new one
#endif
}else{ //wagon is alive
wagoncount++;
}
}
//Effects
if (effect != NULL ){
if (effect->active()){
effect->updateRoutine(ROUTINEUPDATETIME);
}else{ //effect not active anymore
delete effect;
effect=NULL;
}
}
}
//Check spawning
if (lastCheckspawn+CHECKSPAWNDELAY<loopmillis) {
lastCheckspawn=loopmillis;
Serial.print("Checking Spawning, wagons ");
Serial.println(wagoncount);
if (random(0,SPAWNCHANCE)==0 && wagoncount<MAXWAGONS) { //by chance, exclusive SPAWNCHANCE
spawnWagon();
if (random(0,SPAWNCHANCEDOUBLE)==0){
spawnWagon();
}
}
}
//Check Effect Spawning
if (effect==NULL && lastCheckspawnEffect+CHECKSPAWNDELAY_EFFECT<loopmillis) {
lastCheckspawnEffect=loopmillis;
if (random(0,SPAWNCHANCE_EFFECT_SCANNER)==0){
effect=new FX_Scanner(NUMPIXELS,&strip,height,255,-200,strip.Color(100,0,0));
}else if (random(0,SPAWNCHANCE_EFFECT_FLASH)==0){
effect=new FX_Flash(NUMPIXELS,&strip,height,strip.Color(200,200,200));
}/*else if (random(0,SPAWNCHANCE_EFFECT_SHOOTINGSTAR)==0){
effect=new FX_ShootingStar(NUMPIXELS,&strip,height);
}*/
}
}
void removeAllWagons(){
for (std::vector<Wagon>::iterator it = wagon_arr.begin(); it != wagon_arr.end(); ++it) //all wagons
{
Wagon & w = *it;
w.updatePhysics(ROUTINEUPDATETIME);
it = wagon_arr.erase(it); // After erasing, it is now pointing the next element.
--it;
}
}
// 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);
}
void loadHeightmapRaw(){
int c_eepromaddress=EEPROMSTARTADDRESS_HEIGHTMAP;
uint8_t lastvalue=0;
for (int i=0;i<NUMPIXELS;i++){
lastvalue = EEPROM.read(c_eepromaddress);
heightraw[i]=lastvalue;
c_eepromaddress++;
}
interpolateHeightValues();
}
void saveHeightmapRaw(){
int c_eepromaddress=EEPROMSTARTADDRESS_HEIGHTMAP;
for (int i=0;i<NUMPIXELS;i++){
EEPROM.write(c_eepromaddress, heightraw[i]); //address, value
c_eepromaddress++;
}
Serial.println("Saved to EEPROM");
EEPROM.commit(); //write changes to eeprom. EEPROM.end() will also commit and release the ram copy of eeprom contents
}