ESP32 node monitor

Step 9 ESP32 on a breadboard

On the right, you see the first attempt.

I managed to get the first common cathode led burning green after checking my node.

One the first led was fired-up, a second led was a piece of cake.

Changing the colors of the leds is just a matter of software.

Note 1: A led should get no more than 20 mA (I). With V=I*R, you can calculate that the resistance (R) should be 250 Ohm. I used 220 Ohm, which works nicely.

Note 2: I connected a pulse width modulation output to the resistance connected to the common output. Later I discovered that this works perfectly if you only light-up one LED color at the time, but mixing colors did not work. Putting a PWM on every anode with each anode a resistor would probably solve the problem.

Step 10 Sound hardware

The most important feature of the Nodeuitgans is of course the geese honking sound.

Therefore I bought the PAM8302A 2.5W amplifier. The connections are simple:

  • connect the audio in (A+) to  one of the 2 DAC outputs of the ESP32. I used  GPIO25 in this case.
  • connect the other audio input (A-) to the ground.
  • connect 2-5V input to the 5V pin of the ESP32.
    Very important: connect a big capacitor (2200 uF) near the 5V connection, in order to limit the distortions caused by power dips during WiFi communication.
  • connect the ground to one of the ground pins of the ESP32.
  • connect the shutdown (SD) input to one of the free GPIO (outputs). I used GPIO0. This allows you to shutdown the amplifier when not in use, which reduces a lot of noise.
  • Use a 4-8 ohm speaker and connect it to the PAM8302A speaker outputs

Step 11 Sound software

The software to play sound is called XT_DAC_AUDIO.h

How to install and use it, can be found on the webpage of Xtronical.

I used the open source program Audacity to mix a sound track to mono, and to reduce the sample rate to 8kHz. Although, this reduces the quality drastically, it also reduces storage space drastically. I discovered that storage space is scarce in the ESP32.

You can not simply use the sound file in the ESP. You first have to convert it to an eight bit unsigned wav file, and convert this again with xxd -i to a readable format. I got the info by looking at this video.

One important thing I discovered, is that the loop() function needs to be looping quickly (without major delays) in order to produce sound. Therefore, the software bypasses all node and price checks when playing sound.

//////////////LOAD LIBRARIES////////////////

#include “FS.h”
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include “SPIFFS.h”
#include “Geese_honking.h”
#include “XT_DAC_Audio.h”

XT_Wav_Class GeeseHonk(Geese_honking_wav); // create an object of type XT_Wav_Class that is used by
// the dac audio class (below), passing wav data as parameter.

XT_DAC_Audio_Class DacAudio(25,0); // Create the main player class object.
// Use GPIO 25, one of the 2 DAC pins and timer 0

/////////////////SOME VARIABLES///////////////////

bool initial_startup = true;

const int default_intensity = 30; // default intensity of LED’s
const int max_intensity = 255; // max intensity of LED’s

char lnd_server[40] = “10.0.20.50”;
char lnd_port[6] = “8080”;
char lnd_macaroon[500] = “0201036c6e6402ac0103…bla..bla..bla…bc3”;
char static_ip[16] = “10.0.40.120”;
char static_gw[16] = “10.0.40.1”;
char static_sn[16] = “255.255.255.0”;

char price_server[40] = “api.coingecko.com”; // server adress for BTC price
char price_port[6] = “443”;
char BTC_ATH[16] = “67277”; // last all time high value of bitcoin in USD
char BTC_fapgans[16] = “70000”; // Next Satosh Radio fapgans (every 5K all time high)
char Connect_to_node[2] = “Y”;
bool shouldSaveConfig = true;
String lnd_Alias = “”;
boolean lnd_Synced;
int block_Height;
int old_block_height = 0;
int onchain_balance;
int old_onchain_balance = 0;
int lnd_balance;
int old_lnd_balance = 0;
int highest_invoice_settle_index=0;
int last_invoice_settle_index=0;
int last_invoice = 0;
int highest_payment_settle_index=0;
String last_payment_status=””;
int last_payment_index = 0;
int USD_price = 0;
int rnd;
int remaining_delay;
int btc_ATH = atoi(BTC_ATH);
int btc_fapgans = atoi(BTC_fapgans);
int Play_nr_Fapgans = 0;
int i_fap = 0;
bool has_been_significantly_lower =true;
int ATH_threshold = 250; //threshold for all time high blinking
bool onchainIsHigher=false;
bool onchainIsLower=false;
bool lndIsHigher=false;
bool lndIsLower=false;

const char* spiffcontent = “”;
String spiffing;

struct LED {
int pins[4];
int channel;
} left_LED, right_LED;
int MuteSound = 0; // GPIO 0 is used for muting sound

// RGB colors
int RGB_green[3]= {LOW, HIGH, LOW }; // node running
int RGB_red[3] = {HIGH, LOW, LOW}; // node down
int RGB_pink[3] = {HIGH, LOW, HIGH}; // node syncing
int RGB_blue[3] = {LOW, LOW, HIGH}; //
int RGB_aqua[3] = {LOW, HIGH, HIGH };
int RGB_lime[3] = {HIGH, HIGH, LOW };

/////////////////////SETUP////////////////////////

void setup() {
pinMode(MuteSound, OUTPUT);// GPIO 0 is connected to the SD terminal of the amplifier chip
digitalWrite(MuteSound, LOW); // mute sound during startup

Serial.begin(115200);

// LED setup
left_LED.pins[0] = 16; left_LED.pins[1] = 17; left_LED.pins[2] = 5; left_LED.pins[3] = 18; // R, G, B, PWM ports
left_LED.channel = 0;
right_LED.pins[0] = 19; right_LED.pins[1] = 21; right_LED.pins[2] = 22; right_LED.pins[3] = 23; // R, G, B, PWM ports
right_LED.channel = 1;
// CATHODE RGB SETUP, 18 and 23 WILL BE PWM FOR DIMMING THE LED
pinMode(left_LED.pins[0], OUTPUT); pinMode(left_LED.pins[1], OUTPUT); pinMode(left_LED.pins[2], OUTPUT);
pinMode(right_LED.pins[0], OUTPUT); pinMode(right_LED.pins[1], OUTPUT); pinMode(right_LED.pins[2], OUTPUT);

// configure LED PWM functionalitites
ledcSetup(left_LED.channel, 5000, 8); //ledchannel 0, frequency 5000 and resolution 8
ledcSetup(right_LED.channel, 5000, 8); //ledchannel 1, frequency 5000 and resolution 8

// attach the channel to the GPIO to be controlled
ledcAttachPin(left_LED.pins[3], left_LED.channel); // connect pin 18 with led channel 0
ledcAttachPin(right_LED.pins[3], right_LED.channel); // connect pin 23 with led channel 1

Set_color( left_LED, RGB_pink, default_intensity, right_LED, RGB_pink, default_intensity);


// START PORTAL
NodeUitGans();
Set_color( left_LED, RGB_blue, default_intensity, right_LED, RGB_blue, default_intensity);
}


///////////////////MAIN LOOP//////////////////////

void loop() {

if (Play_nr_Fapgans ==0) { // while playing the fapgans sound, all other loops should be interupted
digitalWrite(MuteSound, LOW); // mute sound until palying sound
remaining_delay = 10000; // msec delay. This set the cycle time of the loop
if ((String(Connect_to_node)==”Y”) or (String(Connect_to_node)==”y”)) {
// Check info from node
if(getinfo()) {
// No connection to node
Serial.println(“Not connected to client”);
Set_color( left_LED, RGB_red, default_intensity, right_LED, RGB_red, default_intensity);
}
else {
Serial.println(String(“Node alias : “)+ String(lnd_Alias));
if (!lnd_Synced){
Serial.println(String(“Synced_to_chain : no”));
Set_color( left_LED, RGB_pink, default_intensity, right_LED, RGB_pink, default_intensity);
}
else{
Serial.println(String(“Synced_to_chain : yes”));
Serial.println(String(“Block height : “+ String(block_Height)));
if (block_Height > old_block_height) {
Set_color( left_LED, RGB_blue, max_intensity, right_LED, RGB_blue, max_intensity);
old_block_height = block_Height;
delay(3000);
}
// Node connected and synced
Set_color( left_LED, RGB_green, default_intensity, right_LED, RGB_green, default_intensity);
if (GetOnChain()) {
// could not get onchain balance
Serial.println(“Could not get onchain balance”);
Set_color( left_LED, RGB_red, default_intensity, right_LED, RGB_red, default_intensity);
}
Serial.println(String(“Onchain balance : “)+ String(onchain_balance));
if (initial_startup) {
// get the last settled invoice index. This is used to check if the LND balance is higher
// due to lower chanel closing fees (substracted from tyour balance) or a new invoice is paid.
if(getlastinvoicesettleindex()){Serial.println(“Could not get last invoice settle index”);}
}
if (GetLnd()) {
// could not get onchain balance
Serial.println(“Could not get lnd balance”);
Set_color( left_LED, RGB_red, default_intensity, right_LED, RGB_red, default_intensity);
}
Serial.println(String(“LND balance : “)+ String(lnd_balance));
if (onchainIsHigher or lndIsHigher) {
// you got paid
disco();
remaining_delay=0;
}
if (onchainIsLower or lndIsLower) {
RedBlink();
remaining_delay=0;
}
}
}
}

blink(); // random blink with one eye

//** get price of BTC
if( getprice(USD_price)) {
Serial.println(“Could not get price from Coingecko”);
Set_color( left_LED, RGB_red, default_intensity, right_LED, RGB_red, default_intensity);
}
else {
// new price from Coingecko received
Set_color( left_LED, RGB_green, default_intensity, right_LED, RGB_green, default_intensity);
Serial.println(String(“Bitcoin price : “)+ String(USD_price));
String temp_string=”false”; if(has_been_significantly_lower) {temp_string = “true”;}
Serial.println(String(“Been sign lower : “)+ temp_string);
Serial.println(String(“Bitcoin ATH : “)+ String(btc_ATH));
Serial.println(String(“Bitcoin fapgans : “)+ String(btc_fapgans));
if (!has_been_significantly_lower and (USD_price < (btc_ATH – ATH_threshold))) { has_been_significantly_lower=true;}
if ((((USD_price > btc_ATH) and has_been_significantly_lower) or (USD_price > (btc_ATH + ATH_threshold))) and USD_price < btc_fapgans) {
// only ATH when price has been below threshold in past, or has risen for more than the threshold. Also ignore ATH when fapgans.
// ALL TIME HIGHT BTC PRICE IN USD
Serial.println(String(“All TIME HIGH : “)+ String(USD_price));
BlueBlink();
btc_ATH = USD_price;
itoa(btc_ATH,BTC_ATH,10);
SaveConfig(); // store new ATH
has_been_significantly_lower = false;
remaining_delay =0;
}
if (USD_price >= btc_fapgans) {
// FAPGANS
Serial.println(String(“*** FAPGANS *** : “)+ String(USD_price) + ” >= ” + String(btc_fapgans));
Play_nr_Fapgans = 5;
btc_fapgans = int(USD_price/5000)*5000+5000;
btc_ATH = USD_price;
has_been_significantly_lower=false;
Serial.println(String(“New fapgans price: “)+ String(btc_fapgans));
itoa(btc_ATH,BTC_ATH,10);
itoa(btc_fapgans,BTC_fapgans,10);
SaveConfig(); // store new fapgans value
remaining_delay = 0;
}
}
delay(max(remaining_delay,0)); // when the processing time of above statement is shorter than the loop cycle time, it does an additional delay
old_onchain_balance = onchain_balance;
old_lnd_balance = lnd_balance;
lndIsHigher = false;
lndIsLower = false;
onchainIsHigher = false;
onchainIsLower = false;
if(initial_startup) { initial_startup = false;}
}
else { // play the fapgans sound
digitalWrite(MuteSound, HIGH); // un-mute sound
DacAudio.FillBuffer(); // Fill the sound buffer with data
if(GeeseHonk.Playing==false){ // if not playing,
Serial.println(“playing GeeseHonk”);
DacAudio.Play(&GeeseHonk); // play it, this will cause it to repeat and repeat…
Play_nr_Fapgans–;
}
Fapgans(); // blink leds
}
// end of loop()
}


//////////////////LED SEQUENCES///////////////////

void Set_color( LED leftLED, int leftColor[3],int leftIntensity, LED rightLED, int rightColor[3],int rightIntensity) {
digitalWrite(leftLED.pins[0], leftColor[0]); digitalWrite(leftLED.pins[1], leftColor[1]); digitalWrite(leftLED.pins[2], leftColor[2]);
ledcWrite(leftLED.channel, 255-leftIntensity); //left channel 0 to max led brightness (0-255)
digitalWrite(rightLED.pins[0], rightColor[0]); digitalWrite(rightLED.pins[1], rightColor[1]); digitalWrite(rightLED.pins[2], rightColor[2]);
ledcWrite(rightLED.channel, 255-rightIntensity); //left channel 0 to max led brightness (0-255)
}
//DISCO LOOP // you got paid
void disco(){
Serial.println(“DISCO”);
for (int i = 0; i <= 15; i++) {
for (int intens=0; intens <= 255; intens+=2) {
Set_color( left_LED, RGB_blue, intens, right_LED, RGB_blue, intens);
delay(2);
}
for (int intens=255; intens >= 0; intens-=2) {
Set_color( left_LED, RGB_blue, intens, right_LED, RGB_blue, intens);
delay(2);
}
for (int intens=0; intens <= 255; intens+=2) {
Set_color( left_LED, RGB_green, intens, right_LED, RGB_green, intens);
delay(2);
}
for (int intens=255; intens >= 0; intens-=2) {
Set_color( left_LED, RGB_green, intens, right_LED, RGB_green, intens);
delay(2);
}
for (int intens=0; intens <= 255; intens+=2) {
Set_color( left_LED, RGB_red, intens, right_LED, RGB_red, intens);
delay(2);
}
for (int intens=255; intens >= 0; intens-=2) {
Set_color( left_LED, RGB_red, intens, right_LED, RGB_red, intens);
delay(2);
}

}
Set_color( left_LED, RGB_green, default_intensity, right_LED, RGB_green, default_intensity);
}
//Red blink LOOP // you paid
void RedBlink(){
for (int i = 0; i <= 5; i++) {
Set_color( left_LED, RGB_red, 0, right_LED, RGB_red, max_intensity);
delay(100);
Set_color( left_LED, RGB_red, max_intensity, right_LED, RGB_red, 0);
delay(100);
}
Set_color( left_LED, RGB_green, default_intensity, right_LED, RGB_green, default_intensity);
}
//Blue blink LOOP // all time high
void BlueBlink(){
for (int i = 0; i <= 7; i++) {
Set_color( left_LED, RGB_lime, max_intensity, right_LED, RGB_lime, max_intensity);
delay(200);
Set_color( left_LED, RGB_lime,0, right_LED, RGB_lime, 0);
delay(200);
Set_color( left_LED, RGB_lime, max_intensity, right_LED, RGB_lime, max_intensity);
delay(200);
Set_color( left_LED, RGB_lime,0, right_LED, RGB_lime, 0);
delay(200);
for (int intens=0; intens <= 255; intens+=2) {
Set_color( left_LED, RGB_blue, intens, right_LED, RGB_blue, intens);
delay(10);
}
for (int intens=255; intens >= 0; intens-=2) {
Set_color( left_LED, RGB_blue, intens, right_LED, RGB_blue, intens);
delay(10);
}
Set_color( left_LED, RGB_lime, max_intensity, right_LED, RGB_lime, max_intensity);
delay(200);
Set_color( left_LED, RGB_lime,0, right_LED, RGB_lime, 0);
delay(200);
Set_color( left_LED, RGB_lime, max_intensity, right_LED, RGB_lime, max_intensity);
delay(200);
Set_color( left_LED, RGB_lime,0, right_LED, RGB_lime, 0);
delay(200);
for (int i = 0; i <= 5; i++) {
Set_color( left_LED, RGB_red, 0, right_LED, RGB_red, max_intensity);
delay(100);
Set_color( left_LED, RGB_red, max_intensity, right_LED, RGB_red, 0);
delay(100);
}
for (int i = 0; i <= 5; i++) {
Set_color( left_LED, RGB_blue, 0, right_LED, RGB_blue, max_intensity);
delay(100);
Set_color( left_LED, RGB_blue, max_intensity, right_LED, RGB_blue, 0);
delay(100);
}
for (int i = 0; i <= 5; i++) {
Set_color( left_LED, RGB_green, 0, right_LED, RGB_green, max_intensity);
delay(100);
Set_color( left_LED, RGB_green, max_intensity, right_LED, RGB_green, 0);
delay(100);
}
for (int i = 0; i <= 5; i++) {
Set_color( left_LED, RGB_blue, 0, right_LED, RGB_blue, max_intensity);
delay(100);
Set_color( left_LED, RGB_blue, max_intensity, right_LED, RGB_blue, 0);
delay(100);
}
}
Set_color( left_LED, RGB_green, default_intensity, right_LED, RGB_green, default_intensity);
}

//Fapgans LOOP
void Fapgans(){
int nr_cycles_per_led = 10;
if (i_fap <= 1*nr_cycles_per_led) {
Set_color( left_LED, RGB_blue, 0, right_LED, RGB_blue, max_intensity);
}
else if (i_fap <= 2*nr_cycles_per_led) {
Set_color( left_LED, RGB_blue, max_intensity, right_LED, RGB_blue, 0);
}
else if (i_fap <= 3*nr_cycles_per_led) {
Set_color( left_LED, RGB_red, 0, right_LED, RGB_red, max_intensity);
}
else if (i_fap <= 4*nr_cycles_per_led) {
Set_color( left_LED, RGB_red, max_intensity, right_LED, RGB_red, 0);
delay(10);
}
else if (i_fap <= 5*nr_cycles_per_led) {
Set_color( left_LED, RGB_green, 0, right_LED, RGB_green, max_intensity);
}
else if (i_fap <= 6*nr_cycles_per_led) {
Set_color( left_LED, RGB_green, max_intensity, right_LED, RGB_green, 0);
}
delay(10);
if(i_fap++ >= 7*nr_cycles_per_led) {i_fap=0;}
}
void blink() {
// blink random with one eye
rnd = rand() % 1000;
if (rnd <= 5) {
// dimm left LED
ledcWrite(left_LED.channel, 255-1); //left channel 0 to max led brightness (0-255)
delay(5000);
ledcWrite(left_LED.channel, 255-default_intensity); //left channel 0 to max led brightness (0-255)
remaining_delay -= 5000;
}
else if (rnd >= 1000-5){
ledcWrite(right_LED.channel, 255-1); //left channel 0 to max led brightness (0-255)
delay(5000);
ledcWrite(right_LED.channel, 255-default_intensity); //left channel 0 to max led brightness (0-255)
remaining_delay -= 5000;
}
}
//////////////////NODE CALLS///////////////////
int GetOnChain() {
if (getonchainbalance()) {
Serial.println(“error onchain balance”);
return (1);
}
else {
// check if balance has changes
if ((onchain_balance > old_onchain_balance) and !initial_startup) {
onchainIsHigher = true;
}
else if ((onchain_balance < old_onchain_balance) and !initial_startup) {
onchainIsLower = true;
}
}
return (0);
}


int GetLnd() {
if (getlndbalance()) {Serial.println(“error lnd balance”); return (1); }
// check if balance has changes
if ((lnd_balance > old_lnd_balance) and !initial_startup) {
// balance is higher, but could be caused by changing commit fees
if (getlastinvoicesettleindex()) {Serial.println(“Could not get last invoice settle index”); return (1); }
if (last_invoice_settle_index > highest_invoice_settle_index) {
// **** somebody paid you *****
lndIsHigher = true;
Serial.println(String(“Old invoice settle index : “)+ String(highest_invoice_settle_index));
Serial.println(String(“New invoice settle index : “)+ String(last_invoice_settle_index));
highest_invoice_settle_index = last_invoice_settle_index;
}
}
else if ((lnd_balance < old_lnd_balance) and !initial_startup) {
// balance is lower, but could be caused by changing commit fees
if (getlastpaymentsettleindex()) {Serial.println(“Could not get last payment settle index”); return (1); }
if (last_payment_index > highest_payment_settle_index) {
// **** You paid somebody *****
lndIsLower = true;
Serial.println(String(“Old payment settle index : “)+ String(highest_payment_settle_index));
Serial.println(String(“New payment settle index : “)+ String(last_payment_index));
highest_payment_settle_index = last_payment_index;
}
}
return (0);
}


int getinfo() {
WiFiClientSecure client;
client.setInsecure();
const char* lnd_check;
bool synced_to_chain = false;
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
Serial.println(“error could not connect to client”);
return (1);
}
client.println(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/getinfo HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
const size_t capacity = JSON_OBJECT_SIZE(3) + 620;
DynamicJsonDocument doc(capacity);
deserializeJson(doc, content);
lnd_check = doc[“alias”];
if (!lnd_check){
return (1);
}
lnd_Alias = lnd_check;
synced_to_chain = doc[“synced_to_chain”];
block_Height = doc[“block_height”];
lnd_Synced = synced_to_chain;
// Serial.println(String(“alias : “)+ String(lndAlias));
return (0);
}

int getonchainbalance() {
WiFiClientSecure client;
client.setInsecure();
int new_confirmed_balance = 0;
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
delay(1000);
return (1);
}
client.print(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/balance/blockchain HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
const size_t capacity = JSON_OBJECT_SIZE(3) + 620;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
new_confirmed_balance = doc[“confirmed_balance”];
onchain_balance = new_confirmed_balance;
//Serial.println(“old_onchain_balance: ” + String(confirmed_balance));
//Serial.println(“new_onchain_balance: ” + String(new_confirmed_balance));
return (0);
}



int getlndbalance() {
WiFiClientSecure client;
client.setInsecure();
int new_balance = 0;
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
return (1);
}
client.print(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/balance/channels HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
const size_t capacity = JSON_OBJECT_SIZE(3) + 620;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
new_balance = doc[“balance”];
lnd_balance = new_balance;
//Serial.println(“lnd_balance : ” + String(new_balance));
return (0);
}


int getlastinvoicesettleindex() {
int invoicenr=0;
int settleindex;
if (getlastinvoice()) {
// error last invoice
Serial.println(“could not get last invoice”);
}
if (last_invoice_settle_index > highest_invoice_settle_index) {
Serial.println(“Last invoice is new settled invoice”);
return (0);
}
else {
Serial.println(“Last innvoice is not settled”);
invoicenr=last_invoice-1; // the last invoice was not settled
while (invoicenr– > 0) {
if (getinvoicebynumber(invoicenr, settleindex)) {
Serial.println(“getinvoicebynumber error”);
return (1);
}
if( settleindex > highest_invoice_settle_index) {
// there is a settled invoice with a higher index
last_invoice_settle_index = settleindex;
Serial.println(“Last invoice is new settled invoice”);
break;
}
else if( settleindex !=0 and settleindex <= highest_invoice_settle_index) {
// the indesx is lower, so no new settled invoice was found
Serial.println(“No new settled invoice found”);
break;
}
}
if (settleindex ==0){Serial.println(“No settled invoices found”);}
return (0);
}
}
int getlastinvoice() {
WiFiClientSecure client;
client.setInsecure();
const char* Is_Settled_tmp;
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
return (1);
}
// get the last invoice in order to get the last_index_offset
client.print(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/invoices?num_max_invoices=1&reversed=true HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
//Serial.println(“readstringuntil: “+String(content));
const size_t capacity = 2048;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
// serializeJsonPretty(doc, Serial);
last_invoice_settle_index = doc[“invoices”][0][“settle_index”];
last_invoice = doc[“last_index_offset”];
Serial.println(“Settle index : ” + String(last_invoice_settle_index));
Serial.println(“Last invoice : ” + String(last_invoice));
return (0);
}

int getinvoicebynumber(int invoicenr, int& settleIndex) {
WiFiClientSecure client;
client.setInsecure();
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
return (1);
}
// get the invoice by index
client.print(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/invoices?num_max_invoices=1&index_offset=”+invoicenr+” HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
// Serial.println(“readstringuntil: “+String(content));
const size_t capacity = 2048;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
settleIndex = doc[“invoices”][0][“settle_index”];
invoicenr = doc[“last_index_offset”];
Serial.println(“Invoice “+ String(invoicenr)+ ” has settle_index ” + String(settleIndex));
return (0);
}

 

 

int getlastpaymentsettleindex() {
int paymentnr=0;
String PaymentStatus=””;
if (getlastpayment()) {
// error last payment
Serial.println(“could not get last payment”);
}
if (last_payment_status==”SUCCEEDED” and (last_payment_index > highest_payment_settle_index)) {
Serial.println(“Last payment is new settled payment”);
return (0);
}
else {
Serial.println(“Last payment is not new or not higest settled”);
paymentnr=last_payment_index-1; // the last payment was not settled
while (paymentnr– > 0) {
if (getpaymentbynumber(paymentnr, PaymentStatus)) {
Serial.println(“getpaymentbynumber error”);
return (1);
}
if( PaymentStatus==”SUCCEEDED” and (paymentnr > highest_payment_settle_index)) {
// there is a succeeded payment with a higher index
last_payment_index = paymentnr;
Serial.println(“Last payment is new settled payment”);
break;
}
else if( paymentnr <= highest_payment_settle_index) {
// the indesx is lower, so no new settled payment was found
Serial.println(“No new settled payment found”);
break;
}
}
if (paymentnr ==0){Serial.println(“No settled payments found”);}
return (0);
}
}
int getlastpayment() {
WiFiClientSecure client;
client.setInsecure();
const char* status_payment;
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
return (1);
}
// get the last payment in order to get the last_index_offset
client.print(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/payments?max_payments=1&reversed=true HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
//Serial.println(“readstringuntil: “+String(content));
const size_t capacity = 3072;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
//serializeJsonPretty(doc, Serial);
status_payment = doc[“payments”][0][“status”];
last_payment_index = doc[“last_index_offset”];
last_payment_status = status_payment;
Serial.println(“Last payment status ” + String(last_payment_status));
Serial.println(“Last payment : ” + String(last_payment_index));
return (0);
}

int getpaymentbynumber(int paymentnr, String& paymentstatus) {
WiFiClientSecure client;
client.setInsecure();
const char* lndserver = lnd_server;
const char* macaroon = lnd_macaroon;
const char* status_payment;
int lndport = atoi( lnd_port );
if (!client.connect(lndserver, lndport)){
return (1);
}
// get the payment by index
client.print(String(“GET “)+ “https://” + lndserver +”:”+ lndport + “/v1/payments?num_max_payments=1&index_offset=”+paymentnr+” HTTP/1.1\r\n” +
“Host: ” + lndserver +”:”+ lndport +”\r\n” +
“User-Agent: ESP322\r\n” +
“Grpc-Metadata-macaroon:” + macaroon + “\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);
String line = client.readStringUntil(‘\n’);
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
client.stop();
// Serial.println(“readstringuntil: “+String(content));
const size_t capacity = 3072;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
status_payment= doc[“payments”][0][“status”];
paymentstatus = status_payment;
paymentnr = doc[“last_index_offset”];
Serial.println(“payment “+ String(paymentnr)+ ” has status ” + String(paymentstatus));
return (0);
}

 

 

int getprice(int& USDprice) {
WiFiClientSecure client;
client.setInsecure();
const char* priceserver = price_server;
int priceport = atoi( price_port );
if (!client.connect(priceserver, priceport)){
delay(1000);
Serial.println(“not connected to coingecko for price info”);
return (1);
}
// Serial.println(“connected to coingecko for price info”);
client.print(String(“GET “)+ “https://” + priceserver +”:”+ priceport + “/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_market_cap=false&include_24hr_vol=false&include_24hr_change=false&include_last_updated_at=false HTTP/1.1\r\n” +
“Host: ” + priceserver +”:”+ priceport +”\r\n” +
“Content-Type: application/json\r\n” +
“Connection: close\r\n” +
“\n”);

// Serial.println(“request send to coingecko for price info”);
String line = client.readStringUntil(‘\n’);
// Serial.println(“readstringuntil: “+String(line));
while (client.connected()) {
String line = client.readStringUntil(‘\n’);
// Serial.println(“readstringuntil: “+String(line));
if (line == “\r” or line==””) {
break;
}
}
String content = client.readStringUntil(‘\n’);
content = client.readStringUntil(‘\n’); // extra read is required in this case
client.stop();
// Serial.println(“content: ” + content);
const size_t capacity = JSON_OBJECT_SIZE(3) + 620;
DynamicJsonDocument doc(capacity);
DeserializationError err=deserializeJson(doc, content);
if (err) {
Serial.println(“json error: “+String(err.f_str()));
return (1);
}
USDprice = doc[“bitcoin”][“usd”];
// USDprice = new_Price;
// Serial.println(“new price: ” + String(USDprice));
return (0);
}

void NodeUitGans(){

WiFiManager wm;
Serial.println(“mounting FS…”);
while(!SPIFFS.begin(true)){
Serial.println(“failed to mount FS”);
delay(200);
}

//CHECK IF RESET IS TRIGGERED/WIPE DATA
for (int i = 0; i <= 5; i++) {
// get touch read a number of times to get stable reading
Serial.println(“touchRead :” + String(touchRead(4)));
}
for (int i = 0; i <= 5; i++) {
if(touchRead(4) < 55){
Serial.println(“Reset button pressed”);
File file = SPIFFS.open(“/config.txt”, FILE_WRITE);
file.print(“placeholder”);
wm.resetSettings();
}
delay(1000);
}

//MOUNT FS AND READ CONFIG.JSON
File file = SPIFFS.open(“/config.txt”);

spiffing = file.readStringUntil(‘\n’);
spiffcontent = spiffing.c_str();
DynamicJsonDocument json(1024);
deserializeJson(json, spiffcontent);
if(String(spiffcontent) != “placeholder”){
strcpy(BTC_ATH, json[“btc_ath”]);
strcpy(BTC_fapgans, json[“btc_fapgans”]);
strcpy(Connect_to_node, json[“connect_to_node”]);
strcpy(lnd_server, json[“lnd_server”]);
strcpy(lnd_port, json[“lnd_port”]);
strcpy(lnd_macaroon, json[“lnd_macaroon”]);

}

//ADD PARAMS TO WIFIMANAGER
wm.setSaveConfigCallback(saveConfigCallback);
WiFiManagerParameter custom_lnd_btc_ath(“btc_ath”, “Bitcoin price all time high in usd”, BTC_ATH, 16);
WiFiManagerParameter custom_lnd_btc_fapgans(“btc_fapgans”, “Bitcoin price next fapgans in usd”, BTC_fapgans, 16);
WiFiManagerParameter custom_lnd_con_node(“connect_node”, “Connect to your own node (Y/N)”, Connect_to_node, 2);
WiFiManagerParameter custom_lnd_server(“server”, “LND server”, lnd_server, 40);
WiFiManagerParameter custom_lnd_port(“port”, “LND port”, lnd_port, 6);
WiFiManagerParameter custom_lnd_macaroon(“macaroon”, “LND readonly macaroon”, lnd_macaroon, 500);

wm.addParameter(&custom_lnd_btc_ath);
wm.addParameter(&custom_lnd_btc_fapgans);
wm.addParameter(&custom_lnd_con_node);
wm.addParameter(&custom_lnd_server);
wm.addParameter(&custom_lnd_port);
wm.addParameter(&custom_lnd_macaroon);


//IF RESET WAS TRIGGERED, RUN PORTAL AND WRITE FILES
if (!wm.autoConnect(“⚡⚡Nodeuitgans⚡⚡”, “gakgakgak”)) {
Serial.println(“failed to connect and hit timeout”);
delay(3000);
ESP.restart();
delay(5000);
}
Serial.println(“connected :)”);
strcpy(BTC_ATH, custom_lnd_btc_ath.getValue());
strcpy(BTC_fapgans, custom_lnd_btc_fapgans.getValue());
strcpy(Connect_to_node, custom_lnd_con_node.getValue());
strcpy(lnd_server, custom_lnd_server.getValue());
strcpy(lnd_port, custom_lnd_port.getValue());
strcpy(lnd_macaroon, custom_lnd_macaroon.getValue());
btc_ATH = atoi(BTC_ATH);
btc_fapgans = atoi(BTC_fapgans);
if (shouldSaveConfig) {
SaveConfig();
shouldSaveConfig = false;
}

Serial.println(“local ip”);
Serial.println(WiFi.localIP());
Serial.println(WiFi.gatewayIP());
Serial.println(WiFi.subnetMask());
}

 

void SaveConfig() {
Serial.println(“saving config”);
DynamicJsonDocument json(1024);
json[“btc_ath”] = BTC_ATH;
json[“btc_fapgans”] = BTC_fapgans;
json[“connect_to_node”] = Connect_to_node;
json[“lnd_server”] = lnd_server;
json[“lnd_port”] = lnd_port;
json[“lnd_macaroon”] = lnd_macaroon;


File configFile = SPIFFS.open(“/config.txt”, “w”);
if (!configFile) {
Serial.println(“failed to open config file for writing”);
}
serializeJsonPretty(json, Serial);
serializeJson(json, configFile);
configFile.close();
}

void saveConfigCallback () {
Serial.println(“Should save config”);
shouldSaveConfig = true;
}

Notes:

  • 05-11-2021: the software has been modified (a LND payment flashed the lights as if you got paid).

I intended to add also a small actuator for the “fapping” effect, but the power supply was already a problem, so this was a no go.

On the right hand side, you can find the final software. Since the software is rather messy (understatement), feel free to improve the software.

Furthermore, you can see the resulting goose with green laser eyes.

Enjoy your Nodeuitgans!

Nooduitgans