├── .github
└── workflows
│ └── update-issue-comment.yml
├── .gitignore
├── LICENSE
├── README.md
├── esp8266_iot_button.ino
├── esp8266_iot_button_actions.ino
└── esp8266_iot_button_thing.ino
/.github/workflows/update-issue-comment.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow that is manually triggered
2 |
3 | name: IoT Button Press Workflow
4 |
5 | # Controls when the action will run. Workflow runs when manually triggered using the UI
6 | # or API.
7 | on:
8 | workflow_dispatch:
9 | # Inputs the workflow accepts.
10 | inputs:
11 | button_name:
12 | # Friendly description to be shown in the UI instead of 'name'
13 | description: 'Button Name'
14 | # Default value if no value is explicitly provided
15 | default: 'Sparkfun Thing'
16 | # Input has to be provided for the workflow to run
17 | required: true
18 |
19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
20 | jobs:
21 | createorupdatecomment:
22 | # The type of runner that the job will run on
23 | runs-on: ubuntu-latest
24 | steps:
25 | - name: Get Time
26 | id: time
27 | uses: nanzm/get-time-action@v1.1
28 | with:
29 | timeZone: 8
30 | format: 'YYYY-MM-DD-HH-mm-ss'
31 | - name: Usage
32 | env:
33 | IME: "${{ steps.time.outputs.time }}"
34 | run: |
35 | echo $TIME
36 | - name: Find Comment
37 | uses: peter-evans/find-comment@v1
38 | id: fc
39 | with:
40 | issue-number: 76
41 | comment-author: 'github-actions[bot]'
42 | body-includes: The ${{ github.event.inputs.button_name }} button was pressed!
43 |
44 | - name: Create comment
45 | if: ${{ steps.fc.outputs.comment-id == 0 }}
46 | uses: peter-evans/create-or-update-comment@v1
47 | with:
48 | issue-number: 76
49 | body: |
50 | The ${{ github.event.inputs.button_name }} button was pressed!
51 | - **Yay!** :sparkles:
52 | - Pressed at: ${{ steps.time.outputs.time }}
53 | reaction-type: "rocket"
54 |
55 | - name: Update comment
56 | if: ${{ steps.fc.outputs.comment-id != 0 }}
57 | uses: peter-evans/create-or-update-comment@v1
58 | with:
59 | comment-id: ${{ steps.fc.outputs.comment-id }}
60 | body: |
61 | **Edit:** The ${{ github.event.inputs.button_name }} button was pressed again at ${{ steps.time.outputs.time }}!
62 | reaction-type: "rocket"
63 |
64 | # This workflow contains a single job called "greet"
65 | greet:
66 | # The type of runner that the job will run on
67 | runs-on: ubuntu-latest
68 |
69 | # Steps represent a sequence of tasks that will be executed as part of the job
70 | steps:
71 | - name: View context attributes
72 | uses: actions/github-script@v3
73 | with:
74 | script: console.log(context)
75 |
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Lua sources
2 | luac.out
3 |
4 | # luarocks build files
5 | *.src.rock
6 | *.zip
7 | *.tar.gz
8 |
9 | # Object files
10 | *.o
11 | *.os
12 | *.ko
13 | *.obj
14 | *.elf
15 |
16 | # Precompiled Headers
17 | *.gch
18 | *.pch
19 |
20 | # Libraries
21 | *.lib
22 | *.a
23 | *.la
24 | *.lo
25 | *.def
26 | *.exp
27 |
28 | # Shared objects (inc. Windows DLLs)
29 | *.dll
30 | *.so
31 | *.so.*
32 | *.dylib
33 |
34 | # Executables
35 | *.exe
36 | *.out
37 | *.app
38 | *.i*86
39 | *.x86_64
40 | *.hex
41 |
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Garth Vander Houwen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # esp8266 IoT Button using Github Actions
2 |
3 | 
4 | [Customized Project Enclosure - Thingiverse](http://www.thingiverse.com/thing:981124)
5 |
6 | Tested with Adafruit Huzzah, Sparkfun Thing and WEMOS D1 Mini
7 |
8 | ## Software
9 |
10 | An IoT Button using an ESP8266 WiFI microcontroller, a push button and github actions. POSTS to a [GitHub Actions Workflow dispatch event]( https://docs.github.com/en/rest/reference/actions#create-a-workflow-dispatch-event) endpoint which updates a comment on an issue specific to each button.
11 |
12 | I originally built this project using the IFTTT maker channel sending text messages, then switched to updating a github issue once text messages were no longer free. Now that IFTTT itself is no longer free I wrote a third sketch using GitHub Actions. This makes for a simpler demo anyways as the only service involved is GitHub and everything is stored in code. You can see the ReadMe with instructions from the original IFTTT project [here](https://github.com/garthvh/esp8266button/wiki/Original-IFTTT-Readme).
13 |
14 | The github actions workflow YML file update-issue-comment.yml is also in this repository and you can see the button press comments on the issue i have created for my SparkFun Thing button.
15 |
16 | You will need the following settings in the arduino sketch esp8266_iot_button_actions.ino
17 |
18 | // WiFi and GitHub Variables
19 | const char* ssid = "YOUR_SSID"; // SSID For the WiFi network you want to connect to
20 | const char* password = "YOUR_PSK"; // PSK For the WiFi network you want to connect to
21 | const char* github_user = "YOUR_GITHUB_USERNAME"; // GitHub User for running action
22 | const char* github_repo = "YOUR_GITHUB_REPO"; // GitHub Repo where action lives
23 | const char* github_token = "YOUR_GITHUB_TOKEN"; // GitHub Authorization Token
24 | const char* github_workflow_id = "YOUR_WORKFLOW_YML_FILE"; // Workflow YML file name
25 | const char* host = "api.github.com"; // Server from which data is to be fetched
26 | const int httpsPort = 443; // Default port for HTTPS
27 |
28 |
29 | You will also need to copy the YML from the action in this repository and create a new issue where the comments from the button will go. The issue number in the YML file should be updated to the number of the newly created issue.
30 |
31 | ## Hardware
32 |
33 | I soldered male pins on my Huzzah, and added female headers to my Sparkfun Thing. The thing did not come with any headers and male headers were included with the Huzzah.
34 |
35 | I was able to program both with my [FTDI Friend](https://www.adafruit.com/product/284), you will need to cut the default RTS jumper on the back of the FTDI Friend (used by the Huzzah) and connect the DTR jumper to program the thing. Once cut it has been pretty easy to switch back and forth by soldering the jumpers as needed.
36 |
37 | 
38 |
39 | The built in battery and charging circuit on the ESP8266 Thing really makes it easy to work with. By cutting the DTR trace on the bottom of the board and installing pins for a jumper I am able to program the thing with the jumper installed, and debug over serial with it removed.
40 |
41 | 
42 |
43 | ## BOM
44 |
45 | ### Adafruit Huzzah
46 |
47 | * [Adafruit Huzzah](https://www.adafruit.com/products/2471)
48 | * [Arcade Button](https://www.sparkfun.com/products/9339)
49 | * Standard RGB LCD
50 | * 4 M3 20MM Hex Screws
51 | * AAA Battery Pack
52 | * Female Jumper wires
53 |
54 | ### Sparkfun Thing
55 |
56 | * [Sparkfun Thing](https://www.sparkfun.com/products/13231)
57 | * [Panel Mount Push Button](https://www.adafruit.com/products/1504)
58 | * Standard Green LCD
59 | * 4 M3 20MM Hex Screws
60 | * [150 mAh LiPo Battery](https://www.adafruit.com/product/1317)
61 | * Male Jumper wires
62 |
63 |
64 | ## Enclosure
65 | Using this awesome [Parametric and Customizable Project Enclosure](http://www.thingiverse.com/thing:155001) I made customized enclosures that fit the parts I was using for my buttons.
66 |
67 | 
68 |
69 | [Customized Project Enclosure - Thingiverse](http://www.thingiverse.com/thing:941755)
70 |
--------------------------------------------------------------------------------
/esp8266_iot_button.ino:
--------------------------------------------------------------------------------
1 | /*----------------------------------------------------------------------------
2 | Simple IoT button using the If This Then That(IFTTT) Maker Channel, Sparkfun
3 | ESP8266 Thing or Adafruit Huzzah, a standard Green LED(or onboard green LED)
4 | and a push button. Optional RGB LED for more detailed status indicators
5 |
6 | While attempting to connect to the specified SSID the LCD flashes,
7 | when connected the led remains green, while data is sending the led turns
8 | off, when done it turns back to green.
9 |
10 | If a saved setting is not found in EEPROM for an available WiFi network the
11 | ESP8266 will enter access point mode and allow the user to configure WiFI
12 | ----------------------------------------------------------------------------*/
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 |
21 | /////////////////////////
22 | // Network Definitions //
23 | /////////////////////////
24 | const IPAddress AP_IP(192, 168, 1, 1);
25 | const char* AP_SSID = "ESP8266_IOT_BUTTON_SETUP";
26 | boolean SETUP_MODE;
27 | String SSID_LIST;
28 | DNSServer DNS_SERVER;
29 | ESP8266WebServer WEB_SERVER(80);
30 |
31 | /////////////////////////
32 | // Device Definitions //
33 | /////////////////////////
34 | String DEVICE_TITLE = "IFTTT ESP8266 Dash Like Button";
35 | boolean POWER_SAVE = false;
36 | boolean RGB_LCD = true;
37 |
38 | ///////////////////////
39 | // IFTTT Definitions //
40 | ///////////////////////
41 | const char* IFTTT_URL= "maker.ifttt.com";
42 | const char* IFTTT_KEY= "YOUR IFTTT_KEY";
43 | const char* IFTTT_EVENT = "YOUR_IFTTT_EVENT";
44 | const char* IFTTT_NOTIFICATION_EVENT = "YOUR_IFTTT_NOTIFICATION_EVENT";
45 |
46 | /////////////////////
47 | // Pin Definitions //
48 | /////////////////////
49 | const int LED_GREEN = 5;
50 | // Blue and Red LED Pins if RGB LCD is enabled
51 | const int LED_RED = 0;
52 | const int LED_BLUE = 4;
53 | const int BUTTON_PIN = 2;
54 |
55 | //////////////////////
56 | // Button Variables //
57 | //////////////////////
58 | int BUTTON_STATE;
59 | int LAST_BUTTON_STATE = LOW;
60 | long LAST_DEBOUNCE_TIME = 0;
61 | long DEBOUNCE_DELAY = 50;
62 | int BUTTON_COUNTER = 0;
63 |
64 | void setup() {
65 |
66 | initHardware();
67 | // Try and restore saved settings
68 | if (loadSavedConfig()) {
69 | if (checkWiFiConnection()) {
70 | SETUP_MODE = false;
71 | startWebServer();
72 | // Turn the status led Green when the WiFi has been connected
73 | digitalWrite(LED_GREEN, HIGH);
74 | return;
75 | }
76 | }
77 | SETUP_MODE = true;
78 | setupMode();
79 | }
80 |
81 | void loop() {
82 |
83 | // Handle WiFi Setup and Webserver for reset page
84 | if (SETUP_MODE) {
85 | DNS_SERVER.processNextRequest();
86 | }
87 | WEB_SERVER.handleClient();
88 |
89 | // Wait for button Presses
90 | boolean pressed = debounce();
91 | if (pressed == true) {
92 | BUTTON_COUNTER++;
93 | Serial.print("Trigger" + String(IFTTT_EVENT) + " Event Pressed ");
94 | Serial.print(BUTTON_COUNTER);
95 | Serial.println(" times");
96 | if(BUTTON_COUNTER > 1)
97 | {
98 | // Turn off the Green LED while transmitting.
99 | digitalWrite(LED_GREEN, LOW);
100 | if(RGB_LCD == true){
101 | digitalWrite(LED_BLUE, HIGH);
102 | }
103 | triggerButtonEvent(IFTTT_EVENT);
104 | // After a successful send turn the light back to green
105 | if(RGB_LCD == true){
106 | digitalWrite(LED_BLUE, LOW);
107 | }
108 | digitalWrite(LED_GREEN, HIGH);
109 | }
110 | }
111 | }
112 |
113 | void initHardware()
114 | {
115 | // Serial and EEPROM
116 | Serial.begin(115200);
117 | EEPROM.begin(512);
118 | delay(10);
119 | // LEDS
120 | pinMode(LED_GREEN, OUTPUT);
121 | digitalWrite(LED_GREEN, LOW);
122 | if(RGB_LCD == true){
123 | pinMode(LED_RED, OUTPUT);
124 | digitalWrite(LED_RED, LOW);
125 | pinMode(LED_BLUE, OUTPUT);
126 | digitalWrite(LED_BLUE, LOW);
127 | }
128 | // Button
129 | pinMode(BUTTON_PIN, INPUT);
130 |
131 | }
132 |
133 | //////////////////////
134 | // Button Functions //
135 | //////////////////////
136 | void triggerButtonEvent(String eventName)
137 | {
138 | // Define the WiFi Client
139 | WiFiClient client;
140 | // Set the http Port
141 | const int httpPort = 80;
142 |
143 | // Make sure we can connect
144 | if (!client.connect(IFTTT_URL, httpPort)) {
145 | return;
146 | }
147 |
148 | // We now create a URI for the request
149 | String url = "/trigger/" + String(eventName) + "/with/key/" + String(IFTTT_KEY);
150 |
151 | // Set some values for the JSON data depending on which event has been triggered
152 | IPAddress ip = WiFi.localIP();
153 | String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
154 | String value_1 = "";
155 | String value_2 = "";
156 | String value_3 = "";
157 | if(eventName == IFTTT_EVENT){
158 | value_1 = String(BUTTON_COUNTER -1);
159 | value_2 = ipStr;
160 | value_3 = WiFi.gatewayIP().toString();
161 | }
162 | else if(eventName == IFTTT_NOTIFICATION_EVENT){
163 | value_1 = ipStr;
164 | value_2 = WiFi.SSID();
165 | }
166 |
167 |
168 | // Build JSON data string
169 | String data = "";
170 | data = data + "\n" + "{\"value1\":\""+ value_1 +"\",\"value2\":\""+ value_2 +"\",\"value3\":\""+ value_3 + "\"}";
171 |
172 | // Post the button press to IFTTT
173 | if (client.connect(IFTTT_URL, httpPort)) {
174 |
175 | // Sent HTTP POST Request with JSON data
176 | client.println("POST "+ url +" HTTP/1.1");
177 | Serial.println("POST "+ url +" HTTP/1.1");
178 | client.println("Host: "+ String(IFTTT_URL));
179 | Serial.println("Host: "+ String(IFTTT_URL));
180 | client.println("User-Agent: Arduino/1.0");
181 | Serial.println("User-Agent: Arduino/1.0");
182 | client.print("Accept: *");
183 | Serial.print("Accept: *");
184 | client.print("/");
185 | Serial.print("/");
186 | client.println("*");
187 | Serial.println("*");
188 | client.print("Content-Length: ");
189 | Serial.print("Content-Length: ");
190 | client.println(data.length());
191 | Serial.println(data.length());
192 | client.println("Content-Type: application/json");
193 | Serial.println("Content-Type: application/json");
194 | client.println("Connection: close");
195 | Serial.println("Connection: close");
196 | client.println();
197 | Serial.println();
198 | client.println(data);
199 | Serial.println(data);
200 | }
201 | }
202 |
203 | // Debounce Button Presses
204 | boolean debounce() {
205 | boolean retVal = false;
206 | int reading = digitalRead(BUTTON_PIN);
207 | if (reading != LAST_BUTTON_STATE) {
208 | LAST_DEBOUNCE_TIME = millis();
209 | }
210 | if ((millis() - LAST_DEBOUNCE_TIME) > DEBOUNCE_DELAY) {
211 | if (reading != BUTTON_STATE) {
212 | BUTTON_STATE = reading;
213 | if (BUTTON_STATE == HIGH) {
214 | retVal = true;
215 | }
216 | }
217 | }
218 | LAST_BUTTON_STATE = reading;
219 | return retVal;
220 | }
221 |
222 | /////////////////////////////
223 | // AP Setup Mode Functions //
224 | /////////////////////////////
225 |
226 | // Load Saved Configuration from EEPROM
227 | boolean loadSavedConfig() {
228 | Serial.println("Reading Saved Config....");
229 | String ssid = "";
230 | String password = "";
231 | if (EEPROM.read(0) != 0) {
232 | for (int i = 0; i < 32; ++i) {
233 | ssid += char(EEPROM.read(i));
234 | }
235 | Serial.print("SSID: ");
236 | Serial.println(ssid);
237 | for (int i = 32; i < 96; ++i) {
238 | password += char(EEPROM.read(i));
239 | }
240 | Serial.print("Password: ");
241 | Serial.println(password);
242 | WiFi.begin(ssid.c_str(), password.c_str());
243 | return true;
244 | }
245 | else {
246 | Serial.println("Saved Configuration not found.");
247 | return false;
248 | }
249 | }
250 |
251 | // Boolean function to check for a WiFi Connection
252 | boolean checkWiFiConnection() {
253 | int count = 0;
254 | Serial.print("Waiting to connect to the specified WiFi network");
255 | while ( count < 30 ) {
256 | if (WiFi.status() == WL_CONNECTED) {
257 | Serial.println();
258 | Serial.println("Connected!");
259 | return (true);
260 | }
261 | delay(500);
262 | Serial.print(".");
263 | count++;
264 | }
265 | Serial.println("Timed out.");
266 | return false;
267 | }
268 |
269 | // Start the web server and build out pages
270 | void startWebServer() {
271 | if (SETUP_MODE) {
272 | Serial.print("Starting Web Server at IP address: ");
273 | Serial.println(WiFi.softAPIP());
274 | // Settings Page
275 | WEB_SERVER.on("/settings", []() {
276 | String s = "
Wi-Fi Settings
Please select the SSID of the network you wish to connect to and then enter the password and submit.
";
277 | s += "";
280 | WEB_SERVER.send(200, "text/html", makePage("Wi-Fi Settings", s));
281 | });
282 | // setap Form Post
283 | WEB_SERVER.on("/setap", []() {
284 | for (int i = 0; i < 96; ++i) {
285 | EEPROM.write(i, 0);
286 | }
287 | String ssid = urlDecode(WEB_SERVER.arg("ssid"));
288 | Serial.print("SSID: ");
289 | Serial.println(ssid);
290 | String pass = urlDecode(WEB_SERVER.arg("pass"));
291 | Serial.print("Password: ");
292 | Serial.println(pass);
293 | Serial.println("Writing SSID to EEPROM...");
294 | for (int i = 0; i < ssid.length(); ++i) {
295 | EEPROM.write(i, ssid[i]);
296 | }
297 | Serial.println("Writing Password to EEPROM...");
298 | for (int i = 0; i < pass.length(); ++i) {
299 | EEPROM.write(32 + i, pass[i]);
300 | }
301 | EEPROM.commit();
302 | Serial.println("Write EEPROM done!");
303 | String s = "
WiFi Setup complete.
The button will be connected automatically to \"";
304 | s += ssid;
305 | s += "\" after the restart.";
306 | WEB_SERVER.send(200, "text/html", makePage("Wi-Fi Settings", s));
307 | ESP.restart();
308 | });
309 | // Show the configuration page if no path is specified
310 | WEB_SERVER.onNotFound([]() {
311 | String s = "
";
338 | WEB_SERVER.send(200, "text/html", makePage("Reset Wi-Fi Settings", s));
339 | });
340 | }
341 | WEB_SERVER.begin();
342 | triggerButtonEvent(IFTTT_NOTIFICATION_EVENT);
343 | }
344 |
345 | // Build the SSID list and setup a software access point for setup mode
346 | void setupMode() {
347 | WiFi.mode(WIFI_STA);
348 | WiFi.disconnect();
349 | delay(100);
350 | int n = WiFi.scanNetworks();
351 | delay(100);
352 | Serial.println("");
353 | for (int i = 0; i < n; ++i) {
354 | SSID_LIST += "";
359 | }
360 | delay(100);
361 | WiFi.mode(WIFI_AP);
362 | WiFi.softAPConfig(AP_IP, AP_IP, IPAddress(255, 255, 255, 0));
363 | WiFi.softAP(AP_SSID);
364 | DNS_SERVER.start(53, "*", AP_IP);
365 | startWebServer();
366 | Serial.print("Starting Access Point at \"");
367 | Serial.print(AP_SSID);
368 | Serial.println("\"");
369 | }
370 |
371 | String makePage(String title, String contents) {
372 | String s = "";
373 | s += "";
374 | s += "";
382 | s += "";
383 | s += title;
384 | s += "";
385 | s += "
" + DEVICE_TITLE + "
";
386 | s += "
";
387 | s += contents;
388 | s += "
";
389 | s += "";
390 | return s;
391 | }
392 |
393 | String urlDecode(String input) {
394 | String s = input;
395 | s.replace("%20", " ");
396 | s.replace("+", " ");
397 | s.replace("%21", "!");
398 | s.replace("%22", "\"");
399 | s.replace("%23", "#");
400 | s.replace("%24", "$");
401 | s.replace("%25", "%");
402 | s.replace("%26", "&");
403 | s.replace("%27", "\'");
404 | s.replace("%28", "(");
405 | s.replace("%29", ")");
406 | s.replace("%30", "*");
407 | s.replace("%31", "+");
408 | s.replace("%2C", ",");
409 | s.replace("%2E", ".");
410 | s.replace("%2F", "/");
411 | s.replace("%2C", ",");
412 | s.replace("%3A", ":");
413 | s.replace("%3A", ";");
414 | s.replace("%3C", "<");
415 | s.replace("%3D", "=");
416 | s.replace("%3E", ">");
417 | s.replace("%3F", "?");
418 | s.replace("%40", "@");
419 | s.replace("%5B", "[");
420 | s.replace("%5C", "\\");
421 | s.replace("%5D", "]");
422 | s.replace("%5E", "^");
423 | s.replace("%5F", "-");
424 | s.replace("%60", "`");
425 | return s;
426 | }
427 |
428 | /////////////////////////
429 | // Debugging Functions //
430 | /////////////////////////
431 |
432 | void wipeEEPROM()
433 | {
434 | EEPROM.begin(512);
435 | // write a 0 to all 512 bytes of the EEPROM
436 | for (int i = 0; i < 512; i++)
437 | EEPROM.write(i, 0);
438 |
439 | EEPROM.end();
440 | }
441 |
--------------------------------------------------------------------------------
/esp8266_iot_button_actions.ino:
--------------------------------------------------------------------------------
1 | /*
2 | * Use HTTPS to trigger a GitHub Action
3 | *
4 | * Created by Garth Vander Houwen, 2021.
5 | *
6 | */
7 | // Libraries
8 | #include
9 | #include
10 | #include
11 |
12 | // WiFi and GitHub Variables
13 | const char* ssid = "YOUR_SSID"; // SSID For the WiFi network you want to connect to
14 | const char* password = "YOUR_PSK"; // PSK For the WiFi network you want to connect to
15 | const char* github_user = "YOUR_GITHUB_USERNAME"; // GitHub User for running action
16 | const char* github_repo = "YOUR_GITHUB_REPO"; // GitHub Repo where action lives
17 | const char* github_token = "YOUR_GITHUB_TOKEN"; // GitHub Authorization Token
18 | const char* github_workflow_id = "YOUR_WORKFLOW_YML_FILE"; // Workflow YML file name
19 | const char* host = "api.github.com"; // Server from which data is to be fetched
20 | const int httpsPort = 443; // Default port for HTTPS
21 |
22 | // Use web browser to view and copy
23 | // SHA1 fingerprint of the certificate
24 | const char* fingerprint = "35 85 74 ef 67 35 a7 ce 40 69 50 f3 c0 f6 80 cf 80 3b 2e 19"; // Fingerprint/Thumbprint for website api.github.com
25 |
26 | // Button Variables
27 | const int buttonPin = 0; // pushbutton pin
28 | const int ledPin = 4; // LED pin
29 |
30 | int ledState = LOW; // current state of LED
31 | int buttonState = LOW; // current state of button
32 | int lastButtonState = LOW; // previous state of button
33 |
34 | unsigned long lastDebounceTime = 0; // last toggle
35 | unsigned long debounceDelay = 50; // debounce time
36 |
37 | void setup() {
38 |
39 | // Start Serial
40 | Serial.begin(115200);
41 | Serial.println();
42 | Serial.println("....");
43 | Serial.println("....");
44 | delay (5000);
45 | // Connect WiFi
46 | Serial.print("Connecting to ");
47 | Serial.print(ssid);
48 | WiFi.begin(ssid, password);
49 | while (WiFi.status() != WL_CONNECTED) {
50 | delay(500);
51 | Serial.print(".");
52 | }
53 | Serial.println("");
54 | Serial.println("WiFi connected");
55 | Serial.print("IP address: ");
56 | Serial.println(WiFi.localIP()); // Print out the Local IP assigned by the router to ESP8266
57 | }
58 |
59 | void loop() {
60 | int reading = digitalRead(buttonPin); //read
61 |
62 | if (reading != lastButtonState) // If the switch changed
63 | {
64 | lastDebounceTime = millis(); // reset the debouncing
65 | }
66 | //check if debounce time > 50ms
67 | if ((millis() - lastDebounceTime) > debounceDelay)
68 | { if (reading != buttonState)
69 | { buttonState = reading;
70 | if (buttonState == LOW)
71 | {
72 | Serial.println("button pressed");
73 | SendDispatch();
74 | }
75 | }
76 | }
77 | lastButtonState = reading; // save the reading
78 | }
79 |
80 | void SendDispatch(){
81 |
82 | // Setup HTTPS Client
83 | WiFiClientSecure client; // Use WiFiClientSecure class to create client instance
84 | Serial.print("connecting to ");
85 | Serial.println(host);
86 | client.setInsecure();
87 |
88 | // Connect with the server api.github.com at port 443
89 | if (!client.connect(host, httpsPort)) {
90 | Serial.println("connection failed");
91 | return;
92 | }
93 |
94 | // Verify fingerprint
95 | if (client.verify(fingerprint, host)) {
96 | Serial.println("certificate matches");
97 | }
98 | else {
99 | Serial.println("certificate doesn't match");
100 | }
101 |
102 | // Execute a POST request to create a workflow dispatch event
103 | // https://docs.github.com/en/rest/reference/actions#create-a-workflow-dispatch-event
104 |
105 | // Parameters
106 | // Name Type In Description
107 | // ------------|-------|-------|----------
108 | // accept string header Setting to application/vnd.github.v3+json is recommended.
109 | // owner string path
110 | // repo string path
111 | // workflow_id string path The ID of the workflow. You can also pass the workflow file name as a string.
112 | // ref string body Required. The git reference for the workflow. The reference can be a branch or tag name.
113 | // inputs object body Input keys and values configured in the workflow file. The maximum number of properties is 10. Any default properties configured in the workflow file will be used when inputs are omitted.
114 |
115 | // GitHub Actions API URL /repos/{github_user}/{github_repo}/actions/workflows/{github_workflow_id}/dispatches
116 | String url = "https://api.github.com/repos/" + String(github_user) + "/" + String(github_repo) + "/actions/workflows/" + String(github_workflow_id) + "/dispatches";
117 |
118 | // JSON data with the tag or branch you want the action to run against pass up to 10 custom inputs
119 | String data = "{\"ref\":\"main\",\"inputs\":{\"button_name\":\"SparkFun Thing #76\"}}";
120 |
121 | HTTPClient https;
122 | Serial.println("Starting https client");
123 | if (https.begin(client, url)) {
124 | https.addHeader("Content-Type", "application/json");
125 | https.addHeader("Authorization", "token " + String(github_token));
126 | Serial.println("posting to the github api");
127 | int httpCode = https.POST(data);
128 | // httpCode will be negative on error
129 | if (httpCode > 0) {
130 | // HTTP header has been send and Server response header has been handled
131 | Serial.printf("POST http status code: %d\n", httpCode);
132 | // handle response
133 | if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_NO_CONTENT || HTTP_CODE_MOVED_PERMANENTLY) {
134 | String payload = https.getString();
135 | Serial.println("successfully posted the following JSON data");
136 | Serial.println(data);
137 | }
138 | }
139 | else {
140 | Serial.printf("POST failed, error: %s\n", https.errorToString(httpCode).c_str());
141 | String payload = https.getString();
142 | Serial.println("failed to post the following JSON data");
143 | Serial.println(payload);
144 | }
145 | https.end();
146 | }
147 | else {
148 | Serial.printf("unable to connect\n");
149 | }
150 | Serial.println("closing connection");
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/esp8266_iot_button_thing.ino:
--------------------------------------------------------------------------------
1 | /*----------------------------------------------------------------------------
2 | Simple IoT button using the If This Then That(IFTTT) Maker Channel, Sparkfun
3 | ESP8266 Thing or Adafruit Huzzah, a standard Green LED(or onboard green LED)
4 | and a push button. Optional RGB LED for more detailed status indicators
5 |
6 | While attempting to connect to the specified SSID the LCD flashes,
7 | when connected the led remains green, while data is sending the led turns
8 | off, when done it turns back to green.
9 |
10 | If a saved setting is not found in EEPROM for an available WiFi network the
11 | ESP8266 will enter access point mode and allow the user to configure WiFI
12 | ----------------------------------------------------------------------------*/
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 |
21 | /////////////////////////
22 | // Network Definitions //
23 | /////////////////////////
24 | const IPAddress AP_IP(192, 168, 1, 1);
25 | const char* AP_SSID = "ESP8266_IOT_BUTTON_SETUP";
26 | boolean SETUP_MODE;
27 | String SSID_LIST;
28 | DNSServer DNS_SERVER;
29 | ESP8266WebServer WEB_SERVER(80);
30 |
31 | /////////////////////////
32 | // Device Definitions //
33 | /////////////////////////
34 | String DEVICE_TITLE = "IFTTT ESP8266 Dash Like Button";
35 | boolean POWER_SAVE = false;
36 | boolean RGB_LCD = false;
37 |
38 | ///////////////////////
39 | // IFTTT Definitions //
40 | ///////////////////////
41 | const char* IFTTT_URL= "maker.ifttt.com";
42 | const char* IFTTT_KEY= "YOUR IFTTT_KEY";
43 | const char* IFTTT_EVENT = "YOUR_IFTTT_EVENT";
44 | const char* IFTTT_NOTIFICATION_EVENT = "YOUR_IFTTT_NOTIFICATION_EVENT";
45 |
46 | /////////////////////
47 | // Pin Definitions //
48 | /////////////////////
49 | const int LED_GREEN = 5;
50 | // Blue and Red LED Pins if RGB LCD is enabled
51 | const int LED_RED = 2;
52 | const int LED_BLUE = 4;
53 | const int BUTTON_PIN = 0;
54 |
55 | //////////////////////
56 | // Button Variables //
57 | //////////////////////
58 | int BUTTON_STATE;
59 | int LAST_BUTTON_STATE = LOW;
60 | long LAST_DEBOUNCE_TIME = 0;
61 | long DEBOUNCE_DELAY = 50;
62 | int BUTTON_COUNTER = 0;
63 |
64 | void setup() {
65 |
66 | initHardware();
67 | // Try and restore saved settings
68 | if (loadSavedConfig()) {
69 | if (checkWiFiConnection()) {
70 | SETUP_MODE = false;
71 | startWebServer();
72 | // Turn the status led Green when the WiFi has been connected
73 | digitalWrite(LED_GREEN, HIGH);
74 | return;
75 | }
76 | }
77 | SETUP_MODE = true;
78 | setupMode();
79 | }
80 |
81 | void loop() {
82 |
83 | // Handle WiFi Setup and Webserver for reset page
84 | if (SETUP_MODE) {
85 | DNS_SERVER.processNextRequest();
86 | }
87 | WEB_SERVER.handleClient();
88 |
89 | // Wait for button Presses
90 | boolean pressed = debounce();
91 | if (pressed == true) {
92 | BUTTON_COUNTER++;
93 | Serial.print("Trigger" + String(IFTTT_EVENT) + " Event Pressed ");
94 | Serial.print(BUTTON_COUNTER);
95 | Serial.println(" times");
96 | if(BUTTON_COUNTER > 1)
97 | {
98 | // Turn off the Green LED while transmitting.
99 | digitalWrite(LED_GREEN, LOW);
100 | if(RGB_LCD == true){
101 | digitalWrite(LED_BLUE, HIGH);
102 | }
103 | triggerButtonEvent(IFTTT_EVENT);
104 | // After a successful send turn the light back to green
105 | if(RGB_LCD == true){
106 | digitalWrite(LED_BLUE, LOW);
107 | }
108 | digitalWrite(LED_GREEN, HIGH);
109 | }
110 | }
111 | }
112 |
113 | void initHardware()
114 | {
115 | // Serial and EEPROM
116 | Serial.begin(115200);
117 | EEPROM.begin(512);
118 | delay(10);
119 | // LEDS
120 | pinMode(LED_GREEN, OUTPUT);
121 | digitalWrite(LED_GREEN, LOW);
122 | if(RGB_LCD == true){
123 | pinMode(LED_RED, OUTPUT);
124 | digitalWrite(LED_RED, LOW);
125 | pinMode(LED_BLUE, OUTPUT);
126 | digitalWrite(LED_BLUE, LOW);
127 | }
128 | // Button
129 | pinMode(BUTTON_PIN, INPUT);
130 |
131 | }
132 |
133 | //////////////////////
134 | // Button Functions //
135 | //////////////////////
136 | void triggerButtonEvent(String eventName)
137 | {
138 | // Define the WiFi Client
139 | WiFiClient client;
140 | // Set the http Port
141 | const int httpPort = 80;
142 |
143 | // Make sure we can connect
144 | if (!client.connect(IFTTT_URL, httpPort)) {
145 | return;
146 | }
147 |
148 | // We now create a URI for the request
149 | String url = "/trigger/" + String(eventName) + "/with/key/" + String(IFTTT_KEY);
150 |
151 | // Set some values for the JSON data depending on which event has been triggered
152 | IPAddress ip = WiFi.localIP();
153 | String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
154 | String value_1 = "";
155 | String value_2 = "";
156 | String value_3 = "";
157 | if(eventName == IFTTT_EVENT){
158 | value_1 = String(BUTTON_COUNTER -1);
159 | value_2 = ipStr;
160 | value_3 = WiFi.gatewayIP().toString();
161 | }
162 | else if(eventName == IFTTT_NOTIFICATION_EVENT){
163 | value_1 = ipStr;
164 | value_2 = WiFi.SSID();
165 | }
166 |
167 |
168 | // Build JSON data string
169 | String data = "";
170 | data = data + "\n" + "{\"value1\":\""+ value_1 +"\",\"value2\":\""+ value_2 +"\",\"value3\":\""+ value_3 + "\"}";
171 |
172 | // Post the button press to IFTTT
173 | if (client.connect(IFTTT_URL, httpPort)) {
174 |
175 | // Sent HTTP POST Request with JSON data
176 | client.println("POST "+ url +" HTTP/1.1");
177 | Serial.println("POST "+ url +" HTTP/1.1");
178 | client.println("Host: "+ String(IFTTT_URL));
179 | Serial.println("Host: "+ String(IFTTT_URL));
180 | client.println("User-Agent: Arduino/1.0");
181 | Serial.println("User-Agent: Arduino/1.0");
182 | client.print("Accept: *");
183 | Serial.print("Accept: *");
184 | client.print("/");
185 | Serial.print("/");
186 | client.println("*");
187 | Serial.println("*");
188 | client.print("Content-Length: ");
189 | Serial.print("Content-Length: ");
190 | client.println(data.length());
191 | Serial.println(data.length());
192 | client.println("Content-Type: application/json");
193 | Serial.println("Content-Type: application/json");
194 | client.println("Connection: close");
195 | Serial.println("Connection: close");
196 | client.println();
197 | Serial.println();
198 | client.println(data);
199 | Serial.println(data);
200 | }
201 | }
202 |
203 | // Debounce Button Presses
204 | boolean debounce() {
205 | boolean retVal = false;
206 | int reading = digitalRead(BUTTON_PIN);
207 | if (reading != LAST_BUTTON_STATE) {
208 | LAST_DEBOUNCE_TIME = millis();
209 | }
210 | if ((millis() - LAST_DEBOUNCE_TIME) > DEBOUNCE_DELAY) {
211 | if (reading != BUTTON_STATE) {
212 | BUTTON_STATE = reading;
213 | if (BUTTON_STATE == HIGH) {
214 | retVal = true;
215 | }
216 | }
217 | }
218 | LAST_BUTTON_STATE = reading;
219 | return retVal;
220 | }
221 |
222 | /////////////////////////////
223 | // AP Setup Mode Functions //
224 | /////////////////////////////
225 |
226 | // Load Saved Configuration from EEPROM
227 | boolean loadSavedConfig() {
228 | Serial.println("Reading Saved Config....");
229 | String ssid = "";
230 | String password = "";
231 | if (EEPROM.read(0) != 0) {
232 | for (int i = 0; i < 32; ++i) {
233 | ssid += char(EEPROM.read(i));
234 | }
235 | Serial.print("SSID: ");
236 | Serial.println(ssid);
237 | for (int i = 32; i < 96; ++i) {
238 | password += char(EEPROM.read(i));
239 | }
240 | Serial.print("Password: ");
241 | Serial.println(password);
242 | WiFi.begin(ssid.c_str(), password.c_str());
243 | return true;
244 | }
245 | else {
246 | Serial.println("Saved Configuration not found.");
247 | return false;
248 | }
249 | }
250 |
251 | // Boolean function to check for a WiFi Connection
252 | boolean checkWiFiConnection() {
253 | int count = 0;
254 | Serial.print("Waiting to connect to the specified WiFi network");
255 | while ( count < 30 ) {
256 | if (WiFi.status() == WL_CONNECTED) {
257 | Serial.println();
258 | Serial.println("Connected!");
259 | return (true);
260 | }
261 | delay(500);
262 | Serial.print(".");
263 | count++;
264 | }
265 | Serial.println("Timed out.");
266 | return false;
267 | }
268 |
269 | // Start the web server and build out pages
270 | void startWebServer() {
271 | if (SETUP_MODE) {
272 | Serial.print("Starting Web Server at IP address: ");
273 | Serial.println(WiFi.softAPIP());
274 | // Settings Page
275 | WEB_SERVER.on("/settings", []() {
276 | String s = "
Wi-Fi Settings
Please select the SSID of the network you wish to connect to and then enter the password and submit.
";
277 | s += "";
280 | WEB_SERVER.send(200, "text/html", makePage("Wi-Fi Settings", s));
281 | });
282 | // setap Form Post
283 | WEB_SERVER.on("/setap", []() {
284 | for (int i = 0; i < 96; ++i) {
285 | EEPROM.write(i, 0);
286 | }
287 | String ssid = urlDecode(WEB_SERVER.arg("ssid"));
288 | Serial.print("SSID: ");
289 | Serial.println(ssid);
290 | String pass = urlDecode(WEB_SERVER.arg("pass"));
291 | Serial.print("Password: ");
292 | Serial.println(pass);
293 | Serial.println("Writing SSID to EEPROM...");
294 | for (int i = 0; i < ssid.length(); ++i) {
295 | EEPROM.write(i, ssid[i]);
296 | }
297 | Serial.println("Writing Password to EEPROM...");
298 | for (int i = 0; i < pass.length(); ++i) {
299 | EEPROM.write(32 + i, pass[i]);
300 | }
301 | EEPROM.commit();
302 | Serial.println("Write EEPROM done!");
303 | String s = "
WiFi Setup complete.
The button will be connected automatically to \"";
304 | s += ssid;
305 | s += "\" after the restart.";
306 | WEB_SERVER.send(200, "text/html", makePage("Wi-Fi Settings", s));
307 | ESP.restart();
308 | });
309 | // Show the configuration page if no path is specified
310 | WEB_SERVER.onNotFound([]() {
311 | String s = "