Added SPDX to 30 more files - spdx-27
This commit is contained in:
parent
a9b2066f11
commit
71def0261b
30 changed files with 669 additions and 542 deletions
|
|
@ -1,271 +1,275 @@
|
|||
/*
|
||||
* SDWebBrowse.ino
|
||||
*
|
||||
* This sketch uses the microSD card slot on the a WIZ5500 Ethernet shield
|
||||
* to serve up files over a very minimal browsing interface
|
||||
*
|
||||
* Some code is from Bill Greiman's SdFatLib examples, some is from the
|
||||
* Arduino Ethernet WebServer example and the rest is from Limor Fried
|
||||
* (Adafruit) so its probably under GPL
|
||||
*
|
||||
* Tutorial is at https://learn.adafruit.com/arduino-ethernet-sd-card/serving-files-over-ethernet
|
||||
*
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Ethernet.h>
|
||||
|
||||
/************ ETHERNET STUFF ************/
|
||||
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // change if necessary
|
||||
byte ip[] = { 192, 168, 1, 177 }; // change if necessary
|
||||
EthernetServer server(80);
|
||||
|
||||
/************ SDCARD STUFF ************/
|
||||
#define SDCARD_CS 4
|
||||
File root;
|
||||
|
||||
#if defined(ESP8266)
|
||||
// default for ESPressif
|
||||
#define WIZ_CS 15
|
||||
#elif defined(ESP32)
|
||||
#define WIZ_CS 33
|
||||
#elif defined(ARDUINO_STM32_FEATHER)
|
||||
// default for WICED
|
||||
#define WIZ_CS PB4
|
||||
#elif defined(TEENSYDUINO)
|
||||
#define WIZ_CS 10
|
||||
#elif defined(ARDUINO_FEATHER52)
|
||||
#define WIZ_CS 11
|
||||
#else // default for 328p, 32u4 and m0
|
||||
#define WIZ_CS 10
|
||||
#endif
|
||||
|
||||
// store error strings in flash to save RAM
|
||||
#define error(s) error_P(PSTR(s))
|
||||
|
||||
void error_P(const char* str) {
|
||||
Serial.print(F("error: "));
|
||||
Serial.println(str);
|
||||
|
||||
while(1);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial); // For 32u4 based microcontrollers like 32u4 Adalogger Feather
|
||||
|
||||
//Serial.print(F("Free RAM: ")); Serial.println(FreeRam());
|
||||
|
||||
if (!SD.begin(SDCARD_CS)) {
|
||||
error("card.init failed!");
|
||||
}
|
||||
|
||||
root = SD.open("/");
|
||||
printDirectory(root, 0);
|
||||
|
||||
// Recursive list of all directories
|
||||
Serial.println(F("Files found in all dirs:"));
|
||||
printDirectory(root, 0);
|
||||
|
||||
Serial.println();
|
||||
Serial.println(F("Done"));
|
||||
|
||||
// Debugging complete, we start the server!
|
||||
Serial.println(F("Initializing WizNet"));
|
||||
Ethernet.init(WIZ_CS);
|
||||
// give the ethernet module time to boot up
|
||||
delay(1000);
|
||||
// start the Ethernet connection
|
||||
// Use the fixed IP specified. If you want to use DHCP first
|
||||
// then switch the Ethernet.begin statements
|
||||
Ethernet.begin(mac, ip);
|
||||
// try to congifure using DHCP address instead of IP:
|
||||
// Ethernet.begin(mac);
|
||||
|
||||
// print the Ethernet board/shield's IP address to Serial monitor
|
||||
Serial.print(F("My IP address: "));
|
||||
Serial.println(Ethernet.localIP());
|
||||
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void ListFiles(EthernetClient client, uint8_t flags, File dir) {
|
||||
client.println("<ul>");
|
||||
while (true) {
|
||||
File entry = dir.openNextFile();
|
||||
|
||||
// done if past last used entry
|
||||
if (! entry) {
|
||||
// no more files
|
||||
break;
|
||||
}
|
||||
|
||||
// print any indent spaces
|
||||
client.print("<li><a href=\"");
|
||||
client.print(entry.name());
|
||||
if (entry.isDirectory()) {
|
||||
client.println("/");
|
||||
}
|
||||
client.print("\">");
|
||||
|
||||
// print file name with possible blank fill
|
||||
client.print(entry.name());
|
||||
if (entry.isDirectory()) {
|
||||
client.println("/");
|
||||
}
|
||||
|
||||
client.print("</a>");
|
||||
/*
|
||||
// print modify date/time if requested
|
||||
if (flags & LS_DATE) {
|
||||
dir.printFatDate(p.lastWriteDate);
|
||||
client.print(' ');
|
||||
dir.printFatTime(p.lastWriteTime);
|
||||
}
|
||||
// print size if requested
|
||||
if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
|
||||
client.print(' ');
|
||||
client.print(p.fileSize);
|
||||
}
|
||||
*/
|
||||
client.println("</li>");
|
||||
entry.close();
|
||||
}
|
||||
client.println("</ul>");
|
||||
}
|
||||
|
||||
// How big our line buffer should be. 100 is plenty!
|
||||
#define BUFSIZ 100
|
||||
|
||||
void loop()
|
||||
{
|
||||
char clientline[BUFSIZ];
|
||||
char name[17];
|
||||
int index = 0;
|
||||
|
||||
EthernetClient client = server.available();
|
||||
if (client) {
|
||||
// an http request ends with a blank line
|
||||
boolean current_line_is_blank = true;
|
||||
|
||||
// reset the input buffer
|
||||
index = 0;
|
||||
|
||||
while (client.connected()) {
|
||||
if (client.available()) {
|
||||
char c = client.read();
|
||||
|
||||
// If it isn't a new line, add the character to the buffer
|
||||
if (c != '\n' && c != '\r') {
|
||||
clientline[index] = c;
|
||||
index++;
|
||||
// are we too big for the buffer? start tossing out data
|
||||
if (index >= BUFSIZ)
|
||||
index = BUFSIZ -1;
|
||||
|
||||
// continue to read more data!
|
||||
continue;
|
||||
}
|
||||
|
||||
// got a \n or \r new line, which means the string is done
|
||||
clientline[index] = 0;
|
||||
|
||||
// Print it out for debugging
|
||||
Serial.println(clientline);
|
||||
|
||||
// Look for substring such as a request to get the file
|
||||
if (strstr(clientline, "GET /") != 0) {
|
||||
// this time no space after the /, so a sub-file!
|
||||
char *filename;
|
||||
|
||||
filename = clientline + 5; // look after the "GET /" (5 chars) *******
|
||||
// a little trick, look for the " HTTP/1.1" string and
|
||||
// turn the first character of the substring into a 0 to clear it out.
|
||||
(strstr(clientline, " HTTP"))[0] = 0;
|
||||
|
||||
if(filename[strlen(filename)-1] == '/') { // Trim a directory filename
|
||||
filename[strlen(filename)-1] = 0; // as Open throws error with trailing /
|
||||
}
|
||||
|
||||
Serial.print(F("Web request for: ")); Serial.println(filename); // print the file we want
|
||||
|
||||
File file = SD.open(filename, O_READ);
|
||||
if ( file == 0 ) { // Opening the file with return code of 0 is an error in SDFile.open
|
||||
client.println("HTTP/1.1 404 Not Found");
|
||||
client.println("Content-Type: text/html");
|
||||
client.println();
|
||||
client.println("<h2>File Not Found!</h2>");
|
||||
client.println("<br><h3>Couldn't open the File!</h3>");
|
||||
break;
|
||||
}
|
||||
|
||||
Serial.println("File Opened!");
|
||||
|
||||
client.println("HTTP/1.1 200 OK");
|
||||
if (file.isDirectory()) {
|
||||
Serial.println("is a directory");
|
||||
//file.close();
|
||||
client.println("Content-Type: text/html");
|
||||
client.println();
|
||||
client.print("<h2>Files in /");
|
||||
client.print(filename);
|
||||
client.println(":</h2>");
|
||||
ListFiles(client,LS_SIZE,file);
|
||||
file.close();
|
||||
} else { // Any non-directory clicked, server will send file to client for download
|
||||
client.println("Content-Type: application/octet-stream");
|
||||
client.println();
|
||||
|
||||
char file_buffer[16];
|
||||
int avail;
|
||||
while (avail = file.available()) {
|
||||
int to_read = min(avail, 16);
|
||||
if (to_read != file.read(file_buffer, to_read)) {
|
||||
break;
|
||||
}
|
||||
// uncomment the serial to debug (slow!)
|
||||
//Serial.write((char)c);
|
||||
client.write(file_buffer, to_read);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
// everything else is a 404
|
||||
client.println("HTTP/1.1 404 Not Found");
|
||||
client.println("Content-Type: text/html");
|
||||
client.println();
|
||||
client.println("<h2>File Not Found!</h2>");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// give the web browser time to receive the data
|
||||
delay(1);
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void printDirectory(File dir, int numTabs) {
|
||||
while(true) {
|
||||
File entry = dir.openNextFile();
|
||||
if (! entry) {
|
||||
// no more files
|
||||
break;
|
||||
}
|
||||
for (uint8_t i=0; i<numTabs; i++) {
|
||||
Serial.print('\t');
|
||||
}
|
||||
Serial.print(entry.name());
|
||||
if (entry.isDirectory()) {
|
||||
Serial.println("/");
|
||||
printDirectory(entry, numTabs+1);
|
||||
} else {
|
||||
// files have sizes, directories do not
|
||||
Serial.print("\t\t");
|
||||
Serial.println(entry.size(), DEC);
|
||||
}
|
||||
entry.close();
|
||||
}
|
||||
}
|
||||
// SPDX-FileCopyrightText: 2018 Anne Barela for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
* SDWebBrowse.ino
|
||||
*
|
||||
* This sketch uses the microSD card slot on the a WIZ5500 Ethernet shield
|
||||
* to serve up files over a very minimal browsing interface
|
||||
*
|
||||
* Some code is from Bill Greiman's SdFatLib examples, some is from the
|
||||
* Arduino Ethernet WebServer example and the rest is from Limor Fried
|
||||
* (Adafruit) so its probably under GPL
|
||||
*
|
||||
* Tutorial is at https://learn.adafruit.com/arduino-ethernet-sd-card/serving-files-over-ethernet
|
||||
*
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Ethernet.h>
|
||||
|
||||
/************ ETHERNET STUFF ************/
|
||||
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // change if necessary
|
||||
byte ip[] = { 192, 168, 1, 177 }; // change if necessary
|
||||
EthernetServer server(80);
|
||||
|
||||
/************ SDCARD STUFF ************/
|
||||
#define SDCARD_CS 4
|
||||
File root;
|
||||
|
||||
#if defined(ESP8266)
|
||||
// default for ESPressif
|
||||
#define WIZ_CS 15
|
||||
#elif defined(ESP32)
|
||||
#define WIZ_CS 33
|
||||
#elif defined(ARDUINO_STM32_FEATHER)
|
||||
// default for WICED
|
||||
#define WIZ_CS PB4
|
||||
#elif defined(TEENSYDUINO)
|
||||
#define WIZ_CS 10
|
||||
#elif defined(ARDUINO_FEATHER52)
|
||||
#define WIZ_CS 11
|
||||
#else // default for 328p, 32u4 and m0
|
||||
#define WIZ_CS 10
|
||||
#endif
|
||||
|
||||
// store error strings in flash to save RAM
|
||||
#define error(s) error_P(PSTR(s))
|
||||
|
||||
void error_P(const char* str) {
|
||||
Serial.print(F("error: "));
|
||||
Serial.println(str);
|
||||
|
||||
while(1);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial); // For 32u4 based microcontrollers like 32u4 Adalogger Feather
|
||||
|
||||
//Serial.print(F("Free RAM: ")); Serial.println(FreeRam());
|
||||
|
||||
if (!SD.begin(SDCARD_CS)) {
|
||||
error("card.init failed!");
|
||||
}
|
||||
|
||||
root = SD.open("/");
|
||||
printDirectory(root, 0);
|
||||
|
||||
// Recursive list of all directories
|
||||
Serial.println(F("Files found in all dirs:"));
|
||||
printDirectory(root, 0);
|
||||
|
||||
Serial.println();
|
||||
Serial.println(F("Done"));
|
||||
|
||||
// Debugging complete, we start the server!
|
||||
Serial.println(F("Initializing WizNet"));
|
||||
Ethernet.init(WIZ_CS);
|
||||
// give the ethernet module time to boot up
|
||||
delay(1000);
|
||||
// start the Ethernet connection
|
||||
// Use the fixed IP specified. If you want to use DHCP first
|
||||
// then switch the Ethernet.begin statements
|
||||
Ethernet.begin(mac, ip);
|
||||
// try to congifure using DHCP address instead of IP:
|
||||
// Ethernet.begin(mac);
|
||||
|
||||
// print the Ethernet board/shield's IP address to Serial monitor
|
||||
Serial.print(F("My IP address: "));
|
||||
Serial.println(Ethernet.localIP());
|
||||
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void ListFiles(EthernetClient client, uint8_t flags, File dir) {
|
||||
client.println("<ul>");
|
||||
while (true) {
|
||||
File entry = dir.openNextFile();
|
||||
|
||||
// done if past last used entry
|
||||
if (! entry) {
|
||||
// no more files
|
||||
break;
|
||||
}
|
||||
|
||||
// print any indent spaces
|
||||
client.print("<li><a href=\"");
|
||||
client.print(entry.name());
|
||||
if (entry.isDirectory()) {
|
||||
client.println("/");
|
||||
}
|
||||
client.print("\">");
|
||||
|
||||
// print file name with possible blank fill
|
||||
client.print(entry.name());
|
||||
if (entry.isDirectory()) {
|
||||
client.println("/");
|
||||
}
|
||||
|
||||
client.print("</a>");
|
||||
/*
|
||||
// print modify date/time if requested
|
||||
if (flags & LS_DATE) {
|
||||
dir.printFatDate(p.lastWriteDate);
|
||||
client.print(' ');
|
||||
dir.printFatTime(p.lastWriteTime);
|
||||
}
|
||||
// print size if requested
|
||||
if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
|
||||
client.print(' ');
|
||||
client.print(p.fileSize);
|
||||
}
|
||||
*/
|
||||
client.println("</li>");
|
||||
entry.close();
|
||||
}
|
||||
client.println("</ul>");
|
||||
}
|
||||
|
||||
// How big our line buffer should be. 100 is plenty!
|
||||
#define BUFSIZ 100
|
||||
|
||||
void loop()
|
||||
{
|
||||
char clientline[BUFSIZ];
|
||||
char name[17];
|
||||
int index = 0;
|
||||
|
||||
EthernetClient client = server.available();
|
||||
if (client) {
|
||||
// an http request ends with a blank line
|
||||
boolean current_line_is_blank = true;
|
||||
|
||||
// reset the input buffer
|
||||
index = 0;
|
||||
|
||||
while (client.connected()) {
|
||||
if (client.available()) {
|
||||
char c = client.read();
|
||||
|
||||
// If it isn't a new line, add the character to the buffer
|
||||
if (c != '\n' && c != '\r') {
|
||||
clientline[index] = c;
|
||||
index++;
|
||||
// are we too big for the buffer? start tossing out data
|
||||
if (index >= BUFSIZ)
|
||||
index = BUFSIZ -1;
|
||||
|
||||
// continue to read more data!
|
||||
continue;
|
||||
}
|
||||
|
||||
// got a \n or \r new line, which means the string is done
|
||||
clientline[index] = 0;
|
||||
|
||||
// Print it out for debugging
|
||||
Serial.println(clientline);
|
||||
|
||||
// Look for substring such as a request to get the file
|
||||
if (strstr(clientline, "GET /") != 0) {
|
||||
// this time no space after the /, so a sub-file!
|
||||
char *filename;
|
||||
|
||||
filename = clientline + 5; // look after the "GET /" (5 chars) *******
|
||||
// a little trick, look for the " HTTP/1.1" string and
|
||||
// turn the first character of the substring into a 0 to clear it out.
|
||||
(strstr(clientline, " HTTP"))[0] = 0;
|
||||
|
||||
if(filename[strlen(filename)-1] == '/') { // Trim a directory filename
|
||||
filename[strlen(filename)-1] = 0; // as Open throws error with trailing /
|
||||
}
|
||||
|
||||
Serial.print(F("Web request for: ")); Serial.println(filename); // print the file we want
|
||||
|
||||
File file = SD.open(filename, O_READ);
|
||||
if ( file == 0 ) { // Opening the file with return code of 0 is an error in SDFile.open
|
||||
client.println("HTTP/1.1 404 Not Found");
|
||||
client.println("Content-Type: text/html");
|
||||
client.println();
|
||||
client.println("<h2>File Not Found!</h2>");
|
||||
client.println("<br><h3>Couldn't open the File!</h3>");
|
||||
break;
|
||||
}
|
||||
|
||||
Serial.println("File Opened!");
|
||||
|
||||
client.println("HTTP/1.1 200 OK");
|
||||
if (file.isDirectory()) {
|
||||
Serial.println("is a directory");
|
||||
//file.close();
|
||||
client.println("Content-Type: text/html");
|
||||
client.println();
|
||||
client.print("<h2>Files in /");
|
||||
client.print(filename);
|
||||
client.println(":</h2>");
|
||||
ListFiles(client,LS_SIZE,file);
|
||||
file.close();
|
||||
} else { // Any non-directory clicked, server will send file to client for download
|
||||
client.println("Content-Type: application/octet-stream");
|
||||
client.println();
|
||||
|
||||
char file_buffer[16];
|
||||
int avail;
|
||||
while (avail = file.available()) {
|
||||
int to_read = min(avail, 16);
|
||||
if (to_read != file.read(file_buffer, to_read)) {
|
||||
break;
|
||||
}
|
||||
// uncomment the serial to debug (slow!)
|
||||
//Serial.write((char)c);
|
||||
client.write(file_buffer, to_read);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
// everything else is a 404
|
||||
client.println("HTTP/1.1 404 Not Found");
|
||||
client.println("Content-Type: text/html");
|
||||
client.println();
|
||||
client.println("<h2>File Not Found!</h2>");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// give the web browser time to receive the data
|
||||
delay(1);
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void printDirectory(File dir, int numTabs) {
|
||||
while(true) {
|
||||
File entry = dir.openNextFile();
|
||||
if (! entry) {
|
||||
// no more files
|
||||
break;
|
||||
}
|
||||
for (uint8_t i=0; i<numTabs; i++) {
|
||||
Serial.print('\t');
|
||||
}
|
||||
Serial.print(entry.name());
|
||||
if (entry.isDirectory()) {
|
||||
Serial.println("/");
|
||||
printDirectory(entry, numTabs+1);
|
||||
} else {
|
||||
// files have sizes, directories do not
|
||||
Serial.print("\t\t");
|
||||
Serial.println(entry.size(), DEC);
|
||||
}
|
||||
entry.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
// SPDX-FileCopyrightText: 2011 Limor Fried for Adafruit Industries
|
||||
// SPDX-FileCopyrightText: 2012 Tom Igoe
|
||||
// SPDX-FileCopyrightText: 2018 Anne Barela for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
SD card test for WIZ5500 Compatible Ethernet Boards
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2010 David A. Mellis
|
||||
// SPDX-FileCopyrightText: 2012 Tom Igoe
|
||||
// SPDX-FileCopyrightText: 2014 Scott Fitzgerald
|
||||
// SPDX-FileCopyrightText: 2018 Anne Barela for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: Unlicense
|
||||
|
||||
/* SDlistFiles
|
||||
|
||||
This example shows how print out the files in a directory on a SD card
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 John Edgar Park for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
import board
|
||||
import busio
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
This example acts as a BLE HID keyboard to peer devices.
|
||||
Attach five buttons with pullup resistors to Feather nRF52840
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2021 Jeff Epler for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import random
|
||||
from adafruit_led_animation.animation.rainbowcomet import RainbowComet
|
||||
from adafruit_led_animation.helper import PixelMap
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 Noe Ruiz for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
A CircuitPython 'multimedia' dial demo
|
||||
Uses a ItsyBitsy M0 + Rotary Encoder -> HID keyboard out with neopixel ring
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2017 Mikey Sklar for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// constants won't change. They're used here to
|
||||
// set pin numbers:
|
||||
const int buttonPin = 1; // the number of the pushbutton pin
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
|
||||
import board
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Trinket IO demo - analog inputs
|
||||
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Trinket IO demo - analog output
|
||||
|
||||
import board
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Trinket IO demo - captouch
|
||||
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import os
|
||||
|
||||
import adafruit_sdcard
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
|
||||
import adafruit_sdcard
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Trinket IO demo - captouch to dotstar
|
||||
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Trinket IO demo - USB/Serial echo
|
||||
|
||||
import board
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
NeoPixel Animator code for ItsyBitsy nRF52840 NeoPixel Animation and Color Remote Control.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import pygame, sys
|
||||
from pygame.locals import *
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
import random
|
||||
class FlowMeter():
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
#!/usr/bin/python
|
||||
import os
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -1,271 +1,275 @@
|
|||
"""
|
||||
NEXTBUS SCHEDULER for Adafruit MagTag: Shows arrival predictions for
|
||||
up to four lines/stops. Requires WiFi internet access.
|
||||
|
||||
Written by Phil 'PaintYourDragon' Burgess for Adafruit Industries.
|
||||
MIT license, all text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
# pylint: disable=import-error
|
||||
import gc
|
||||
import time
|
||||
from secrets import secrets
|
||||
import displayio
|
||||
from rtc import RTC
|
||||
from adafruit_magtag.magtag import Graphics
|
||||
from adafruit_magtag.magtag import Network
|
||||
from adafruit_magtag.magtag import Peripherals
|
||||
from adafruit_bitmap_font import bitmap_font
|
||||
from adafruit_display_text.label import Label
|
||||
from nextbus import NextBus
|
||||
|
||||
|
||||
# CONFIGURABLE SETTINGS ----------------------------------------------------
|
||||
|
||||
# List of bus lines/stops to predict. Use nextbus_routefinder.py
|
||||
# (desktop Python) to look up lines/stops for your location, copy & paste
|
||||
# results here. The 4th element on each line can then be edited for
|
||||
# brevity if desired (screen space is tight!).
|
||||
STOPS = [
|
||||
('lametro', '79', '11086', 'Downtown'),
|
||||
('lametro', '79', '2549', 'Arcadia'),
|
||||
('lametro', '260', '11086', 'Altadena'),
|
||||
('lametro', '260', '2549', 'Artesia')
|
||||
]
|
||||
|
||||
# How often to query the NextBus server, in seconds.
|
||||
# Balance accuracy vs. limiting bandwidth & battery power.
|
||||
QUERY_INTERVAL = 4 * 60
|
||||
|
||||
# Maximum number of predictions to display (NextBus allows up to 5, I think,
|
||||
# but there's only so much screen and we probably don't care that far ahead).
|
||||
MAX_PREDICTIONS = 3
|
||||
|
||||
# Don't display any times below this threshold, in seconds.
|
||||
# This is to discourage unsafe bus-chasing behavior! 5 minute default.
|
||||
MINIMUM_TIME = 5 * 60
|
||||
|
||||
# How often to sync clock with time server, in seconds. Clock is only used
|
||||
# for 'Last checked' display, this does NOT affect predictions, so it's
|
||||
# not a big problem if this drifts a bit due to infrequent synchronizations.
|
||||
# 6 hour default.
|
||||
CLOCK_SYNC_INTERVAL = 6 * 60 * 60
|
||||
# Load time zone string from secrets.py, else IP geolocation is used
|
||||
# (http://worldtimeapi.org/api/timezone for list). Again, this is only
|
||||
# used for the 'Last checked' display, not predictions, so it's not
|
||||
# especially disruptive if missing.
|
||||
# pylint: disable=bare-except
|
||||
try:
|
||||
TIME_ZONE = secrets['timezone'] # e.g. 'America/New_York'
|
||||
except:
|
||||
TIME_ZONE = None # Use IP geolocation
|
||||
|
||||
|
||||
# SOME UTILITY FUNCTIONS ---------------------------------------------------
|
||||
|
||||
def fillrect(xpos, ypos, width, height, color):
|
||||
""" Generate a solid rectangle that's (usually) more RAM-efficient
|
||||
than allocating a full bitmap of the requested size.
|
||||
Returns a TileGrid that can be appended to a Group.
|
||||
"""
|
||||
palette = displayio.Palette(1)
|
||||
palette[0] = color
|
||||
# Allocate a bitmap, rectangle's width by 1 pixel tall
|
||||
bitmap = displayio.Bitmap(width, 1, 1)
|
||||
# Allocate and return a TileGrid, 1 cell wide by rectangle's height
|
||||
# cells tall. Each cell value is 0 by default, which points to our
|
||||
# full-width rectangle.
|
||||
# A more thoughtful implementation would optimize for wide vs tall
|
||||
# vs full-rect bitmaps, whichever is most RAM-efficient for the
|
||||
# situation, which would require some behind-the-scenes detailed
|
||||
# knowledge of Bitmap and TileGrid memory requirements. But for now...
|
||||
return displayio.TileGrid(bitmap, pixel_shader=palette, x=xpos, y=ypos,
|
||||
width=1, height=height)
|
||||
|
||||
def parse_time(timestring, is_dst=-1):
|
||||
""" Given a string of the format YYYY-MM-DDTHH:MM:SS.SS-HH:MM (and
|
||||
optionally a DST flag), convert to and return an equivalent
|
||||
time.struct_time (strptime() isn't available here). Calling function
|
||||
can use time.mktime() on result if epoch seconds is needed instead.
|
||||
Time string is assumed local time; UTC offset is ignored. If seconds
|
||||
value includes a decimal fraction it's ignored.
|
||||
"""
|
||||
date_time = timestring.split('T') # Separate into date and time
|
||||
year_month_day = date_time[0].split('-') # Separate time into Y/M/D
|
||||
hour_minute_second = date_time[1].split('+')[0].split('-')[0].split(':')
|
||||
return time.struct_time(int(year_month_day[0]),
|
||||
int(year_month_day[1]),
|
||||
int(year_month_day[2]),
|
||||
int(hour_minute_second[0]),
|
||||
int(hour_minute_second[1]),
|
||||
int(hour_minute_second[2].split('.')[0]),
|
||||
-1, -1, is_dst)
|
||||
|
||||
def update_time(timezone=None):
|
||||
""" Update system date/time from WorldTimeAPI public server;
|
||||
no account required. Pass in time zone string
|
||||
(http://worldtimeapi.org/api/timezone for list)
|
||||
or None to use IP geolocation. Returns current local time as a
|
||||
time.struct_time and UTC offset as string. This may throw an
|
||||
exception on fetch_data() - it is NOT CAUGHT HERE, should be
|
||||
handled in the calling code because different behaviors may be
|
||||
needed in different situations (e.g. reschedule for later).
|
||||
"""
|
||||
if timezone: # Use timezone api
|
||||
time_url = 'http://worldtimeapi.org/api/timezone/' + timezone
|
||||
else: # Use IP geolocation
|
||||
time_url = 'http://worldtimeapi.org/api/ip'
|
||||
|
||||
time_data = NETWORK.fetch_data(time_url,
|
||||
json_path=[['datetime'], ['dst'],
|
||||
['utc_offset']])
|
||||
time_struct = parse_time(time_data[0], time_data[1])
|
||||
RTC().datetime = time_struct
|
||||
return time_struct, time_data[2]
|
||||
|
||||
def hh_mm(time_struct, twelve_hour=True):
|
||||
""" Given a time.struct_time, return a string as H:MM or HH:MM, either
|
||||
12- or 24-hour style depending on twelve_hour flag. This is ONLY
|
||||
for 'clock time,' NOT for countdown time.
|
||||
"""
|
||||
if twelve_hour:
|
||||
if time_struct.tm_hour > 12:
|
||||
hour_string = str(time_struct.tm_hour - 12) # 13-23 -> 1-11 (pm)
|
||||
elif time_struct.tm_hour > 0:
|
||||
hour_string = str(time_struct.tm_hour) # 1-12
|
||||
else:
|
||||
hour_string = '12' # 0 -> 12 (am)
|
||||
else:
|
||||
hour_string = '{hh:02d}'.format(hh=time_struct.tm_hour)
|
||||
return hour_string + ':{mm:02d}'.format(mm=time_struct.tm_min)
|
||||
|
||||
|
||||
# ONE-TIME INITIALIZATION --------------------------------------------------
|
||||
|
||||
PERIPHERALS = Peripherals()
|
||||
NETWORK = Network(status_neopixel=None)
|
||||
GRAPHICS = Graphics(auto_refresh=False)
|
||||
DISPLAY = GRAPHICS.display
|
||||
|
||||
FONT_SMALL = bitmap_font.load_font('/fonts/Impact-16.pcf')
|
||||
FONT_MEDIUM = bitmap_font.load_font('/fonts/Impact-24.pcf')
|
||||
FONT_LARGE = bitmap_font.load_font('/fonts/Impact-30.pcf')
|
||||
|
||||
# displayio group holds all the labels for the stops and predictions...
|
||||
GROUP = displayio.Group()
|
||||
GROUP.append(fillrect(0, 0, DISPLAY.width, DISPLAY.height, 0xFFFFFF))
|
||||
# Clear the screen ASAP before populating rest of group (erase any old
|
||||
# prediction data)...
|
||||
DISPLAY.show(GROUP)
|
||||
DISPLAY.refresh()
|
||||
time.sleep(5) # Don't allow another refresh() too soon
|
||||
|
||||
# Populate list of NextBus objects from STOPS[] and generate initial text
|
||||
# labels (these get positioned in a second pass later)...
|
||||
STOP_LIST = []
|
||||
MAX_SIZE = (0, 0) # Pixel dimensions of largest route number
|
||||
for stop in STOPS:
|
||||
STOP_LIST.append(NextBus(NETWORK, stop[0], stop[1], stop[2], None,
|
||||
MAX_PREDICTIONS, MINIMUM_TIME))
|
||||
TEXT = Label(FONT_LARGE, text=stop[1], color=0)
|
||||
# Keep track of the largest route label for positioning things later
|
||||
MAX_SIZE = (max(TEXT.width, MAX_SIZE[0]), max(TEXT.height, MAX_SIZE[1]))
|
||||
TEXT.anchor_point = (1.0, 1.0) # Bottom-right align route
|
||||
GROUP.append(TEXT)
|
||||
# Because text anchoring works from bounding rectangles rather than
|
||||
# the font baseline, we use a kludge here of upper-casing the route
|
||||
# description to eliminate descenders that would throw off alignment.
|
||||
TEXT = Label(FONT_SMALL, text=stop[3].upper(), color=0)
|
||||
TEXT.anchor_point = (0.0, 1.0) # Bottom-left align description
|
||||
GROUP.append(TEXT)
|
||||
INITIAL = 'No predictions'
|
||||
TEXT = Label(FONT_MEDIUM, text=INITIAL, color=0)
|
||||
TEXT.anchor_point = (1.0, 1.0) # Bottom-right align predictions
|
||||
GROUP.append(TEXT)
|
||||
|
||||
# "Last checked" time at bottom of screen
|
||||
TEXT = Label(FONT_SMALL, text='Last checked 00:00', color=0)
|
||||
TEXT.anchor_point = (0.5, 1.0) # Bottom-center align last-checked time
|
||||
TEXT.anchored_position = (DISPLAY.width // 2, DISPLAY.height - 1)
|
||||
GROUP.append(TEXT)
|
||||
|
||||
# Second pass through STOPS to position the corresponding text elements...
|
||||
SPACING = min(MAX_SIZE[1] + 4,
|
||||
(DISPLAY.height - TEXT.height - 4 - MAX_SIZE[1]) / 3)
|
||||
# TEXT.width/height doesn't seem to be working correctly with bitmap fonts
|
||||
# right now, so for now this does a brute-force override thing. Once that's
|
||||
# resolved, these two lines can be removed:
|
||||
MAX_SIZE = (48, 24)
|
||||
SPACING = 28
|
||||
for stop_index, stop in enumerate(STOPS):
|
||||
baseline = MAX_SIZE[1] + SPACING * stop_index
|
||||
GROUP[1 + stop_index * 3].anchored_position = (MAX_SIZE[0], baseline)
|
||||
GROUP[2 + stop_index * 3].anchored_position = (MAX_SIZE[0] + 4, baseline)
|
||||
# Third element (predictions) is NOT positioned here...see main loop
|
||||
|
||||
DISPLAY.show(GROUP)
|
||||
|
||||
NETWORK.connect()
|
||||
|
||||
# Force initial server queries for time and predictions
|
||||
LAST_SYNC_TIME = -CLOCK_SYNC_INTERVAL
|
||||
LAST_QUERY_TIME = -QUERY_INTERVAL
|
||||
|
||||
|
||||
# MAIN LOOP ----------------------------------------------------------------
|
||||
|
||||
while True:
|
||||
|
||||
# Periodically sync clock with time server
|
||||
if time.monotonic() - LAST_SYNC_TIME >= CLOCK_SYNC_INTERVAL:
|
||||
try:
|
||||
update_time(TIME_ZONE)
|
||||
LAST_SYNC_TIME = time.monotonic()
|
||||
except:
|
||||
# Time sync error isn't fatal, retry in 30 mins
|
||||
LAST_SYNC_TIME += 30 * 60
|
||||
|
||||
# Periodically poll all the stops, rather than staggering the queries
|
||||
# like the Raspberry Pi version does, since the screen is updated much
|
||||
# less frequently.
|
||||
if time.monotonic() - LAST_QUERY_TIME >= QUERY_INTERVAL:
|
||||
LAST_QUERY_TIME = time.monotonic()
|
||||
for stop in STOP_LIST:
|
||||
stop.fetch()
|
||||
TEXT.text = 'Last checked ' + hh_mm(time.localtime())
|
||||
|
||||
# Update displayed predictions on every pass though...
|
||||
for stop_index, stop in enumerate(STOP_LIST):
|
||||
times = stop.predict()
|
||||
group_index = 3 + stop_index * 3 # GROUP element for prediction text
|
||||
baseline = MAX_SIZE[1] + SPACING * stop_index
|
||||
if times:
|
||||
label = ''
|
||||
for time_index, time_seconds in enumerate(times):
|
||||
if time_index:
|
||||
label += ', '
|
||||
time_minutes = int(time_seconds // 60)
|
||||
if time_minutes > 60:
|
||||
# 'HHhMM' format is used rather than 'HH:MM' because
|
||||
# latter might be confused with 'clock time' rather
|
||||
# than countdown.
|
||||
label += '{hh}h{mm:02d}'.format(hh=time_minutes // 60,
|
||||
mm=time_minutes % 60)
|
||||
else:
|
||||
label += str(time_minutes)
|
||||
GROUP[group_index].text = label
|
||||
else:
|
||||
GROUP[group_index].text = 'No predictions'
|
||||
|
||||
# Because text anchoring currently uses bounding rect (not baseline),
|
||||
# the prediction text is scooted down by 2 pixels to compensate for
|
||||
# any commas (if present). Not ideal, assumes certain things about
|
||||
# font used here. Text without commas is NOT scooted down, to
|
||||
# maintain baseline across entire row.
|
||||
offset = 2 if ',' in GROUP[group_index].text else 0
|
||||
GROUP[group_index].anchored_position = (DISPLAY.width - 1,
|
||||
baseline + offset)
|
||||
|
||||
DISPLAY.refresh()
|
||||
gc.collect()
|
||||
time.sleep(60) # Update predictions about once a minute
|
||||
# SPDX-FileCopyrightText: 2020 Phillip Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
NEXTBUS SCHEDULER for Adafruit MagTag: Shows arrival predictions for
|
||||
up to four lines/stops. Requires WiFi internet access.
|
||||
|
||||
Written by Phil 'PaintYourDragon' Burgess for Adafruit Industries.
|
||||
MIT license, all text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
# pylint: disable=import-error
|
||||
import gc
|
||||
import time
|
||||
from secrets import secrets
|
||||
import displayio
|
||||
from rtc import RTC
|
||||
from adafruit_magtag.magtag import Graphics
|
||||
from adafruit_magtag.magtag import Network
|
||||
from adafruit_magtag.magtag import Peripherals
|
||||
from adafruit_bitmap_font import bitmap_font
|
||||
from adafruit_display_text.label import Label
|
||||
from nextbus import NextBus
|
||||
|
||||
|
||||
# CONFIGURABLE SETTINGS ----------------------------------------------------
|
||||
|
||||
# List of bus lines/stops to predict. Use nextbus_routefinder.py
|
||||
# (desktop Python) to look up lines/stops for your location, copy & paste
|
||||
# results here. The 4th element on each line can then be edited for
|
||||
# brevity if desired (screen space is tight!).
|
||||
STOPS = [
|
||||
('lametro', '79', '11086', 'Downtown'),
|
||||
('lametro', '79', '2549', 'Arcadia'),
|
||||
('lametro', '260', '11086', 'Altadena'),
|
||||
('lametro', '260', '2549', 'Artesia')
|
||||
]
|
||||
|
||||
# How often to query the NextBus server, in seconds.
|
||||
# Balance accuracy vs. limiting bandwidth & battery power.
|
||||
QUERY_INTERVAL = 4 * 60
|
||||
|
||||
# Maximum number of predictions to display (NextBus allows up to 5, I think,
|
||||
# but there's only so much screen and we probably don't care that far ahead).
|
||||
MAX_PREDICTIONS = 3
|
||||
|
||||
# Don't display any times below this threshold, in seconds.
|
||||
# This is to discourage unsafe bus-chasing behavior! 5 minute default.
|
||||
MINIMUM_TIME = 5 * 60
|
||||
|
||||
# How often to sync clock with time server, in seconds. Clock is only used
|
||||
# for 'Last checked' display, this does NOT affect predictions, so it's
|
||||
# not a big problem if this drifts a bit due to infrequent synchronizations.
|
||||
# 6 hour default.
|
||||
CLOCK_SYNC_INTERVAL = 6 * 60 * 60
|
||||
# Load time zone string from secrets.py, else IP geolocation is used
|
||||
# (http://worldtimeapi.org/api/timezone for list). Again, this is only
|
||||
# used for the 'Last checked' display, not predictions, so it's not
|
||||
# especially disruptive if missing.
|
||||
# pylint: disable=bare-except
|
||||
try:
|
||||
TIME_ZONE = secrets['timezone'] # e.g. 'America/New_York'
|
||||
except:
|
||||
TIME_ZONE = None # Use IP geolocation
|
||||
|
||||
|
||||
# SOME UTILITY FUNCTIONS ---------------------------------------------------
|
||||
|
||||
def fillrect(xpos, ypos, width, height, color):
|
||||
""" Generate a solid rectangle that's (usually) more RAM-efficient
|
||||
than allocating a full bitmap of the requested size.
|
||||
Returns a TileGrid that can be appended to a Group.
|
||||
"""
|
||||
palette = displayio.Palette(1)
|
||||
palette[0] = color
|
||||
# Allocate a bitmap, rectangle's width by 1 pixel tall
|
||||
bitmap = displayio.Bitmap(width, 1, 1)
|
||||
# Allocate and return a TileGrid, 1 cell wide by rectangle's height
|
||||
# cells tall. Each cell value is 0 by default, which points to our
|
||||
# full-width rectangle.
|
||||
# A more thoughtful implementation would optimize for wide vs tall
|
||||
# vs full-rect bitmaps, whichever is most RAM-efficient for the
|
||||
# situation, which would require some behind-the-scenes detailed
|
||||
# knowledge of Bitmap and TileGrid memory requirements. But for now...
|
||||
return displayio.TileGrid(bitmap, pixel_shader=palette, x=xpos, y=ypos,
|
||||
width=1, height=height)
|
||||
|
||||
def parse_time(timestring, is_dst=-1):
|
||||
""" Given a string of the format YYYY-MM-DDTHH:MM:SS.SS-HH:MM (and
|
||||
optionally a DST flag), convert to and return an equivalent
|
||||
time.struct_time (strptime() isn't available here). Calling function
|
||||
can use time.mktime() on result if epoch seconds is needed instead.
|
||||
Time string is assumed local time; UTC offset is ignored. If seconds
|
||||
value includes a decimal fraction it's ignored.
|
||||
"""
|
||||
date_time = timestring.split('T') # Separate into date and time
|
||||
year_month_day = date_time[0].split('-') # Separate time into Y/M/D
|
||||
hour_minute_second = date_time[1].split('+')[0].split('-')[0].split(':')
|
||||
return time.struct_time(int(year_month_day[0]),
|
||||
int(year_month_day[1]),
|
||||
int(year_month_day[2]),
|
||||
int(hour_minute_second[0]),
|
||||
int(hour_minute_second[1]),
|
||||
int(hour_minute_second[2].split('.')[0]),
|
||||
-1, -1, is_dst)
|
||||
|
||||
def update_time(timezone=None):
|
||||
""" Update system date/time from WorldTimeAPI public server;
|
||||
no account required. Pass in time zone string
|
||||
(http://worldtimeapi.org/api/timezone for list)
|
||||
or None to use IP geolocation. Returns current local time as a
|
||||
time.struct_time and UTC offset as string. This may throw an
|
||||
exception on fetch_data() - it is NOT CAUGHT HERE, should be
|
||||
handled in the calling code because different behaviors may be
|
||||
needed in different situations (e.g. reschedule for later).
|
||||
"""
|
||||
if timezone: # Use timezone api
|
||||
time_url = 'http://worldtimeapi.org/api/timezone/' + timezone
|
||||
else: # Use IP geolocation
|
||||
time_url = 'http://worldtimeapi.org/api/ip'
|
||||
|
||||
time_data = NETWORK.fetch_data(time_url,
|
||||
json_path=[['datetime'], ['dst'],
|
||||
['utc_offset']])
|
||||
time_struct = parse_time(time_data[0], time_data[1])
|
||||
RTC().datetime = time_struct
|
||||
return time_struct, time_data[2]
|
||||
|
||||
def hh_mm(time_struct, twelve_hour=True):
|
||||
""" Given a time.struct_time, return a string as H:MM or HH:MM, either
|
||||
12- or 24-hour style depending on twelve_hour flag. This is ONLY
|
||||
for 'clock time,' NOT for countdown time.
|
||||
"""
|
||||
if twelve_hour:
|
||||
if time_struct.tm_hour > 12:
|
||||
hour_string = str(time_struct.tm_hour - 12) # 13-23 -> 1-11 (pm)
|
||||
elif time_struct.tm_hour > 0:
|
||||
hour_string = str(time_struct.tm_hour) # 1-12
|
||||
else:
|
||||
hour_string = '12' # 0 -> 12 (am)
|
||||
else:
|
||||
hour_string = '{hh:02d}'.format(hh=time_struct.tm_hour)
|
||||
return hour_string + ':{mm:02d}'.format(mm=time_struct.tm_min)
|
||||
|
||||
|
||||
# ONE-TIME INITIALIZATION --------------------------------------------------
|
||||
|
||||
PERIPHERALS = Peripherals()
|
||||
NETWORK = Network(status_neopixel=None)
|
||||
GRAPHICS = Graphics(auto_refresh=False)
|
||||
DISPLAY = GRAPHICS.display
|
||||
|
||||
FONT_SMALL = bitmap_font.load_font('/fonts/Impact-16.pcf')
|
||||
FONT_MEDIUM = bitmap_font.load_font('/fonts/Impact-24.pcf')
|
||||
FONT_LARGE = bitmap_font.load_font('/fonts/Impact-30.pcf')
|
||||
|
||||
# displayio group holds all the labels for the stops and predictions...
|
||||
GROUP = displayio.Group()
|
||||
GROUP.append(fillrect(0, 0, DISPLAY.width, DISPLAY.height, 0xFFFFFF))
|
||||
# Clear the screen ASAP before populating rest of group (erase any old
|
||||
# prediction data)...
|
||||
DISPLAY.show(GROUP)
|
||||
DISPLAY.refresh()
|
||||
time.sleep(5) # Don't allow another refresh() too soon
|
||||
|
||||
# Populate list of NextBus objects from STOPS[] and generate initial text
|
||||
# labels (these get positioned in a second pass later)...
|
||||
STOP_LIST = []
|
||||
MAX_SIZE = (0, 0) # Pixel dimensions of largest route number
|
||||
for stop in STOPS:
|
||||
STOP_LIST.append(NextBus(NETWORK, stop[0], stop[1], stop[2], None,
|
||||
MAX_PREDICTIONS, MINIMUM_TIME))
|
||||
TEXT = Label(FONT_LARGE, text=stop[1], color=0)
|
||||
# Keep track of the largest route label for positioning things later
|
||||
MAX_SIZE = (max(TEXT.width, MAX_SIZE[0]), max(TEXT.height, MAX_SIZE[1]))
|
||||
TEXT.anchor_point = (1.0, 1.0) # Bottom-right align route
|
||||
GROUP.append(TEXT)
|
||||
# Because text anchoring works from bounding rectangles rather than
|
||||
# the font baseline, we use a kludge here of upper-casing the route
|
||||
# description to eliminate descenders that would throw off alignment.
|
||||
TEXT = Label(FONT_SMALL, text=stop[3].upper(), color=0)
|
||||
TEXT.anchor_point = (0.0, 1.0) # Bottom-left align description
|
||||
GROUP.append(TEXT)
|
||||
INITIAL = 'No predictions'
|
||||
TEXT = Label(FONT_MEDIUM, text=INITIAL, color=0)
|
||||
TEXT.anchor_point = (1.0, 1.0) # Bottom-right align predictions
|
||||
GROUP.append(TEXT)
|
||||
|
||||
# "Last checked" time at bottom of screen
|
||||
TEXT = Label(FONT_SMALL, text='Last checked 00:00', color=0)
|
||||
TEXT.anchor_point = (0.5, 1.0) # Bottom-center align last-checked time
|
||||
TEXT.anchored_position = (DISPLAY.width // 2, DISPLAY.height - 1)
|
||||
GROUP.append(TEXT)
|
||||
|
||||
# Second pass through STOPS to position the corresponding text elements...
|
||||
SPACING = min(MAX_SIZE[1] + 4,
|
||||
(DISPLAY.height - TEXT.height - 4 - MAX_SIZE[1]) / 3)
|
||||
# TEXT.width/height doesn't seem to be working correctly with bitmap fonts
|
||||
# right now, so for now this does a brute-force override thing. Once that's
|
||||
# resolved, these two lines can be removed:
|
||||
MAX_SIZE = (48, 24)
|
||||
SPACING = 28
|
||||
for stop_index, stop in enumerate(STOPS):
|
||||
baseline = MAX_SIZE[1] + SPACING * stop_index
|
||||
GROUP[1 + stop_index * 3].anchored_position = (MAX_SIZE[0], baseline)
|
||||
GROUP[2 + stop_index * 3].anchored_position = (MAX_SIZE[0] + 4, baseline)
|
||||
# Third element (predictions) is NOT positioned here...see main loop
|
||||
|
||||
DISPLAY.show(GROUP)
|
||||
|
||||
NETWORK.connect()
|
||||
|
||||
# Force initial server queries for time and predictions
|
||||
LAST_SYNC_TIME = -CLOCK_SYNC_INTERVAL
|
||||
LAST_QUERY_TIME = -QUERY_INTERVAL
|
||||
|
||||
|
||||
# MAIN LOOP ----------------------------------------------------------------
|
||||
|
||||
while True:
|
||||
|
||||
# Periodically sync clock with time server
|
||||
if time.monotonic() - LAST_SYNC_TIME >= CLOCK_SYNC_INTERVAL:
|
||||
try:
|
||||
update_time(TIME_ZONE)
|
||||
LAST_SYNC_TIME = time.monotonic()
|
||||
except:
|
||||
# Time sync error isn't fatal, retry in 30 mins
|
||||
LAST_SYNC_TIME += 30 * 60
|
||||
|
||||
# Periodically poll all the stops, rather than staggering the queries
|
||||
# like the Raspberry Pi version does, since the screen is updated much
|
||||
# less frequently.
|
||||
if time.monotonic() - LAST_QUERY_TIME >= QUERY_INTERVAL:
|
||||
LAST_QUERY_TIME = time.monotonic()
|
||||
for stop in STOP_LIST:
|
||||
stop.fetch()
|
||||
TEXT.text = 'Last checked ' + hh_mm(time.localtime())
|
||||
|
||||
# Update displayed predictions on every pass though...
|
||||
for stop_index, stop in enumerate(STOP_LIST):
|
||||
times = stop.predict()
|
||||
group_index = 3 + stop_index * 3 # GROUP element for prediction text
|
||||
baseline = MAX_SIZE[1] + SPACING * stop_index
|
||||
if times:
|
||||
label = ''
|
||||
for time_index, time_seconds in enumerate(times):
|
||||
if time_index:
|
||||
label += ', '
|
||||
time_minutes = int(time_seconds // 60)
|
||||
if time_minutes > 60:
|
||||
# 'HHhMM' format is used rather than 'HH:MM' because
|
||||
# latter might be confused with 'clock time' rather
|
||||
# than countdown.
|
||||
label += '{hh}h{mm:02d}'.format(hh=time_minutes // 60,
|
||||
mm=time_minutes % 60)
|
||||
else:
|
||||
label += str(time_minutes)
|
||||
GROUP[group_index].text = label
|
||||
else:
|
||||
GROUP[group_index].text = 'No predictions'
|
||||
|
||||
# Because text anchoring currently uses bounding rect (not baseline),
|
||||
# the prediction text is scooted down by 2 pixels to compensate for
|
||||
# any commas (if present). Not ideal, assumes certain things about
|
||||
# font used here. Text without commas is NOT scooted down, to
|
||||
# maintain baseline across entire row.
|
||||
offset = 2 if ',' in GROUP[group_index].text else 0
|
||||
GROUP[group_index].anchored_position = (DISPLAY.width - 1,
|
||||
baseline + offset)
|
||||
|
||||
DISPLAY.refresh()
|
||||
gc.collect()
|
||||
time.sleep(60) # Update predictions about once a minute
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 Phillip Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
NextBus class -- handles NextBus server queries and infers
|
||||
arrival times based on last-queried predictions and elapsed time.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 Anne Barela for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Circuit Playground Express Data Time/Light Intensity/Temp
|
||||
# Log data to a spreadsheet on-screen
|
||||
# Open Spreadsheet beforehand and position to start (A,1)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
PyPortal MineSweeper
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# SPDX-FileCopyrightText: 2019 Dano Wall for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Music Box code in CircuitPython - Dano Wall and Anne Barela
|
||||
# Revised by Ladyada 2019-01-16
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 Dave Astels for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Dice roller for the NeoTrellisM4
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
import board
|
||||
import audioio
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
RED = 0xFF0000
|
||||
MAROON = 0x800000
|
||||
ORANGE = 0xFF8000
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Read data from a BerryMed pulse oximeter, model BM1000C, BM1000E, etc.
|
||||
Run this on Feather nRF52840
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Demo code to generate an alternating color-gradient effect in
|
||||
# the QT Py LED cuff bracelet LEDs.
|
||||
import time
|
||||
|
|
|
|||
Loading…
Reference in a new issue