Will write more, but just posting arduino sketch up for now
Follow me…
Archives
-
Recent Posts
XBOX Gamertag
Will write more, but just posting arduino sketch up for now
Apologies for the slapdash nature of this “How to”. I started with the best of intentions, and set out to create videos documenting everything I’ve done up to this point, after a 10 month break.
The construction of the display is straight forward and this video covers it well. The Fast-SPI explanation I think is fine, but as soon as I started to put together the video for how all the LED/pixel calculations work for the “Matrix” example, I realised I was going to bore the hell out of everyone who watched it.
The problem with code is that is difficult to explain without overcomplicating the matter, and this whole project is stupidly complex already.
So, instead of creating a whole series of step by step videos, I’ve spent quite a few hours annotating all of the code as clearly as I can. This comes with warnings.
There is absolutely no way that you will build exactly the same display that I’ve built, you will most likely build something better. The code will not transplant into your Arduino and work perfectly, there are many reasons for this, the main one being that my web server which is hosting my time and weather scraper, will not be the same as yours and as a result, that whole section will probably not work for you.
The point of me posting up this annotated code is that you can get the general gist of what I’ve done and use the individual functions for you in your own situation.
There are many tips I can give when setting out on a project like this, but these are key.
So here is the Opt300RGBEthAnnPub file
And here is the php file for the WWO and time calculator for your own website ardData
Also the files of the images that I’m reading off my SD card and the myData.txt file that the script it accessing SDCardContents
So what next? For this project, I want to get some weather icons alongside the temperature. I’m thinking about perspex rods to shine the light through in a fibre optic style.
Here for all the search engine indexing spider things, is all of my code…
// Darrenlloyd Gent 2013-2014 // This version 26/06/2014 // Under a whatever, do what you want licence. // Most of this has been cobbled together from various places and I wasn't diligent enough to make notes. // Fastled has been updated since I did all this. http://fastled.io/ // They deserve way more credit. #include //SD Library #include "FastSPI_LED2.h" // FastSPI_LED2 library #include <avr/pgmspace.h> #include // SPI library #include // Ethernet Library //#include // comment out #define NUM_LEDS 300 // Defining the number of LEDs in the strip CRGB leds[NUM_LEDS]; // Create the led Array which will contain the RGB values for each pixel 0 -299 // The offsets for the splash radius. Stored in Progmem as there wasn't enough space to store in normal memory // Each splash 1-10 gets larger and larger needing more pixels.LEDs. // This was much quicker than calculating the radius in realtime. prog_char splash1[4][2] PROGMEM={{0,0},{0,1},{1,0},{1,1}}; prog_char splash2[8][2] PROGMEM = {{0,-1},{0,2},{-1,0},{1,-1},{-1,1},{1,2},{2,0},{2,1}}; prog_char splash3[12][2] PROGMEM = {{0,-2},{0,3},{-1,-1},{1,-2},{-1,2},{1,3},{-2,0},{2,-1},{-2,1},{2,2},{3,0},{3,1}}; prog_char splash4[16][2] PROGMEM = {{0,-3},{0,4},{-1,-2},{1,-3},{-1,3},{1,4},{-2,-1},{2,-2},{-2,2},{2,3},{-3,0},{3,-1},{-3,1},{3,2},{4,0},{4,1}}; prog_char splash5[36][2] PROGMEM = {{0,-4},{0,5},{-1,-3},{1,-4},{-1,4},{-1,-4},{1,5},{-1,5},{-2,-2},{2,-3},{-2,3},{-2,-3},{2,4},{2,-4},{-2,4},{2,5},{-3,-1},{3,-2},{-3,2},{-3,-2},{3,3},{3,-3},{-3,3},{3,4},{-4,0},{4,-1},{-4,1},{-4,-1},{4,2},{4,-2},{-4,2},{4,3},{5,0},{5,1},{5,-1},{5,2}}; prog_char splash6[28][2] PROGMEM = {{0,-5},{0,6},{1,-5},{-1,-5},{1,6},{-1,6},{-2,-4},{2,-5},{-2,5},{2,6},{-3,-3},{3,-4},{-3,4},{3,5},{-4,-2},{4,-3},{-4,3},{4,4},{-5,0},{-5,1},{-5,-1},{5,-2},{-5,2},{5,3},{6,0},{6,1},{6,-1},{6,2}}; prog_char splash7[32][2] PROGMEM= {{0,-6},{0,7},{1,-6},{-1,-6},{1,7},{-1,7},{-2,-5},{2,-6},{-2,6},{2,7},{-3,-4},{3,-5},{-3,5},{3,6},{-4,-3},{4,-4},{-4,4},{4,5},{-5,-2},{5,-3},{-5,3},{5,4},{-6,0},{-6,1},{-6,-1},{6,-2},{-6,2},{6,3},{7,0},{7,1},{7,-1},{7,2}}; prog_char splash8[36][2] PROGMEM= {{0,-7},{0,8},{1,-7},{-1,-7},{1,8},{-1,8},{-2,-6},{2,-7},{-2,7},{2,8},{-3,-5},{3,-6},{-3,6},{3,7},{-4,-4},{4,-5},{-4,5},{4,6},{-5,-3},{5,-4},{-5,4},{5,5},{-6,-2},{6,-3},{-6,3},{6,4},{-7,0},{-7,1},{-7,-1},{7,-2},{-7,2},{7,3},{8,0},{8,1},{8,-1},{8,2}}; prog_char splash9[68][2] PROGMEM= {{0,-8},{0,9},{1,-8},{-1,-8},{1,9},{-1,9},{-2,-7},{2,-8},{-2,8},{-2,-8},{2,9},{-2,9},{-3,-6},{3,-7},{-3,7},{-3,-7},{3,8},{3,-8},{-3,8},{3,9},{-4,-5},{4,-6},{-4,6},{-4,-6},{4,7},{4,-7},{-4,7},{4,8},{-5,-4},{5,-5},{-5,5},{-5,-5},{5,6},{5,-6},{-5,6},{5,7},{-6,-3},{6,-4},{-6,4},{-6,-4},{6,5},{6,-5},{-6,5},{6,6},{-7,-2},{7,-3},{-7,3},{-7,-3},{7,4},{7,-4},{-7,4},{7,5},{-8,0},{-8,1},{-8,-1},{8,-2},{-8,2},{-8,-2},{8,3},{8,-3},{-8,3},{8,4},{9,0},{9,1},{9,-1},{9,2},{9,-2},{9,3}}; prog_char splash10[48][2] PROGMEM= {{0,10},{0,-9},{1,10},{-1,10},{1,-9},{-1,-9},{10,0},{10,1},{10,-1},{10,2},{10,-2},{10,3},{2,10},{-2,10},{2,-9},{-2,-9},{3,10},{-3,-8},{3,-9},{-3,9},{-4,-7},{4,-8},{-4,8},{4,9},{-5,-6},{5,-7},{-5,7},{5,8},{-6,-5},{6,-6},{-6,6},{6,7},{-7,-4},{7,-5},{-7,5},{7,6},{-8,-3},{8,-4},{-8,4},{8,5},{-9,0},{-9,1},{-9,-1},{-9,2},{-9,-2},{9,-3},{-9,3},{9,4}}; // My characters for each ASCII character, stored in ASCII order, hence the first 30 odd blank. // A binary representation of the individual columns is a 10bit interger. // 1024 indicates end of the character so then the updatescroll moves onto the next letter prog_uint16_t myChars[126][8] PROGMEM={ { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 0 , 0 , 0 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 382 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 14 , 0 , 14 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 160 , 496 , 160 , 496 , 160 , 1024 , 1024 , 1024 }, { 288 , 336 , 1016 , 336 , 144 , 1024 , 1024 , 1024 }, { 136 , 64 , 32 , 16 , 136 , 1024 , 1024 , 1024 }, { 118 , 153 , 169 , 70 , 160 , 1024 , 1024 , 1024 }, { 7 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 60 , 66 , 129 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 129 , 66 , 60 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 17 , 10 , 31 , 10 , 17 , 1024 , 1024 , 1024 }, { 32 , 32 , 248 , 32 , 32 , 1024 , 1024 , 1024 }, { 256 , 128 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 32 , 32 , 32 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 256 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 448 , 48 , 14 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 252 , 322 , 290 , 274 , 252 , 1024 , 1024 , 1024 }, //0 { 8 , 4 , 510 , 1024 , 1024 , 1024 , 1024 , 1024 }, //1 { 396 , 322 , 290 , 274 , 268 , 1024 , 1024 , 1024 }, //2 { 258 , 290 , 290 , 290 , 220 , 1024 , 1024 , 1024 }, //3 { 62 , 32 , 32 , 504 , 32 , 1024 , 1024 , 1024 }, //4 { 318 , 290 , 290 , 290 , 194 , 1024 , 1024 , 1024 }, //5 { 252 , 290 , 290 , 290 , 194 , 1024 , 1024 , 1024 }, //6 { 2 , 258 , 194 , 50 , 14 , 1024 , 1024 , 1024 }, //7 { 220 , 290 , 290 , 290 , 220 , 1024 , 1024 , 1024 }, //8 { 28 , 290 , 290 , 290 , 252 , 1024 , 1024 , 1024 }, //9 { 0 , 144 , 0 , 1024 , 1024 , 1024 , 1024 , 1024 }, //: { 256 , 144 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, //; { 32 , 80 , 136 , 1024 , 1024 , 1024 , 1024 , 1024 }, //< { 80 , 80 , 80 , 80 , 1024 , 1024 , 1024 , 1024 }, //= { 136 , 80 , 32 , 1024 , 1024 , 1024 , 1024 , 1024 }, //> { 2 , 1 , 177 , 9 , 6 , 1024 , 1024 , 1024 }, //? { 72 , 180 , 330 , 330 , 50 , 68 , 56 , 1024 }, //@ { 508 , 34 , 34 , 34 , 508 , 1024 , 1024 , 1024 }, //A { 510 , 290 , 290 , 290 , 200 , 1024 , 1024 , 1024 }, //B { 120 , 132 , 258 , 258 , 258 , 1024 , 1024 , 1024 }, //C { 510 , 258 , 258 , 132 , 120 , 1024 , 1024 , 1024 }, //D { 510 , 290 , 290 , 290 , 290 , 1024 , 1024 , 1024 }, //E { 510 , 34 , 34 , 34 , 34 , 1024 , 1024 , 1024 }, //F { 120 , 132 , 258 , 290 , 482 , 1024 , 1024 , 1024 }, //G { 510 , 32 , 32 , 32 , 510 , 1024 , 1024 , 1024 }, //H { 258 , 258 , 510 , 258 , 258 , 1024 , 1024 , 1024 }, //I { 226 , 258 , 254 , 2 , 2 , 1024 , 1024 , 1024 }, //J { 510 , 32 , 80 , 136 , 262 , 1024 , 1024 , 1024 }, //K { 510 , 256 , 256 , 256 , 256 , 1024 , 1024 , 1024 }, //L { 510 , 4 , 24 , 4 , 510 , 1024 , 1024 , 1024 }, //M { 508 , 2 , 2 , 2 , 508 , 1024 , 1024 , 1024 }, //M { 252 , 258 , 258 , 258 , 252 , 1024 , 1024 , 1024 }, //O { 510 , 34 , 34 , 34 , 28 , 1024 , 1024 , 1024 }, //P { 252 , 258 , 386 , 258 , 764 , 1024 , 1024 , 1024 }, //Q { 510 , 34 , 34 , 100 , 408 , 1024 , 1024 , 1024 }, //R { 284 , 290 , 290 , 290 , 194 , 1024 , 1024 , 1024 }, //S { 2 , 2 , 510 , 2 , 2 , 1024 , 1024 , 1024 }, //T { 254 , 256 , 256 , 256 , 254 , 1024 , 1024 , 1024 }, //U { 126 , 128 , 256 , 128 , 126 , 1024 , 1024 , 1024 }, //V { 254 , 256 , 224 , 256 , 254 , 1024 , 1024 , 1024 }, //W { 398 , 80 , 32 , 80 , 398 , 1024 , 1024 , 1024 }, //X { 6 , 24 , 480 , 24 , 6 , 1024 , 1024 , 1024 }, //Y { 386 , 322 , 290 , 274 , 270 , 1024 , 1024 , 1024 }, //Z { 255 , 129 , 129 , 129 , 1024 , 1024 , 1024 , 1024 }, //( { 3 , 12 , 48 , 192 , 1024 , 1024 , 1024 , 1024 }, //backslash { 129 , 129 , 129 , 255 , 1024 , 1024 , 1024 , 1024 }, //) { 4 , 2 , 1 , 2 , 4 , 1024 , 1024 , 1024 }, // { 128 , 128 , 128 , 128 , 128 , 1024 , 1024 , 1024 }, //_ { 1 , 2 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, //` { 112 , 136 , 136 , 136 , 112 , 128 , 1024 , 1024 }, //a { 255 , 136 , 136 , 136 , 112 , 1024 , 1024 , 1024 }, //b { 112 , 136 , 136 , 136 , 1024 , 1024 , 1024 , 1024 },//c { 112 , 136 , 136 , 136 , 127 , 1024 , 1024 , 1024 },//d { 112 , 168 , 168 , 168 , 16 , 1024 , 1024 , 1024 },//e { 8 , 254 , 9 , 9 , 1024 , 1024 , 1024 , 1024 },//f { 112 , 648 , 648 , 648 , 496 , 1024 , 1024 , 1024 },//g { 255 , 8 , 8 , 8 , 240 , 1024 , 1024 , 1024 },//h { 250 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 },//i { 384 , 512 , 506 , 1024 , 1024 , 1024 , 1024 , 1024 },//j { 255 , 32 , 80 , 136 , 1024 , 1024 , 1024 , 1024 },//k { 127 , 128 , 128 , 1024 , 1024 , 1024 , 1024 , 1024 },//l { 240 , 8 , 48 , 8 , 240 , 1024 , 1024 , 1024 },//m { 240 , 8 , 8 , 8 , 240 , 1024 , 1024 , 1024 },//n { 112 , 136 , 136 , 136 , 112 , 1024 , 1024 , 1024 },//o { 1016 , 136 , 136 , 136 , 112 , 1024 , 1024 , 1024 },//p { 112 , 136 , 136 , 1016 , 256 , 1024 , 1024 , 1024 },//q { 240 , 8 , 8 , 8 , 1024 , 1024 , 1024 , 1024 },//r { 144 , 168 , 168 , 72 , 1024 , 1024 , 1024 , 1024 },//s { 16 , 124 , 144 , 1024 , 1024 , 1024 , 1024 , 1024 },//t { 120 , 128 , 128 , 120 , 1024 , 1024 , 1024 , 1024 },//u { 120 , 128 , 120 , 1024 , 1024 , 1024 , 1024 , 1024 },//v { 120 , 128 , 112 , 128 , 120 , 1024 , 1024 , 1024 },//w { 136 , 80 , 32 , 80 , 136 , 1024 , 1024 , 1024 },//x { 120 , 640 , 640 , 640 , 504 , 1024 , 1024 , 1024 },//y { 136 , 200 , 168 , 152 , 136 , 1024 , 1024 , 1024 },//z { 16 , 126 , 129 , 129 , 1024 , 1024 , 1024 , 1024 }, { 255 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 }, { 129 , 129 , 126 , 16 , 1024 , 1024 , 1024 , 1024 }, { 32 , 16 , 8 , 16 , 32 , 64 , 32 , 1024 } }; // general variables CRGB currBackCol = CRGB(64,64,0); // A global variable to keep a note of the current background GRB value (Some strips may have Red and Green switched CRGB currTextCol = CRGB(0,0,255); // a global variable to keep a track of the current text colour. /* 1 = Message 2 = Time 3 = Date 4 = Temp 5 = Ripples 6 = Matrix 7 = Snake 8 = Eyes 9 = Pac */ char stateOrder[] = {9,1,2,3,4,5,6,7,8,9};// an array indicating the order that each feature or state should run in. // char stateX = 0; // Related to the stateOrder array. Indicates the current state that should be run // variables for ethernet and getting data from website boolean connectNow = true; // boolean, set to true after x millisencds have passed since the last website retrieval boolean readyToRead = false; // if the data has been retrieved from the webpage and if the first character is an * then this will become true and the data will be stored. Used as error checking char comma = 0; // used to read through how many fields have been read from the web page. char c,cc; // c is used to store the character being read in the parseData function. cc is used to keep track of which index in the myData array is it inserting. byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // A mac address for the ethernet client. EthernetClient client; // The ethernet client long sinceLastConnect = 0; // the number of millisencds since the last update from the website char postcode[5]; // used for future plans to read postcode from sd card and send to WWO API via website to allow for custom users char message[60]; // A short message to display. Will store line from txt file on SD card char myData[15][4]; // stores data retrieved from webpage. char iSecond,iMinute, iHour, iDay, iMonth, iYear; // data for time once extracted from myData // variables for State 1. Scrolling Message char currLetter =0; //a variable used to track the current letter that the scrolling message should be processing int cl =0; // cl is the current line (column) that is to be processed by the scrolling text. Each character's column defined by a 10 bit interger. lower case i has one column, m has 5 int h; // something to with counting characters... int endScroll = 0; // Once the final character from the scrolling message passes, this will increment until 30, when all the characters have left the left of the display /// variables for states 2,3 &4 Time, Date and Temp char myTime[5]; // a char array used for time and date. Might not need to be global... "hh:mm" or "dd/mm"; char myTemp; //char used for the current temp "10c" "-5c". Not sure if negative works. Will check next winter. long sinceLastSecond = 0; // a variable used to check if one second has passed since the last time calcTime was called. boolean blinkX = true; // should a : be used to blink the seconds? // variables for state 5, ripples int dropletA[2] = {-1,-1}; // For the dropletCycle. There can be four droplets at a time and this tracks the centre for each. int dropletB[2] = {-1,-1}; int dropletC[2] = {-1,-1}; int dropletD[2] = {-1,-1}; char dropCount = 0; //How many droplets are in action char dropRandom = 0; // a global variable used to countdown to the next droplet unsigned long sinceStateStarted; // used to count howlong in milliseconds since the sinceStateStarted function started. Stupidly used and not renamed as it's used by other states. // variables for state 6, matrix boolean matStart = false; // A boolean used to see if the Matrix effect has been started int mats[30]; // an array used to track the individual Matrix Slivers // variables for state 7, snake char lastMove = 0; // char used for the snake to retain the last direction the snake moved in. boolean snakeStart = false; // boolean to check if the snake has or hasn't started unsigned char snakeHue = 0; // what value is the hue currently for the snake? int snakePos=0; // pixel that the snake is. //variables for state 8 & 9 Bitmaps File bmpFile2; // file for the bitmap for the temp stored in ///////////////////////////// void setup() { LEDS.setBrightness(32); // sets the brightness of the LEDs LEDS.addLeds<WS2811, 6>(leds, NUM_LEDS); // Adds the leds colour array to the LEDS array with chip type on data pin 6, with the number of leds. digitalWrite(10,HIGH); // Deselect the Wiznet Chip digitalWrite(4,HIGH); // Deselect the SD Card if (!SD.begin(4)) { // ensure the SD card is working return; } File myData; //local variable used for reading contents of memory card. As local, doesn't conflict with other myData. int lineCount=0; // set the counter for reading in lines to 0 myData = SD.open("myData.txt"); // open the myData.txt int charCount=0; // set the character counter to 0 while (myData.available()) { // while there is data to be read char cx = myData.read(); // store the next character as cx if(cx==10){ // if cx == 10 which is line feed switch(lineCount){ // which line is this? // Previously when the wifi shield was used, the SD card was used to store SSID and Password. This is a bit overkill just for a string, but can be used for future customisation. case 0: // if it's the first line h = charCount; // store the number of characters we've readed as h message[charCount]='\0'; // append onto the last char in the message array a Nul teminator break; case 1: // if it's the second line postcode[charCount]='\0'; // append onto the last char in the postcode array a Nul teminator break; } lineCount++; //increment the line count charCount=0; // rest the char count to 0 }else if(cx==13){ // if a carridge return, ignore it. }else { // if not a carriage return or a nul terminator switch(lineCount){ // check the line case 0: // if the first line message[charCount]=cx; // add a character onto the message variable break; case 1:// if the next line postcode[charCount]=cx; // add a character onto the postcode variable break; } charCount++; // incremement the character count } } myData.close(); // once done close the file setupLetters(); // clean up variables, wipe screen delay(100); // pause has been added to give the ethernet a moment to kick in if (Ethernet.begin(mac) == 0) { // if the ethernet hasn't begun Ethernet.begin(mac); // begin } connectToData(); // call the data from the website } void loop(){ if(millis()>(sinceLastConnect+600000)){ // if it's been 10 minutes since the last time we checked the website for data connectNow = true; // set this to true } if(connectNow == true){ // if this is true and it's time to check for data connectToData(); // call this function to check connectNow = false; // turn off the boolean checking for data sinceLastConnect = millis(); // store the time in millis since the last check } while (client.available()) { // if the ethernet client is available parseData(); // parse the data } if (!client.connected() && comma ==15) { // if the datat has been read in and the 15th item has been identified. Must consider a variable to identify how many items are coming in as this might change. readyToRead = false; // We're not ready to read. outputResults(); // Doesn't output anything, but does sort the data and save into variable sinceStateStarted = millis(); // generic counter to ID the last time a state changed client.stop(); // stop the ethernet client comma++; // increment comma to 16 to prevent this if statement from running again } if(sinceLastConnect != 0) // if we've connected at some point { if(sinceLastSecond == 0){ sinceLastSecond = sinceLastConnect; /// not sure about this, but not going to remove it... } else { checkSecond(); // check if a second has passed since the last time } } if(stateX>=sizeof(stateOrder)){ // if we've gone through more states than we have listed stateX=0; // go back to the first state } char currState = stateOrder[stateX]; // what state are we in? switch(currState){ // lets do something for each case 1: // Message updateScroll(); // scroll the text if(currLetter>=h){ // if the current letter we are checking is the length of the message we read from the SD card endScroll++; //, start counting the number of updates since it passed if(endScroll>30){ //Finished Scrolling stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis(); // record the time of this change setupLetters(); //wipe, clean, change... } } break; case 2: showTime(); // errr.... run this function if(millis()-sinceStateStarted>10000){ // if 10 seconds has past since we started this. stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change setupLetters(); //wipe, clean, change... } break; case 3: showDate(); if(millis()-sinceStateStarted>5000){ stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change setupLetters(); } break; case 4: showTemp(); if(millis()-sinceStateStarted>5000){ stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change setupLetters(); //wipe, clean, change... } break; case 5: setRandomDrop(); dropletCycle(); if(millis()-sinceStateStarted>10000){ stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change setupLetters(); //wipe, clean, change... } break; case 6: matrixStyle(); if(millis()-sinceStateStarted>10000){ // Serial.println("End"); stateX++;// move to the next state in the list. This will force the next loop to use the next state //writeLog("Finished Matrix"); sinceStateStarted = millis();// record the time of this change matStart = false; // set the boolean that is check when this first starts to false setupLetters(); //wipe, clean, change... } break; case 7: snake(); if(millis()-sinceStateStarted>30000){ stateX++;// move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change snakeStart = false; // set Snake so that it hasn't started setupLetters(); //wipe, clean, change... } break; case 8: bmpDraw("VID002/", 0, 0); if(millis()-sinceStateStarted>15000){ stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis(); // record the time of this change setupLetters(); //wipe, clean, change... } break; case 9: bmpDraw("VID001/", 0, 0); if(millis()-sinceStateStarted>8000){ stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change setupLetters(); //wipe, clean, change... } break; case 10: //memCheck(); if(millis()-sinceStateStarted>3000){ stateX++; // move to the next state in the list. This will force the next loop to use the next state sinceStateStarted = millis();// record the time of this change setupLetters(); //wipe, clean, change... } break; } delay(1); } void checkSecond(){ // has a second passed? if(millis()>(sinceLastSecond+1000)) // if a second has passed since the last time we checked { calcTime(); // calculate the new time sinceLastSecond+=1000; // add 1000 millis to the last sencond checkpoint } } void snake(){ // The snake generator CHSV temphsv;// This is a varaible that stored Hue Saturation and brightness instead of RGB if(!snakeStart){ // if we haven't already started snakeHue=random(256); // create a random hue 0-255 snakePos=random(300); // create a random start point for(int v=0;v<300;v++){ // for each pixel, temphsv.h = snakeHue; // the temp hue to the snake hue temphsv.s = 187; //medium saturation temphsv.v = 255; // full brightness hsv2rgb_rainbow( temphsv, leds[v]); // Converts HSV to RGB annd sets the LED snakeStart = true; // set this to true so this first bit doesn't run again } LEDS.show(); // send all of the LED data through to the strip } snakeHue++; // adjust the snake hue by 1. As it's an unsigned character it will automatically become 0 when it goes over 255 temphsv.h = snakeHue; // do the set up of the HSV temphsv.s = 187; temphsv.v = 255; hsv2rgb_rainbow( temphsv, leds[snakePos]); // This time only make the snake pos the new colour boolean goodMove = false; // set this to false so that it sets this to think no good move has been made char diceRoll = random(4); // a random number 0 -3 sets the char also while(!goodMove){ // whislt no good move has been made diceRoll = random(4); // make a new random number... /// 0 moves left, 1 moves right, 2 moves up, 3 moves down if(diceRoll == 0 && lastMove!=1){goodMove = true;} // if this move is left and the last move was not right (back on itself) then this is a good move if(diceRoll == 1 && lastMove!=0){goodMove = true;} // if this move is right and the last move was not left (back on itself) then this is a good move if(diceRoll == 2 && lastMove!=3){goodMove = true;} // if this move is up and the last move was not moving down (back on itself) then this is a good move if(diceRoll == 3 && lastMove!=2){goodMove = true;} // if this move is down and the last move was not up (back on itself) then this is a good move } lastMove = diceRoll; // set the last move to the current move switch(diceRoll){ // act on the random number case 0: // left if(snakePos%30==0){ //if the snake position is right on the left so that the LED number is whole divisable by 0 snakePos+=29; // move the pixel to the far right of the display (so it wraps around) }else{ snakePos--; // otherwise move it to the LED to the left } break; case 1: // right if(snakePos%30==29){ // if the remainder from this is 29, it means the snake is at the far right of the display snakePos-=29; // substract 29 moves it to the other side of the screen }else{ snakePos++; // otherwise just move it to the pixel on the right } break; case 2: // up if(snakePos<29){ //if the current LED is less than 29, it is already at the top snakePos+=270; // so adding 270 moves it to the bottom }else{ snakePos-=30; // otherwise substract 30 will move it directly to the line above } break; case 3: // down if(snakePos>269){ // if it's greater than 269 then the snake is already on the bottom line snakePos-=270; // subtracting 270 will move it to the top }else{ snakePos+=30; // otherwise, adding 30 will move it to the next line down } break; } LEDS.show(); // Once everything is done, send it through to the strip } void setupLetters(){ // cleans up the variables for messages and text, calls the random colour chooser, wipes background, endScroll = 0; // reset the endscroll to 0, otherwise the next scrolling message will think it's already completed currLetter = 0; // set the current letter back to 0 so that it starts with the first character next time cl=0; // set the current line to 0 so that the first column of the letter is rendered first randomColourPicker(); // chooses a new colour from a selection wipeBG(currBackCol); // wipes everything with the new background colour } void randomColourPicker(){ // for picking a random colour scheme from a selection char randChar = random(7); // pick a random number // thinking about this, an array in progmem might be better for this? switch(randChar){ // what's our number? case 0: currBackCol = CRGB(0,0,0); // set backgroun to black currTextCol = CRGB(255,0,255); // set the text to whatever break; case 1: currBackCol = CRGB(0,0,0);// rinse and repeat currTextCol = CRGB(255,255,255); break; case 2: currBackCol = CRGB(0,0,0); currTextCol = CRGB(255,255,0); break; case 3: currBackCol = CRGB(0,0,0); currTextCol = CRGB(0,255,255); break; case 4: currBackCol = CRGB(0,0,0); currTextCol = CRGB(0,0,255); break; case 5: currBackCol = CRGB(0,0,0); currTextCol = CRGB(0,255,0); break; case 6: currBackCol = CRGB(0,0,0); currTextCol = CRGB(255,0,0); break; // case 7: // not using these are the moment as they didn't work that well // currBackCol = CRGB(255,0,0); // currTextCol = CRGB(0,0,0); // break; // case 8: // currBackCol = CRGB(0,255,0); // currTextCol = CRGB(0,0,0); // break; // case 9: // currBackCol = CRGB(0,0,255); // currTextCol = CRGB(0,0,0); // break; // case 10: // currBackCol = CRGB(0,255,255); // currTextCol = CRGB(0,0,0); // break; // case 11: // currBackCol = CRGB(255,255,0); // currTextCol = CRGB(0,0,0); // break; // case 12: // currBackCol = CRGB(255,0,255); // currTextCol = CRGB(0,0,0); // break; // case 13: // currBackCol = CRGB(255,255,255); // currTextCol = CRGB(0,0,0); // break; } } void wipeBG(CRGB thisCol){ // with this colour for(int v=0; v<300; v++){ // every LED in the house leds[v] = thisCol;// change to this colour; } LEDS.show(); //update the strip } void showTime(){ //showTime, really, not the TV channel char buffer[2]; // creates a buffer so that we can get around a single digit for numbers less than 10 itoa(iHour,buffer,10); // converts iHour, an integer to a string in the char buffer, to base 10 if(iHour<10){ //if less than ten myTime[0] = '0'; //set the first char in myTime to '0' myTime[1] = buffer[0]; // set the send char to the number }else{ myTime[0] = buffer[0]; //if greater than 9 then just take the hour to the respective index. myTime[1] = buffer[1]; } if(blinkX){ // the third char is a blinking divider myTime[2] = ':'; // which is either on... }else{ myTime[2] = ' ';// or not on } itoa(iMinute,buffer,10); // do the same conversion and check for minutes if(iMinute<10){ myTime[3] = '0'; // enter a zero for 'tens' of minutes into the 4th char myTime[4] = buffer[0]; //the then the rest }else{ myTime[3] = buffer[0]; myTime[4] = buffer[1]; } blinkX^=true; //reverse blink. delay(500); // pause for half a second showStillText(myTime); // call the function to display the time } void showDate(){ // pretty much as the showTime function. Read that for the same char buffer[2]; itoa(iDay,buffer,10); if(iDay<10){ myTime[0] = '0'; myTime[1] = buffer[0]; }else{ myTime[0] = buffer[0]; myTime[1] = buffer[1]; } myTime[2] = '/'; itoa(iMonth,buffer,10); if(iMonth<10){ myTime[3] = '0'; myTime[4] = buffer[0]; }else{ myTime[3] = buffer[0]; myTime[4] = buffer[1]; } delay(500); showStillText(myTime); } void showTemp(){//Simliar to the previous two functions. char myTemp[5]; for(int l=0;l<5;l++){ if(myData[0][l]!='\0'){ // if this isn't the end of the data myTemp[l] = myData[0][l]; // add it to the char array }else{ myTemp[l++] = 'c'; // if it is, then add celius on the end while(l<5){ myTemp[l++] = ' '; // if still less than 5 chars, add a space } } } showStillText(myTemp); // show the text delay(500); // pause } void showStillText(char newText[5]){ // only take five characters int updateLine,vv,i, k, y; for(i=0;i<29;i++){ // for each column if(i==0){ vv=0; // which character are we dealing with? cl=0; // which column in the character are we dealing with updateLine = 0; }else{ y =newText[vv]; // y is the current chracter we need to process updateLine = findLine(y); // checks for the binary 10 bit column data. if(vv>4){ // if we've checked all the chars in the newText updateLine = 0; } if(updateLine==1024){ //if 1024 indicates that all columns in the character have been rendered for(char pp=0;pp<10;pp++){ // for each pixel in this column leds[((270+i)-(pp*30))] = currBackCol; // wipe all to the background colour before we begin } cl=0; // set the current column to process to 0 vv++; //increment the variable counting which character we are processing }else{ /// see the bottom of the script for an ASCII explaination if(updateLine/512==1){ // If the data can be divided by 512 and is 1 (integers are whole), then the the bottom LED is lit leds[270+i] = currTextCol; // The bottom LED is set to the the text colour. updateLine-=512; // Remove 512, otherwise everything else can divide into it }else{ leds[270+i] = currBackCol; // otherwise set this LED to off } if(updateLine/256==1){ // If divisable by 256 leds[240+i] = currTextCol; // then go to the second to bottom LED and set updateLine-=256; // remove }else{ leds[240+i] = currBackCol; } if(updateLine/128==1) //rinse and repeat { leds[210+i] = currTextCol; updateLine-=128; }else{ leds[210+i] = currBackCol; } if(updateLine/64==1) { leds[180+i] = currTextCol; updateLine-=64; }else{ leds[180+i] = currBackCol; } if(updateLine/32==1){ leds[150+i] = currTextCol; updateLine-=32; }else{ leds[150+i] = currBackCol; } if(updateLine/16==1) { leds[120+i] = currTextCol; updateLine-=16; }else{ leds[120+i] = currBackCol; } if(updateLine/8==1) { leds[90+i] = currTextCol; updateLine-=8; }else{ leds[90+i] = currBackCol; } if(updateLine/4==1) { leds[60+i] = currTextCol; updateLine-=4; }else{ leds[60+i] = currBackCol; } if(updateLine/2==1) { leds[30+i] = currTextCol; updateLine-=2; }else{ leds[30+i] = currBackCol; } if(updateLine==1) { leds[i] = currTextCol; }else{ leds[i] = currBackCol; } } } } LEDS.show(); } void matrixStyle(){ int v; // holding integer for the current "sliver" running down the screen if(matStart == false){ //If this is the start of the cycle... for(v=0;v<30;v++){ // Set up 30 "slivers" mats[v] = random(420)-120; // between -120 and 300 } //writeLog("Start Martix"); matStart = true; // switch the boolean so the above won't run each time. } for(int v=0; v<300; v++){ // for every pixel on the display, wipe all and leds[v] = CRGB(16,0,0); // set to 16 green. } // each silver is 5 pixels long, and the start postion is from the top (screen starts top left) //so the following if statements checks for(v=0;v<30;v++){ // each silver in the mats array int pv = mats[v]; // temp store the top position in pv if(pv>-120 && pv < 179){ // if the start position is at least more than four lines from the bottom of the display leds[pv+120] = CRGB(255,0,0); // make the pixel four lines down the brightest green it can. } if(pv>-90 && pv < 209){ // if the start position is at least more than three lines from the bottom of the display leds[pv+90] = CRGB(200,0,0); // make the pixel three lines down the 200 green. } if(pv>-60 && pv < 239){ // if the start position is at least more than two lines from the bottom of the display leds[pv+60] = CRGB(150,0,0); // make the pixel two lines down the 150 green. } if(pv>-30 && pv < 269){// if the start position is at least more than one line from the bottom of the display leds[pv+30] = CRGB(100,0,0); // make the pixel one line down the 100 green. } pv+=30; //add 30 to the sliver position so the next cycle it is one line down if(pv>300){ // if the srat position is past the bottom of the screen mats[v] = random(30)-120; // set the new position between -120 and -91 }else{ mats[v]=pv; // otherwise set it to one line down } } LEDS.show(); //push this data to the strip delay(50); //pause for 1/20th second } /////////////////////////////////////////////////////////////////////////////////////////////////////////// // Start of the code dealing with the droplets. // Made up of four functions. // setRandomDrop // dropletCycle // droplets // ripple1, ripple2, ripple3. ripple4, ripple5, ripple6, ripple7, ripple8, ripple9, ripple 10 // setRipPix // // setRandomDrop(); and dropletCycle(); are called from the update loop every loop that char currState = stateOrder[stateX]; = 5 // setRandomDrop times the creation of new drops and gives them a random position // dropletCycle makes the ripple effect and for each active drop, calls droplets // droplets works out the age of the droplet and calls one to three of the ripple(1-10) functions that make up the size of the ripple // ripple(1-10) accesses the splash(1-10) arrays that holds the offsets for every pixel that makes up the ripple. Calls setRipPix // setRipPix uses the offsets for each pixel in each ripple to set the colour of each. void setRandomDrop(){ if(dropRandom<0){ // dropRandom allows a random number of frame updates before it creates another droplet. int r = random(300); // create a random pixel to start the droplet at. switch(dropCount){ // depending on which droplet should be created.... case 0: dropletA[0] = r; // set the position of the new droplet dropletA[1] = 0; // set the age of the new droplet to 0 break; case 1: dropletB[0] = r; dropletB[1] = 0; break; case 2: dropletC[0] = r; dropletC[1] = 0; break; case 3: dropletD[0] = r; dropletD[1] = 0; break; } dropRandom = random(20); // Create a new random amount of cycles until the next droplet is generated dropCount++; // Increment the order of droplets } if(dropCount>3){ //if the order of droplets has gone beyond 3, switch to 0 so that it can't have mroe than 4. dropCount = 0; } dropRandom--; //decrement the counter countingdown to the next droplet } void dropletCycle(){ for(int v=0; v<300; v++){ // for all pixels int blueRand = random(25); // a random number leds[v] = CRGB(0,0,25+blueRand); // this will create a rippling effect as each pixel will be set to a blue between 25 and 50 } if(dropletA[1] != -1){ // if dropletA's age is -1, it hasn't been created, so only run this if it exists. dropletA[1] = droplets(dropletA); // call the droplets function for dropletA. The returned value will increment the age of the ripple. } if(dropletB[1] != -1){ dropletB[1] = droplets(dropletB); } if(dropletC[1] != -1){ dropletC[1] = droplets(dropletC); } if(dropletD[1] != -1){ dropletD[1] = droplets(dropletD); } LEDS.show(); } int droplets(int x[2]){ // from above. This will expect an integer array with two values // int c = 85; int stage = x[1]; // stage = age of droplet int q = x[0]; // centre of the droplet. Pixel switch(stage){ // depending on the age of the droplet case 1: ripple1(q, 1); // only run the smallest ripple, first variable sent through is position, second is the brightness of the part of the ripple 1*85 = 85, 3*85 is 255 break; case 2: ripple1(q,3); // Ripple 1 (smallest) will be 3 times brighter than the larger ripple. ripple2(q,1); // Ripple 2 will be one third of the brightness of ripple 1. break; case 3: ripple1(q,1); ripple2(q,3); ripple3(q,1); break; case 4: ripple2(q,1); ripple3(q,3); ripple4(q,1); break; case 5: ripple3(q,1); ripple4(q,3); ripple5(q,1); break; case 6: ripple4(q,1); ripple5(q,3); ripple6(q,1); break; case 7: ripple5(q,1); ripple6(q,3); ripple7(q,1); break; case 8: ripple6(q,1); ripple7(q,3); ripple8(q,1); break; case 9: ripple7(q,1); ripple8(q,3); ripple9(q,1); break; case 10: ripple8(q,1); ripple9(q,2); ripple10(q,1); break; case 11: ripple9(q,1); ripple10(q,2); break; case 12: ripple10(q,1); break; } stage++; if(stage>14){ stage = -1; // if the ripple is past the age of 14, then set it to -1, which indcates non-existance. } return stage; // return the age } void ripple1(int w, int f){ for(int p=0;p<4;p++){ //splash1 which is in progMem has 4 pixels, hence looping 4 times char ax = pgm_read_byte_near(&(splash1[p][0])); // retrive the x offset for this pixel of the ripple char ay = pgm_read_byte_near(&(splash1[p][1])); // retrive the y offset for this pixel of the ripple setRipPix(w,f,ax,ay); // send the centre position of the ripple, the intensity of the brightness, x offset and y offet to setRipPix.... } } void ripple2(int w, int f){ for(int p=0;p<8;p++){ //splash2 which is in progMem has 8 pixels, hence looping 8 times char ax = pgm_read_byte_near(&(splash2[p][0])); char ay = pgm_read_byte_near(&(splash2[p][1])); setRipPix(w,f,ax,ay); } } void ripple3(int w, int f){ for(int p=0;p<12;p++){ char ax = pgm_read_byte_near(&(splash3[p][0])); char ay = pgm_read_byte_near(&(splash3[p][1])); setRipPix(w,f,ax,ay); } } void ripple4(int w, int f){ for(int p=0;p<16;p++){ char ax = pgm_read_byte_near(&(splash4[p][0])); char ay = pgm_read_byte_near(&(splash4[p][1])); setRipPix(w,f,ax,ay); } } void ripple5(int w, int f){ for(int p=0;p<36;p++){ char ax = pgm_read_byte_near(&(splash5[p][0])); char ay = pgm_read_byte_near(&(splash5[p][1])); setRipPix(w,f,ax,ay); } } void ripple6(int w, int f){ for(int p=0;p<28;p++){ char ax = pgm_read_byte_near(&(splash6[p][0])); char ay = pgm_read_byte_near(&(splash6[p][1])); setRipPix(w,f,ax,ay); } } void ripple7(int w, int f){ for(int p=0;p<32;p++){ char ax = pgm_read_byte_near(&(splash7[p][0])); char ay = pgm_read_byte_near(&(splash7[p][1])); setRipPix(w,f,ax,ay); } } void ripple8(int w, int f){ for(int p=0;p<36;p++){ char ax = pgm_read_byte_near(&(splash8[p][0])); char ay = pgm_read_byte_near(&(splash8[p][1])); setRipPix(w,f,ax,ay); } } void ripple9(int w, int f){ for(int p=0;p<68;p++){ char ax = pgm_read_byte_near(&(splash9[p][0])); char ay = pgm_read_byte_near(&(splash9[p][1])); setRipPix(w,f,ax,ay); } } void ripple10(int w, int f){ for(int p=0;p<48;p++){ char ax = pgm_read_byte_near(&(splash10[p][0])); char ay = pgm_read_byte_near(&(splash10[p][1])); setRipPix(w,f,ax,ay); } } void setRipPix(int w, int f, char ax, char ay){ int pix = w + ax + (ay*30); //calculate the individual pixel sent that makes up part of the current ripple by taking the centre (w), adding the x offset, and adding the yOffset by multiplying it by 30 to get the Y position if(pix > -1 && pix < 300){ // ensure that this pixel is in the display range leds[pix] = CRGB(0,0,85*f); // set that pixel to the intestity. } } void updateScroll(){ int i, k, l; //////////////////////////////////////////// for(i=0;i<29;i++) // { // This nested loop will just budge each pixel/LED value for(k=0;k<10;k++) // back one The value of LED[10] will become LED[9], { // LED[234] becomes LED[233]. This means the only LEDs that l = i+(k*30); // have to be recalculated are 29,59,89,119,149,179,209,239,269 leds[l] = leds[l+1]; // } // } // ////////////////////////////////////////// int updateLine; // See the function showStillText for the rest of this function explaination as they are very much the same // This one just budges the LEDs left once and calculates the far right LEDs. char y =message[currLetter]; if(currLetter>h-1){ updateLine=0; currLetter++; }else{ updateLine = findLine(y); } if(updateLine==1024) { for(char pp=0;pp<10;pp++){ leds[(299-(pp*30))] = currBackCol; } cl=0; currLetter++; } else { if(updateLine/512==1) { leds[299] = currTextCol; updateLine-=512; } else { leds[299] = currBackCol; } if(updateLine/256==1) { leds[269] = currTextCol; updateLine-=256; } else { leds[269] = currBackCol;; } if(updateLine/128==1) { leds[239] = currTextCol; updateLine-=128; } else { leds[239] = currBackCol; } if(updateLine/64==1) { leds[209] = currTextCol; updateLine-=64; } else { leds[209] = currBackCol; } if(updateLine/32==1) { leds[179] = currTextCol; updateLine-=32; } else { leds[179] = currBackCol; } if(updateLine/16==1) { leds[149] = currTextCol; updateLine-=16; } else { leds[149] = currBackCol; } if(updateLine/8==1) { leds[119] = currTextCol; updateLine-=8; } else { leds[119] = currBackCol; } if(updateLine/4==1) { leds[89] = currTextCol; updateLine-=4; } else { leds[89] = currBackCol; } if(updateLine/2==1) { leds[59] = currTextCol; updateLine-=2; } else { leds[59] = currBackCol; } if(updateLine==1) { leds[29] = currTextCol; } else { leds[29] = currBackCol; } } LEDS.show(); delay(50); } int findLine(char x){ // identifies the 10bit integer that indicates the LEDs in the column of the character. X is the character/ASCII code int p; // p = pgm_read_word(&(myChars[x-1][cl])); //Accesss the ProgMem array myChars. Minus 1 for zero index. CL is the current line(column) of the character to be processed cl++; //increments the current line (column) for the next time return p; // returns the 10 bit column information. } void calcTime(){ //Some of this is overkill due to the 10 minute check, but it might look stupid if you have a March 32 at 00.01 iSecond++; //Add one to iSecond if(iSecond>59) // if we have 60 or more seconds { iSecond=0; // set seconds to 0 iMinute++; // add one to minutes if(iMinute>59) // if we now have more than 60 minutes { iMinute=0; // set minutes to 0 iHour++;// and add one to the hour if(iHour>23)// if we have more than 23 hours { iHour=0; // set hours to 0 iDay++; // add a day if(iMonth==2) // if feb { if(iDay>=28)// if days over 28 { if(iYear%4==0)//if a leap year { if(iDay==29) // if over 29 { iMonth++; // add a month iDay=1; // set day to 1 } } else // else must be 28 days { iMonth++; // add a month iDay=1; // set day to 1 } } } if(iMonth==1,3,5,7,8,10,12) // if jan, march, July, August, October or December { if(iDay>31) // if the day is over 31 { iMonth++; // add a month iDay=1; // set day to 1 } } if(iMonth==4,6,9,11) // if April, June, Septmeber or November { if(iDay>30)// if day is greater than 30 { iMonth++; // add a month iDay=1; // set day to 1 } } if(iMonth==13)// if month is 13 { iMonth==1; /// set to 1 (Jan) iYear++; // add a year } } } } } void connectToData(){ // This is the function that checks for data from a website. See the php files for how this works for(int p=0;p<15;p++){ // for 15 bits of data, this should be in a variable rather than hardcoded.... for(int o=0;o<4;o++){ // for five characters myData[p][o] = '\0'; // blitz the lot with Nul terminators. } } comma = 0; //set the comma count to 0 delay(2000); // The delay allows the Enternet client time to settle, otherwise data might not be reached.... if (client.connect("your.wesbite.com", 80)) { // The website client.println("GET /ardData.php HTTP/1.1"); // The URL client.println("Host: your.wesbite.com"); // The host client.println("Connection: close"); // Not sure, but needed.... client.println(); // No idea, but not going to mess with it. delay(2000); // Another delay to also the client time.... } } void parseData(){ // Reading in the data from the website // Example // *19,116,8,ESE,21,10,8,E,116,06,26,14,13,28,43 // * indidcates the start and is used to check the data is mine. // Each bit of data is delimited with a comma // Check youtube video for more info. c = client.read(); // read in the next (or first if we've just started) if(c=='*'){ // if the first character is an * then it's my data. An error checker of sorts readyToRead = true; // Ready to read the data in } if(c!=',' && readyToRead == true && c!='*' && c!=-1){ // If the character is not a comma, and not an * or dead, and we're ready to read myData[comma-1][cc]+=c; // add the character read into c appropate field cc++; //increment the character counter } if(c==','){ // if we've read in a comma, then it's the next bit of data myData[comma-1][cc]= '\0';; // end the current bit of data we're reading with a Nul Terminator cc=0; // Reset the character comma++; //increment the counter counting the different bits of data. } } void outputResults(){ // this will save the data from the website into various variables iSecond = atoi(myData[14]); //Convert chars to integers and save iMinute = atoi(myData[13]); iHour = atoi(myData[12]); iDay = atoi(myData[10]); if(iDay==0){ connectNow=true; // if iDay = 0 then something is wrong with my website so try again. } iMonth = atoi(myData[9]); if(iMonth==0){ connectNow=true; } iYear = atoi(myData[11]); if(iYear==0){ connectNow=true; } /* Serial.print("currTemp "); //1,1 Serial.println(myData[0]); //1,1 Serial.print("currWeathercode "); //1,2 Serial.println(myData[1]); //1,2 Serial.print("currWindSpeed "); //1,5 Serial.println(myData[2]); //1,5 Serial.print("currWindDirection "); //1,8 Serial.println(myData[3]); //1,8 Serial.print("futTempHigh "); //2,1 Serial.println(myData[4]); //2,1 Serial.print("futTempLow "); //2,3 Serial.println(myData[5]); //2,3 Serial.print("futWindSpeed "); //2,5 Serial.println(myData[6]); //2,5 Serial.print("futWeatherCode "); //2,9 Serial.println(myData[7]); //2,9 Serial.print("futWindDirection "); //2,8 Serial.println(myData[8]); //2,8 printTime();*/ } void bmpDraw(char *filename, uint8_t x, uint8_t y) { // This is slightly complex int bmpWidth, bmpHeight; // W+H in pixels uint8_t bmpDepth; // Bit depth (currently must be 24) uint32_t bmpImageoffset; // Start of image data in file uint32_t rowSize; // Not always = bmpWidth; may have padding uint8_t sdbuffer[3*20]; // pixel buffer (R+G+B per pixel) uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer boolean goodBmp = false; // Set to true on valid header parse boolean flip = true; // BMP is stored bottom-to-top int w, h, row, col; // setting up integers for width, height, row and column. uint8_t r, g, b,lc; // unsigned 8 bit integers.... bmpFile2 = SD.open(filename); // open the folder bmpFile2.rewindDirectory(); // go to the start of the directory while(true) { delay(1); lc++; //? File bmpFile = bmpFile2.openNextFile(); // read in the next file if (! bmpFile) { //If no file break; // stop } uint32_t pos = 0; if(read16(bmpFile) == 0x4D42) { // If this has a BMP signature //Going through the header data, there are things we can read and ignore.... (void)read32(bmpFile); //ignore this (void)read32(bmpFile); // Read & ignore creator bytes bmpImageoffset = read32(bmpFile); // Start of image data (void)read32(bmpFile); // Ignore bmpWidth = read32(bmpFile); //This retrieves the width of the bitmap in pixels bmpHeight = read32(bmpFile); //This retrieves the height of the bitmap in pixels if(read16(bmpFile) == 1) { // # planes -- must be '1' bmpDepth = read16(bmpFile); // bits per pixel if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed goodBmp = true; // Supported BMP format -- proceed! rowSize = (bmpWidth * 3 + 3) & ~3; if(bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; } w = bmpWidth; h = bmpHeight; for (row=0; row<h; row++) { // For each scanline... if(flip) // Bitmap is stored bottom-to-top order (normal BMP) pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize; else // Bitmap is stored top-to-bottom pos = bmpImageoffset + row * rowSize; if(bmpFile.position() != pos) { // Need seek? bmpFile.seek(pos); buffidx = sizeof(sdbuffer); // Force buffer reload } // not sure about these bits.... for (col=0; col<w; col++) { // For each pixel... // Time to read more pixel data? if (buffidx >= sizeof(sdbuffer)) { // Indeed bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx = 0; // Set index to beginning } b = sdbuffer[buffidx++]; //blue first g = sdbuffer[buffidx++]; // then green r = sdbuffer[buffidx++]; // then red leds[(row*30)+col] = CRGB(g,r,b); // set led } // end pixel } // end scanline } // end goodBmp } } bmpFile.close(); // close file if(!goodBmp) { } else { LEDS.show(); if(lc==10){ lc = 0; checkSecond(); } } } bmpFile2.close(); } /// These bits are not mine, from somewhere.... uint16_t read16(File f) { uint16_t result; ((uint8_t *)&result)[0] = f.read(); // LSB ((uint8_t *)&result)[1] = f.read(); // MSB return result; } uint32_t read32(File f) { uint32_t result; ((uint8_t *)&result)[0] = f.read(); // LSB ((uint8_t *)&result)[1] = f.read(); ((uint8_t *)&result)[2] = f.read(); ((uint8_t *)&result)[3] = f.read(); // MSB return result; } /// I've left the next in just incase you need to debug in some way // The serial monitor seems to be broken when running this script //void //writeLog(String debug){ // File myLog = SD.open("MyLog.txt", FILE_WRITE); //if(myLog){ // char buffer[2]; // itoa(iHour,buffer,10); // myLog.print(itoa(iHour,buffer,10)); // myLog.print(":"); // myLog.print(itoa(iMinute,buffer,10)); // myLog.print(":"); // myLog.print(itoa(iSecond,buffer,10)); // myLog.print(" --- "); // myLog.println(debug); // myLog.print("freeMemory()="); // myLog.println(freeMemory()); //} //myLog.close(); // //delay(500); //} //void memCheck(){ // char buff[5] =" "; // // itoa(freeMemory(),buff,10); // showStillText(buff); //} //void showMem(){ // Serial.print("freeMemory()="); // Serial.println(freeMemory()); //} //////// Text rendering explaination /* The display is ten pixels high, so for a character there is a width that is dependent on the character width, these are refered to in this script as columns. Each column has ten pixels. This script uses values between 0 and 1024 to indicate which pixels should be lit. 1024 is a sort of Nul terminator, so when this is read in, the functions leave a gap and move to the next character A lower case 'i' has { 250 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 , 1024 } 2+8+16+32+64+128 = 250 An uppercase 'N' is { 508 , 2 , 2 , 2 , 508 , 1024 , 1024 , 1024 } 4+8+16+32+64+128+256 = 508 0001 0002 X XXX 0004 X X 0008 X X X 0016 X X X 0032 X X X 0064 X X X 0128 X X X 0256 X X 0512 */
My colleague Ryan send me on a Tweet about SensoDuino, an app for Android Phones that allows you through the phone’s bluetooth to send sensor data from your phone; from gyroscope and accelerometer data to light, heat and humidity!; to your Arduino. As luck would have it I had borrowed a few items from work and had a Bluetooth module sitting right next to me on my coffee table. The problem being that the author recommends the cheaper RC05 bluetooth module and I had the rather expensive RN42 bluetooth module, adapted by Parallax into their 30086 module, which rather handily narrows down all of the various pins of the RN42 into seven outputs. There is very little information out there on connecting this module to the Arduino. So this post is to document process I went through to get the RN42 module to work with SensoDuino.
The wiring of the module is extremely easy to connect to the Arduino Pro as it has a jumper for 3.3v and 5v. The wiring is as so…
Arduino —> 30086 RN42
5v — > VIN
GND —> GND
0(RX) –> TX
1(TX) —> RX
That’s it, no need to knock the voltage down with combinations of resistors.
Uploading the sketch to the Arduino is standard, although as Hazam Bitar points out, you may need to disconnect the power from the bluetooth module as it will interfere with uploading a new sketch.
By default, the jumper settings on the Bluetooth module sets the baud rate to 9600, so you will need to change this in the sketch
Serial.begin(9600);
Or change the first jumper to off and it will run at 115200.
So that’s it. It will be interesting to see if I will be able to make my own app that can use the sensors in my phone to control whatever I wish without going through the rather excellent SensoDuino, but for those learning and wanting to prototype devices without working with complex wiring and badly documented components, then this is a brilliant app.
Yesterday I posted my Photoshop Script for creating heightmaps from OS data, but I wasn’t happy with the 30 minutes it took Photoshop to render the map.
Many moons ago I used to be a web developer and I loved messing around with the Image creation in PHP. I thought that generating images was a lot quicker, so this evening, I’ve ported my script to PHP and I can confirm it is quicker. About 29 minutes and 50 seconds quicker. In fact it takes longer to upload the OS data than it does render the image.
So the new steps are this:
1. Get the OS data as before.
2. Go to My Heightmap generator
3. Choose between Relative (best for single heightmaps) or Absolute (Best for multiple if used with multiple terrain)
4. Right click and copy image.
5. Open a package of your choice and create a new file. Photoshop will take the settings of whatever is in the clipboard. Ensure it is greyscale.
6. Save as a RAW file.
That’s it.
This tutorial will allow you to create heightmap data from Ordnance Survey’s OS Terrain 50 grid data. The information is free to download from https://www.ordnancesurvey.co.uk/opendatadownload/products.html
This process may well work for data from other sources, but as far as I know, this works for 10x10km areas of the UK only and produces a resolution of 50 metres.
A preview of my Arduino/Unity experiment that I will talk about at LUUG 21.
This is much the same as the Ultrasonic experiment from a while back. I’m not going to write this up, but I have made another video which explains everything.
Later this month I’ll be doing a talk for LUUG (London Unity Usergroup) on using Arduinos with Unity, so to prepare for that I’ve gone through the basic processes of talking to the Arduino from Unity.
For this tutorial, you’ll need…
Howdy all,
A few weeks ago I revisited Retroshooter for my class as a part of a transfer from Unity to UDK. I don’t think that I sold UDK very well due to my ranting about creating a basic terrain, or the process of shutting down UDK to compile, then reopen and test…
Anyway, I’ve resurrected Retroshooter for UDK Feb 2013 beta and included a model I created, rigged and brought into UDK to take the place of the Zombies. Unfortunately the poor chaps seem to lose their arms, probably because I disarmed them, making them… (h)armless.
I thank you… Please comment on youTube if you wish, I can’t work out how to turn on comments here without getting hammered by spam. The original video is at Retroshooter on youTube
Retroshooter files This contains…
Continue reading
Today I did a quick test to see how well I could get an Arduino to talk to Unity. The video is the result of the experiment.
aurduinoTest – Zip File of my Unity Project
The Ultrasonic code has largely been taken from http://www.instructables.com/id/Simple-Arduino-and-HC-SR04-Example/ and the Unity example was largely taken from http://arduino.cc/forum/index.php?action=printpage;topic=73425.0 I just stuck the two together.
My altered code for the Arduino is below.