-
-
Save jonbro/342e0c2fb0f9bfafacff3ce1b44f888a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _CONFIG_H | |
#define _CONFIG_H | |
// wifi & api config | |
#define WF_SSID_NAME "SSID" | |
#define WF_PASSWORD "WIFI PASSWORD" | |
#define FC_AUTH "Friend Camp API key" | |
// alert config | |
#define SECONDS_UNTIL_SLEEP 20 | |
#define TIMER_WAKEUP_SECONDS 600 | |
// random config stuff, not sure about where to put it | |
#define POST_ID_BUFFER_LENGTH 256 | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// font by managore https://managore.itch.io/m6x11 | |
#include <SPI.h> | |
#include <TFT_eSPI.h> | |
#include "driver/rtc_io.h" | |
#include "m5x77b.h" | |
#include "web.h" | |
#include "config.h" | |
#include "sleep.h" | |
#include "gui.h" | |
#include "song.h" | |
#include "screen.h" | |
#define SECONDS_UNTIL_SLEEP 30 | |
// setting PWM properties | |
const int ledPin = 4; | |
const int freq = 5000; | |
const int ledChannel = 0; | |
const int resolution = 8; | |
int i=0; | |
/**/ | |
int buttons[9] = {23, 22, 14, 32, 15, 33, 27, 25, 26}; | |
char buttonMap[9] {'h', 'o', 'r', 's', 'e', ' ', 'p', 'i', '\n'}; | |
int buttonState[9] = {0}; | |
String outputString; | |
#include <C:\Users\jonbro\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\libraries\WiFi\src\WiFi.h> | |
void wifiConnect() | |
{ | |
WiFi.mode(WIFI_STA); | |
WiFi.begin(WF_SSID_NAME, WF_PASSWORD); | |
// wait for WiFi connection | |
Serial.print("Waiting for WiFi to connect..."); | |
while ((WiFi.status() != WL_CONNECTED)) { | |
delay(15); | |
} | |
} | |
// the user triggered wakeup by pressing a button | |
void userTriggeredWakeup() | |
{ | |
// Use this initializer if using a 1.8" TFT screen: | |
tft.begin(); | |
tft.setFreeFont(&m6x118pt7b); | |
drawFullStatusbar("wifi connect..."); | |
EnableScreen(); | |
wifiConnect(); | |
Serial.println(" connected"); | |
drawFullStatusbar("pee"); | |
for(int i=0;i<9;i++) | |
{ | |
pinMode(buttons[i], INPUT_PULLUP); | |
buttonState[i] = HIGH; | |
} | |
Serial.println(F("Initialized")); | |
} | |
void runAlert(bool horse, bool piss) | |
{ | |
drawFullStatusbar("OH YES"); | |
if(horse && piss) | |
{ | |
drawAlert("HORSEPISS TALK!"); | |
playSong(campSong, campSongLength); | |
} | |
else if(horse) | |
{ | |
drawAlert("HORSE TALK!"); | |
playSong(horseSong, horseSongLength); | |
} | |
else | |
{ | |
drawAlert("PISS TALK!"); | |
playSong(pissSong, pissSongLength); | |
} | |
} | |
RTC_DATA_ATTR char mostRecentCheckedId[POST_ID_BUFFER_LENGTH]; | |
void checkForAlertPosts() | |
{ | |
wifiConnect(); | |
// construct result holder | |
char *oldestCheckedId = (char*)malloc(POST_ID_BUFFER_LENGTH*sizeof(char)); | |
PostAlertResult res = { | |
.oldestCheckedId = oldestCheckedId, | |
.mostRecentCheckedId = mostRecentCheckedId | |
}; | |
res.firstLoopCall = true; | |
oldestCheckedId[0] = 0; | |
// this will check 20 posts total | |
// it performs two checks per each call | |
for(int i=0;i<10;i++) | |
{ | |
lastest_post_has_alert(res); | |
res.firstLoopCall = false; | |
if(res.foundPost) | |
{ | |
// play alert noise and turn on screen | |
// turn on screen | |
tft.begin(); | |
tft.setFreeFont(&m6x118pt7b); | |
drawFullStatusbar("OH YES"); | |
for(int i=0;i<9;i++) | |
{ | |
pinMode(buttons[i], INPUT_PULLUP); | |
buttonState[i] = HIGH; | |
} | |
EnableScreen(); | |
runAlert(res.horsePost, res.pissPost); | |
// delay sleep, since it may have taken a while to check all the posts here | |
triggerSleep(true); | |
free(oldestCheckedId); | |
return; | |
} | |
if(res.hadZeroResults) | |
break; | |
} | |
// early return in the loop for an alert | |
// otherwise go back to sleep | |
free(oldestCheckedId); | |
sleep(); | |
} | |
void setup(void) { | |
Serial.begin(115200); | |
// disable the hold on pin 12 | |
rtc_gpio_hold_dis(GPIO_NUM_12); | |
esp_sleep_wakeup_cause_t wakeup_reason; | |
wakeup_reason = esp_sleep_get_wakeup_cause(); | |
switch(wakeup_reason) | |
{ | |
case ESP_SLEEP_WAKEUP_EXT0 : userTriggeredWakeup(); break; | |
case ESP_SLEEP_WAKEUP_TIMER : checkForAlertPosts(); break; | |
default : mostRecentCheckedId[0] = 0; checkForAlertPosts(); break; | |
} | |
} | |
int lastChar = 0; | |
void loop() { | |
bool updateScreen = false; | |
for(int i=0;i<9;i++) | |
{ | |
int newVal = digitalRead(buttons[i]); | |
if(buttonState[i] == HIGH && newVal == LOW) | |
{ | |
if(i==8) | |
{ | |
if(outputString.length() != 0) | |
{ | |
drawFullStatusbar("posting"); | |
web_post(outputString); | |
outputString = ""; | |
drawFullStatusbar("post"); | |
updateScreen = true; | |
} | |
} | |
else | |
{ | |
outputString += buttonMap[i]; | |
updateScreen = true; | |
} | |
} | |
if(newVal == LOW) | |
Serial.println(buttonMap[i]); | |
buttonState[i] = newVal; | |
} | |
if(buttonState[8]==LOW) | |
{ | |
if(buttonState[0] == LOW) | |
{ | |
runAlert(true, false); | |
outputString = ""; | |
} | |
if(buttonState[1] == LOW) | |
{ | |
runAlert(false, true); | |
outputString = ""; | |
} | |
if(buttonState[2] == LOW) | |
{ | |
runAlert(true, true); | |
outputString = ""; | |
} | |
} | |
if(updateScreen) | |
{ | |
tft.fillRect(0,STATUS_HEIGHT, tft.width(), tft.height()-STATUS_HEIGHT, DARK_PURP); | |
tft.setCursor(4, STATUS_HEIGHT+13); | |
tft.setTextColor(LIGHT_PURP); | |
tft.setTextWrap(true); | |
tft.println(outputString); | |
} | |
delay(15); | |
triggerSleep(updateScreen); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _GRAPHICS_CONFIG_H | |
#define _GRAPHICS_CONFIG_H | |
TFT_eSPI tft = TFT_eSPI(); | |
// gui element constants | |
#define STATUS_HEIGHT 20 | |
// Colors | |
#define BLACK 0x0 | |
#define WHITE 0xFFFF | |
#define DARK_PURP 0x418B | |
#define MEDIUM_PURP 0x4131 | |
#define LIGHT_PURP 0xA439 | |
#define TEXT_FADE 0xC65A | |
#define TEXT_BRIGHT 0xEF7D | |
#endif // _GRAPHICS_CONFIG_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "graphics_config.h" | |
uint16_t gradientColor(uint16_t x, uint16_t y) | |
{ | |
if(y == STATUS_HEIGHT) return TFT_BLACK; | |
if (y > STATUS_HEIGHT) return DARK_PURP; // Outside gradient area | |
uint8_t alpha = (255 * y) / STATUS_HEIGHT; // alpha is a value in the range 0-255 | |
return tft.alphaBlend(alpha, MEDIUM_PURP, LIGHT_PURP); | |
} | |
void drawFullStatusbar(String statusText) | |
{ | |
tft.setTextSize(1); | |
tft.fillScreen(DARK_PURP); | |
for(uint16_t i=0;i<STATUS_HEIGHT;i++) | |
{ | |
tft.drawFastHLine(0, i, 128, gradientColor(0, i)); | |
} | |
tft.drawFastHLine(0, STATUS_HEIGHT, 128, 0x388B); | |
tft.setTextColor(DARK_PURP); | |
tft.setCursor(3, 12); | |
tft.println(statusText); | |
tft.setTextColor(TEXT_FADE); | |
tft.setCursor(3, 13); | |
tft.println(statusText); | |
} | |
void drawAlert(String alertString) | |
{ | |
tft.drawRoundRect(10, STATUS_HEIGHT+10, tft.width()-20, 40, 3, DARK_PURP); | |
tft.fillRoundRect(11, STATUS_HEIGHT+11, tft.width()-22, 38, 3, LIGHT_PURP); | |
int center = (10+tft.width()-20)/2; | |
tft.setTextColor(TEXT_FADE); | |
tft.setCursor(center-6*5/2, STATUS_HEIGHT+10+16); | |
tft.print("ALERT!"); | |
tft.setCursor(center-6*alertString.length()/2, STATUS_HEIGHT+10+16+16); | |
tft.print(alertString); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _SCREEN_H | |
#define _SCREEN_H | |
#define SCREEN_PIN 12 | |
#include "driver/ledc.h" | |
bool screenEnabled = false; | |
void EnableScreen() | |
{ | |
screenEnabled = true; | |
ledcAttachPin(12, LEDC_CHANNEL_1); | |
ledcSetup(LEDC_CHANNEL_1, 800, 8); | |
// set the brightness on LEDC channel 0 | |
for(int i=0;i<128;i++) | |
{ | |
ledcWrite(LEDC_CHANNEL_1, i); | |
delay(2); | |
} | |
} | |
void DisableScreen() | |
{ | |
if(!screenEnabled) | |
return; | |
// set the brightness on LEDC channel 0 | |
for(int i=0;i<128;i++) | |
{ | |
ledcWrite(LEDC_CHANNEL_1, 128-i); | |
delay(2); | |
} | |
ledcDetachPin(SCREEN_PIN); | |
} | |
#endif // _SCREEN_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "config.h" | |
#include "screen.h" | |
#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */ | |
#define TIME_TO_SLEEP 20 | |
void sleep() | |
{ | |
DisableScreen(); | |
Serial.println("going to sleep now"); | |
// only one pin can be used to wakeup the system | |
esp_sleep_enable_ext0_wakeup(GPIO_NUM_26,0); | |
// setup alert wakeup | |
esp_sleep_enable_timer_wakeup(TIMER_WAKEUP_SECONDS * uS_TO_S_FACTOR); | |
// turn off the screen | |
digitalWrite(12, LOW); | |
// this call should turn off all power to the screen, but it doesn't seem to work? | |
rtc_gpio_isolate(GPIO_NUM_12); | |
esp_deep_sleep_start(); | |
} | |
unsigned long sleep_lastPressed = 0; | |
void triggerSleep(bool delaySleep) | |
{ | |
if(delaySleep) | |
{ | |
sleep_lastPressed = millis(); | |
} | |
else if(millis() - sleep_lastPressed > 1000*SECONDS_UNTIL_SLEEP) | |
{ | |
sleep(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _SONG_H | |
#define _SONG_H | |
#include "driver/ledc.h" | |
#define PEIZO_PIN 4 | |
const int horseSongLength = 24*2; | |
int horseSong[horseSongLength] = { | |
64,2, | |
67,2, | |
69,2, | |
64,2, | |
67,2, | |
69,2, | |
64,2, | |
67,2, | |
69,2, | |
64,2, | |
67,2, | |
69,2, | |
69,2, | |
68,2, | |
64,2, | |
69,2, | |
68,2, | |
64,2, | |
69,2, | |
68,2, | |
64,2, | |
69,2, | |
68,2, | |
64,2 | |
}; | |
const int pissSongLength = 12*2; | |
int pissSong[pissSongLength] = { | |
64,2, | |
66,2, | |
67,2, | |
66,2, | |
64,2, | |
61,2, | |
64,2, | |
71,2, | |
64,2, | |
64,2, | |
71,2, | |
64,2, | |
}; | |
const int campSongLength = 13*2; | |
int campSong[campSongLength] = { | |
52,2, | |
69,2, | |
61,2, | |
71,2, | |
69,2, | |
54,2, | |
69,2, | |
62,2, | |
61,2, | |
71,2, | |
69,2, | |
54,2, | |
64,5 | |
}; | |
int noteToFreq(float note) | |
{ | |
return 440.0f * powf(2.0f, (note - 69.0f) / 12.0f); | |
} | |
void playSong(int *song, int songLength) | |
{ | |
ledcSetup(LEDC_CHANNEL_0, 800, 8); | |
ledcAttachPin(PEIZO_PIN, LEDC_CHANNEL_0); | |
// set the brightness on LEDC channel 0 | |
ledcWrite(LEDC_CHANNEL_0, 255); | |
int currentChar = 0; | |
while(songLength > currentChar) | |
{ | |
int note = song[currentChar++]; | |
if(note == 0) | |
{ | |
ledcWrite(LEDC_CHANNEL_0, 0); | |
} | |
else | |
{ | |
ledcWrite(LEDC_CHANNEL_0, 255); | |
ledcWriteTone(LEDC_CHANNEL_0, noteToFreq(note)); | |
} | |
int delayLength = song[currentChar++]*130; | |
for(int i=0;i<delayLength;i++) | |
{ | |
ledcWrite(LEDC_CHANNEL_0, 255-i*255/delayLength); | |
delay(1); | |
} | |
} | |
ledcWrite(LEDC_CHANNEL_0, 0); | |
ledcDetachPin(PEIZO_PIN); | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _WEB_H | |
#define _WEB_H | |
#include <HTTPClient.h> | |
#include <C:\Users\jonbro\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\libraries\WiFiClientSecure\src\WiFiClientSecure.h> | |
#include "config.h" | |
#include <ArduinoJson.h> | |
String urlencode(String str) | |
{ | |
String encodedString=""; | |
char c; | |
char code0; | |
char code1; | |
char code2; | |
for (int i =0; i < str.length(); i++){ | |
c=str.charAt(i); | |
if (c == ' '){ | |
encodedString+= '+'; | |
} else if (isalnum(c)){ | |
encodedString+=c; | |
} else{ | |
code1=(c & 0xf)+'0'; | |
if ((c & 0xf) >9){ | |
code1=(c & 0xf) - 10 + 'A'; | |
} | |
c=(c>>4)&0xf; | |
code0=c+'0'; | |
if (c > 9){ | |
code0=c - 10 + 'A'; | |
} | |
code2='\0'; | |
encodedString+='%'; | |
encodedString+=code0; | |
encodedString+=code1; | |
//encodedString+=code2; | |
} | |
yield(); | |
} | |
return encodedString; | |
} | |
// just strips out tags from the content | |
String htmlToPlain(String html) | |
{ | |
String res = String(); | |
int r = 0; | |
while(r < html.length()) | |
{ | |
if(html.charAt(r) == '<') | |
{ | |
while(html.charAt(r++) != '>') | |
{ | |
; | |
} | |
} | |
else | |
{ | |
res.concat(html.charAt(r++)); | |
} | |
} | |
return res; | |
} | |
bool containsSubstring(String s, String sub) | |
{ | |
int r = 0; | |
int r2 = 0; | |
while(r < s.length()) | |
{ | |
while(s.charAt(r++) == sub.charAt(r2++)) | |
{ | |
if(r2==sub.length()) | |
return true; | |
} | |
r2 = 0; | |
} | |
return false; | |
} | |
void poster_init() | |
{ | |
} | |
#define HORSE_POST 1 | |
#define PISS_POST 1 << 1 | |
#define HORSEPISS_POST 1 | (1 << 1) | |
typedef struct | |
{ | |
char* oldestCheckedId; | |
char* mostRecentCheckedId; | |
bool foundPost; | |
bool horsePost; | |
bool pissPost; | |
bool firstLoopCall; | |
bool hadZeroResults; | |
} PostAlertResult; | |
void lastest_post_has_alert(PostAlertResult &res) | |
{ | |
res.foundPost = false; | |
res.horsePost = false; | |
res.pissPost = false; | |
WiFiClientSecure *client = new WiFiClientSecure; | |
if(client) { | |
client->setInsecure(); | |
{ | |
// Add a scoping block for HTTPClient https to make sure it is destroyed before WiFiClientSecure *client is | |
HTTPClient https; | |
Serial.print("[HTTPS] begin...\n"); | |
String requestString = "https://friend.camp/api/v1/timelines/home?local=True&limit=2"; | |
if(res.mostRecentCheckedId[0] != 0) | |
{ | |
requestString.concat(String("&since_id=") + res.mostRecentCheckedId); | |
} | |
if(res.oldestCheckedId[0] != 0) | |
{ | |
requestString.concat(String("&max_id=") + res.oldestCheckedId); | |
} | |
Serial.printf("[HTTPS] Request string: %s\n", requestString.c_str()); | |
if (https.begin(*client,requestString)) { // HTTPS | |
https.addHeader("Authorization", FC_AUTH); | |
// start connection and send HTTP header | |
int httpCode = https.GET(); | |
// httpCode will be negative on error | |
if (httpCode > 0) { | |
// HTTP header has been send and Server response header has been handled | |
Serial.printf("[HTTPS] GET... code: %d\n", httpCode); | |
// file found at server | |
if (httpCode == HTTP_CODE_OK) { | |
DynamicJsonDocument doc(1024*100); | |
DeserializationError err = deserializeJson(doc, https.getString()); | |
if (err) { | |
Serial.print(F("deserializeJson() failed: ")); | |
Serial.println(err.f_str()); | |
return; | |
} | |
JsonArray array = doc.as<JsonArray>(); | |
bool sinceIdSet = false; | |
int count = 0; | |
for(JsonVariant v : array) { | |
String id = v["id"].as<String>(); | |
if(res.firstLoopCall && !sinceIdSet) | |
{ | |
id.toCharArray(res.mostRecentCheckedId, POST_ID_BUFFER_LENGTH); | |
sinceIdSet = true; | |
} | |
id.toCharArray(res.oldestCheckedId, POST_ID_BUFFER_LENGTH); | |
String contentPlain = htmlToPlain(v["content"].as<String>()); | |
contentPlain.toLowerCase(); | |
if(containsSubstring(contentPlain, "horse")) | |
{ | |
res.foundPost = true; | |
res.horsePost = true; | |
} | |
if(containsSubstring(contentPlain, "piss")) | |
{ | |
res.foundPost = true; | |
res.pissPost = true; | |
} | |
if(res.foundPost) | |
return; | |
count++; | |
} | |
if(count ==0) | |
res.hadZeroResults = true; | |
} | |
else | |
{ | |
Serial.printf("Code incorrect: %s\n", https.errorToString(httpCode).c_str()); | |
} | |
} else { | |
// we had an error, lets wipe the most recent post so we can try to get it again | |
res.mostRecentCheckedId[0] = 0; | |
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); | |
} | |
https.end(); | |
} else { | |
Serial.printf("[HTTPS] Unable to connect\n"); | |
} | |
// End extra scoping block | |
} | |
delete client; | |
} else { | |
Serial.println("Unable to create client"); | |
} | |
} | |
void web_post(String &post) | |
{ | |
if(post.length() == 0) | |
return; | |
//post = "@jonbro " + post; | |
post = post + "\n #confbadge"; | |
post = urlencode(post); | |
WiFiClientSecure *client = new WiFiClientSecure; | |
if(client) { | |
client->setInsecure(); | |
{ | |
// Add a scoping block for HTTPClient https to make sure it is destroyed before WiFiClientSecure *client is | |
HTTPClient https; | |
Serial.print("[HTTPS] begin...\n"); | |
if (https.begin(*client, "https://friend.camp/api/v1/statuses")) { // HTTPS | |
https.addHeader("Authorization", FC_AUTH); | |
String httpRequestData = "status="+post;//+"&visibility=direct"; | |
Serial.print("[HTTPS] POST...\n"); | |
// start connection and send HTTP header | |
int httpCode = https.POST(httpRequestData); | |
// httpCode will be negative on error | |
if (httpCode > 0) { | |
// HTTP header has been send and Server response header has been handled | |
Serial.printf("[HTTPS] POST... code: %d\n", httpCode); | |
// file found at server | |
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { | |
String payload = https.getString(); | |
Serial.println(payload); | |
} | |
} else { | |
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); | |
Serial.println(https.getString()); | |
} | |
https.end(); | |
} else { | |
Serial.printf("[HTTPS] Unable to connect\n"); | |
} | |
// End extra scoping block | |
} | |
delete client; | |
} else { | |
Serial.println("Unable to create client"); | |
} | |
} | |
#endif // _WEB_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment