/ Temperature_GIF_Player / Temperature_GIF_Player.ino
Temperature_GIF_Player.ino
  1  // SPDX-FileCopyrightText: 2020 Limor Fried/ladyada for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // please read credits at the bottom of file
  6  
  7  #include <Adafruit_Arcada.h>
  8  #include <Arcada_GifDecoder.h>
  9  
 10  /*************** Display setup */
 11  Adafruit_Arcada arcada;
 12  GifDecoder<ARCADA_TFT_WIDTH, ARCADA_TFT_HEIGHT, 12> decoder;
 13  
 14  File file;
 15  int16_t  gif_offset_x, gif_offset_y;
 16  #define GIF_DIRECTORY        "/"    // on SD or QSPI
 17  
 18  float hotTemp = 24;       // how warm it has to be to be considered hot
 19  float coldTemp = 20;      // how cool it has to be to be considered cold
 20  
 21  
 22  #if defined(ARDUINO_NRF52840_CIRCUITPLAY)
 23  void setupTemperature() {
 24  }
 25  float readTemperature() {
 26    return CircuitPlayground.temperature();
 27  }
 28  #endif
 29  
 30  #define BRIGHTER_BUTTON        ARCADA_BUTTONMASK_A
 31  #define DIMMER_BUTTON          ARCADA_BUTTONMASK_B
 32  
 33  
 34  // Setup method runs once, when the sketch starts
 35  void setup() {
 36    decoder.setScreenClearCallback(screenClearCallback);
 37    decoder.setUpdateScreenCallback(updateScreenCallback);
 38    decoder.setDrawPixelCallback(drawPixelCallback);
 39    decoder.setDrawLineCallback(drawLineCallback);
 40  
 41    decoder.setFileSeekCallback(fileSeekCallback);
 42    decoder.setFilePositionCallback(filePositionCallback);
 43    decoder.setFileReadCallback(fileReadCallback);
 44    decoder.setFileReadBlockCallback(fileReadBlockCallback);
 45  
 46    // Start arcada!
 47    if (!arcada.arcadaBegin()) {
 48      Serial.println("Couldn't start Arcada");
 49      while(1) yield();
 50    }
 51    // If we are using TinyUSB & QSPI we will have the filesystem show up!
 52    arcada.filesysBeginMSD();
 53  
 54    //while (!Serial) delay(10);
 55    
 56    Serial.begin(115200);
 57    Serial.println("Animated GIFs Demo");
 58    
 59    arcada.displayBegin();
 60    arcada.display->fillScreen(ARCADA_BLUE);
 61    arcada.setBacklight(255);
 62  
 63    if (arcada.filesysBegin()) {
 64      Serial.println("Found filesystem!");
 65    } else {
 66      arcada.haltBox("No filesystem found! For QSPI flash, load CircuitPython. For SD cards, format with FAT");
 67    }
 68  
 69    if (! arcada.loadConfigurationFile()) {
 70       //arcada.infoBox("No configuration file found, using default 10 seconds per GIF");
 71       arcada.display->fillScreen(ARCADA_BLUE);
 72    } else {
 73      if (arcada.configJSON.containsKey("hot_temp")) {
 74        hotTemp = arcada.configJSON["hot_temp"];
 75      }
 76      if (arcada.configJSON.containsKey("cold_temp")) {
 77        coldTemp = arcada.configJSON["hot_temp"];
 78      }
 79    }
 80  }
 81  
 82  uint32_t cycle_start = 0L;
 83  int8_t currGIF = 0, nextGIF = 0;       // 0 for no change, +1 for hot gif, -1 for cool gif
 84  
 85  void loop() {
 86      if (arcada.recentUSB()) { 
 87        yield();
 88        return;       // prioritize USB over GIF decoding
 89      }
 90  
 91      // Check button presses
 92      arcada.readButtons();
 93      uint8_t buttons = arcada.justPressedButtons();
 94      if (buttons & BRIGHTER_BUTTON) {
 95        int16_t newbrightness = arcada.getBacklight();   // brightness up
 96        newbrightness = min(255, newbrightness+25);      // about 10 levels
 97        Serial.printf("New brightness %d", newbrightness);
 98        arcada.setBacklight(newbrightness, true);   // save to disk
 99      }
100      if (buttons & DIMMER_BUTTON) {
101        int16_t newbrightness = arcada.getBacklight();   // brightness down
102        newbrightness = max(25, newbrightness-25);       // about 10 levels
103        Serial.printf("New brightness %d", newbrightness);
104        arcada.setBacklight(newbrightness, true);   // save to disk
105      }
106  
107      uint32_t now = millis();
108  
109      // at least one 'cycle' elapsed, check if its time to change gifs
110      if(decoder.getCycleNo() > 1) {
111        // Print the stats for this GIF    
112        char buf[80];
113        int32_t frames       = decoder.getFrameCount();
114        int32_t cycle_design = decoder.getCycleTime();  // Intended duration
115        int32_t cycle_actual = now - cycle_start;       // Actual duration
116        int32_t percent = 100 * cycle_design / cycle_actual;
117        sprintf(buf, "[%ld frames = %ldms] actual: %ldms speed: %ld%%",
118                frames, cycle_design, cycle_actual, percent);
119        Serial.println(buf);
120  
121        float temp = readTemperature();
122        Serial.printf("Temp = %0.1f C\n", temp);
123        if (temp >= hotTemp) {
124          file = arcada.open("hot.gif");
125        } else if (temp <= coldTemp) {
126          file = arcada.open("cold.gif");
127        } else {
128          file = arcada.open("neutral.gif");
129        }
130  
131        cycle_start = now;
132        
133        if (!file) {
134          Serial.println("Gif not found!");
135          return;
136        }
137  
138        arcada.display->dmaWait();
139        arcada.display->endWrite();   // End transaction from any prior callback
140        arcada.display->fillScreen(ARCADA_BLACK);
141        decoder.startDecoding();
142  
143        // Center the GIF
144        uint16_t w, h;
145        decoder.getSize(&w, &h);
146        Serial.print("Width: "); Serial.print(w); Serial.print(" height: "); Serial.println(h);
147        if (w < arcada.display->width()) {
148          gif_offset_x = (arcada.display->width() - w) / 2;
149        } else {
150          gif_offset_x = 0;
151        }
152        if (h < arcada.display->height()) {
153          gif_offset_y = (arcada.display->height() - h) / 2;
154        } else {
155          gif_offset_y = 0;
156        }
157      }
158  
159      decoder.decodeFrame();
160  }
161  
162  /******************************* Drawing functions */
163  
164  void updateScreenCallback(void) {  }
165  
166  void screenClearCallback(void) {  }
167  
168  void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
169      arcada.display->drawPixel(x, y, arcada.display->color565(red, green, blue));
170  #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS
171      arcada.display2->drawPixel(x, y, arcada.display->color565(red, green, blue));
172  #endif
173  }
174  
175  void drawLineCallback(int16_t x, int16_t y, uint8_t *buf, int16_t w, uint16_t *palette, int16_t skip) {
176      uint16_t maxline = arcada.display->width();
177      bool splitdisplay = false;
178      
179      uint8_t pixel;
180      //uint32_t t = millis();
181      x += gif_offset_x;
182      y += gif_offset_y;
183      if (y >= arcada.display->height() || x >= maxline ) {
184        return;
185      }
186      
187  #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS
188      // two possibilities
189      if ((x + w) > 2*maxline) {
190        w = 2*maxline - x;
191      } 
192      if ((x + w) > maxline) {
193        splitdisplay = true;  // split the gif over both displays
194      }
195  #else
196      if (x + w > maxline) {
197        w = maxline - x;
198      }
199  #endif
200      if (w <= 0) return;
201  
202      //Serial.printf("Line (%d, %d) %d pixels skipping %d\n", x, y, w, skip);
203  
204      uint16_t buf565[2][w];
205      bool first = true; // First write op on this line?
206      uint8_t bufidx = 0;
207      uint16_t *ptr;
208  
209      for (int i = 0; i < w; ) {
210          int n = 0, startColumn = i;
211          ptr = &buf565[bufidx][0];
212          // Handle opaque span of pixels (stop at end of line or first transparent pixel)
213          if (skip == -1) {// no transparent pixels
214            while(i < w) {
215              ptr[n++] = palette[buf[i++]];
216            }
217          }
218          else {
219            while((i < w) && ((pixel = buf[i++]) != skip)) {
220              ptr[n++] = palette[pixel];
221            }
222          }
223          if (n) {
224              arcada.display->dmaWait(); // Wait for prior DMA transfer to complete
225  #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS
226              arcada.display2->dmaWait(); // Wait for prior DMA transfer to complete
227  #endif
228              if (first) {
229                arcada.display->endWrite();   // End transaction from prior callback
230                arcada.display->startWrite(); // Start new display transaction
231  #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS
232                arcada.display2->endWrite();   // End transaction from prior callback
233                arcada.display2->startWrite(); // Start new display transaction
234  #endif
235                first = false;
236              }
237              arcada.display->setAddrWindow(x + startColumn, y, min(maxline, n), 1);
238              arcada.display->writePixels(ptr, min(maxline, n), false, true);
239  #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS
240              if (! splitdisplay) { // same image on both!
241                arcada.display2->setAddrWindow(x + startColumn, y, min(maxline, n), 1);
242                arcada.display2->writePixels(ptr, min(maxline, n), false, true);
243              } else {
244                arcada.display2->setAddrWindow(x + startColumn, y, n-maxline, 1);
245                arcada.display2->writePixels(ptr+maxline, n-maxline, false, true);
246              }
247  #endif
248              bufidx = 1 - bufidx;
249          }
250      }
251  //    arcada.display->dmaWait(); // Wait for last DMA transfer to complete
252  #ifdef ADAFRUIT_MONSTER_M4SK_EXPRESS
253      arcada.display2->dmaWait(); // Wait for last DMA transfer to complete
254  #endif
255  }
256  
257  
258  bool fileSeekCallback(unsigned long position) {
259    return file.seek(position);
260  }
261  
262  unsigned long filePositionCallback(void) { 
263    return file.position(); 
264  }
265  
266  int fileReadCallback(void) {
267      return file.read(); 
268  }
269  
270  int fileReadBlockCallback(void * buffer, int numberOfBytes) {
271      return file.read((uint8_t*)buffer, numberOfBytes); //.kbv
272  }
273  
274  
275  /*
276      Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels
277  
278      Uses SmartMatrix Library for Teensy 3.1 written by Louis Beaudoin at pixelmatix.com
279  
280      Written by: Craig A. Lindley
281  
282      Copyright (c) 2014 Craig A. Lindley
283      Refactoring by Louis Beaudoin (Pixelmatix)
284  
285      Permission is hereby granted, free of charge, to any person obtaining a copy of
286      this software and associated documentation files (the "Software"), to deal in
287      the Software without restriction, including without limitation the rights to
288      use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
289      the Software, and to permit persons to whom the Software is furnished to do so,
290      subject to the following conditions:
291  
292      The above copyright notice and this permission notice shall be included in all
293      copies or substantial portions of the Software.
294  
295      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
296      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
297      FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
298      COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
299      IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
300      CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
301  */
302  
303  /*
304      This example displays 32x32 GIF animations loaded from a SD Card connected to the Teensy 3.1
305      The GIFs can be up to 32 pixels in width and height.
306      This code has been tested with 32x32 pixel and 16x16 pixel GIFs, but is optimized for 32x32 pixel GIFs.
307  
308      Wiring is on the default Teensy 3.1 SPI pins, and chip select can be on any GPIO,
309      set by defining SD_CS in the code below
310      Function     | Pin
311      DOUT         |  11
312      DIN          |  12
313      CLK          |  13
314      CS (default) |  15
315  
316      This code first looks for .gif files in the /gifs/ directory
317      (customize below with the GIF_DIRECTORY definition) then plays random GIFs in the directory,
318      looping each GIF for displayTimeSeconds
319  
320      This example is meant to give you an idea of how to add GIF playback to your own sketch.
321      For a project that adds GIF playback with other features, take a look at
322      Light Appliance and Aurora:
323      https://github.com/CraigLindley/LightAppliance
324      https://github.com/pixelmatix/aurora
325  
326      If you find any GIFs that won't play properly, please attach them to a new
327      Issue post in the GitHub repo here:
328      https://github.com/pixelmatix/AnimatedGIFs/issues
329  */
330  
331  /*
332      CONFIGURATION:
333      - If you're using SmartLED Shield V4 (or above), uncomment the line that includes <SmartMatrixShieldV4.h>
334      - update the "SmartMatrix configuration and memory allocation" section to match the width and height and other configuration of your display
335      - Note for 128x32 and 64x64 displays with Teensy 3.2 - need to reduce RAM:
336        set kRefreshDepth=24 and kDmaBufferRows=2 or set USB Type: "None" in Arduino,
337        decrease refreshRate in setup() to 90 or lower to get good an accurate GIF frame rate
338      - Set the chip select pin for your board.  On Teensy 3.5/3.6, the onboard microSD CS pin is "BUILTIN_SDCARD"
339  */