Skip to content

Instantly share code, notes, and snippets.

@jonbro

jonbro/config.h Secret

Created September 12, 2021 01:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonbro/342e0c2fb0f9bfafacff3ce1b44f888a to your computer and use it in GitHub Desktop.
Save jonbro/342e0c2fb0f9bfafacff3ce1b44f888a to your computer and use it in GitHub Desktop.
#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
// 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);
}
#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
#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);
}
#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
#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();
}
}
#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
#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