├── .gitattributes ├── .gitignore ├── DeviceDataDashboard ├── WiFiTCPClientLogger │ └── WiFiTCPClientLogger.ino ├── WiFiUDPClientLogger │ └── WiFiUDPClientLogger.ino ├── dashboard │ ├── index.html │ ├── log.json │ └── script.js └── readme.md ├── LICENSE ├── _config.yml ├── _includes └── nav.html ├── _layouts └── default.html ├── bluetooth-p5ble ├── ButtonLED │ ├── index.html │ ├── script.js │ └── styles.css └── ReadOneCharacteristic │ ├── SensorCharacteristic │ └── SensorCharacteristic.ino │ ├── SensorCharacteristicBetter │ └── SensorCharacteristicBetter.ino │ ├── index.html │ ├── script.js │ └── styles.css ├── fetch-with-server ├── package-lock.json ├── package.json ├── public │ ├── index.html │ ├── script.js │ └── style.css └── server.js ├── fetch ├── index.html └── script.js ├── geolocation ├── index.html └── script.js ├── input-types ├── img │ └── button.png ├── index.html ├── script.js └── styles.css ├── purifier.html ├── qr-code ├── index.html └── script.js ├── queryString ├── SSD1306_QRCode_redirect │ └── SSD1306_QRCode_redirect.ino ├── index.html ├── script.js └── styles.css ├── readme.md ├── responsive-layout ├── index.html ├── script.js └── styles.css ├── sensor-tests ├── index.html ├── script.js └── styles.css ├── serialport ├── ArduinoSendJSON │ └── ArduinoSendJSON.ino ├── img │ ├── nano_button_potentiometer.fzz │ ├── nano_button_potentiometer_bb.png │ ├── nano_button_potentiometer_bb.svg │ └── nano_button_potentiometer_schem.svg ├── index.html ├── script.js └── styles.css ├── template ├── index.html ├── script.js └── styles.css ├── webSerial ├── ArduinoSendReceive │ └── ArduinoSendReceive.ino ├── index.html ├── p5.webserial │ ├── index.html │ └── sketch.js ├── script.js ├── styles.css ├── webSerial-p5 │ ├── index.html │ └── sketch.js └── webserial.js └── websocket ├── index.html ├── script.js └── styles.css /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | DeviceDataDashboard/WiFiTCPClientLogger/arduino_secrets.h 4 | */node_modules/* 5 | DeviceDataDashboard/WiFiUDPClientLogger/arduino_secrets.h 6 | -------------------------------------------------------------------------------- /DeviceDataDashboard/WiFiTCPClientLogger/WiFiTCPClientLogger.ino: -------------------------------------------------------------------------------- 1 | /* 2 | WiFi TCP Client 3 | TCP Socket client for WiFiNINA and WiFi101 libraries. 4 | Connects to the TCP socket server, reads a sensor once 5 | every five seconds, and sends a message with the reading. 6 | 7 | You'll need to include an arduino_secrets.h file with the following info: 8 | #define SECRET_SSID "ssid" // your network name 9 | #define SECRET_PASS "password" // your network password 10 | 11 | Here's a test with netcat: 12 | char serverAddress[] = "x.x.x.x"; // replace with your computer's IP 13 | then on your computer, run netcat: 14 | $ nc -klw 2 8080 | tee log.json 15 | This will send the output to the command line and to a file called log.json 16 | 17 | created 30 Dec 2022 18 | updated 27 Jan 2025 19 | by Tom Igoe 20 | */ 21 | 22 | // #include // use this for MKR1000 board 23 | #include // use this for Nano 33 IoT or MKR1010 boards 24 | // #include // use this for Nano ESP32 board 25 | #include "arduino_secrets.h" 26 | 27 | // Initialize the Wifi client library 28 | WiFiClient client; 29 | 30 | // replace with your host computer's IP address 31 | const char server[] = "0.0.0.0"; 32 | const int portNum = 8080; 33 | // change this to a unique name for the device: 34 | String deviceName = "first"; 35 | // message sending interval, in ms: 36 | int interval = 5000; 37 | // last time a message was sent, in ms: 38 | long lastSend = 0; 39 | 40 | void setup() { 41 | //Initialize serial 42 | Serial.begin(9600); 43 | // if serial monitor's not open, wait 3 seconds: 44 | if (!Serial) delay(3000); 45 | 46 | // Connect to WPA/WPA2 network. 47 | WiFi.begin(SECRET_SSID, SECRET_PASS); 48 | 49 | // attempt to connect to Wifi network: 50 | while (WiFi.status() != WL_CONNECTED) { 51 | Serial.print("Attempting to connect to SSID: "); 52 | Serial.println(SECRET_SSID); 53 | // wait a second for connection: 54 | delay(1000); 55 | } 56 | Serial.print("Connected to to SSID: "); 57 | Serial.println(SECRET_SSID); 58 | Serial.print("IP: "); 59 | Serial.println(WiFi.localIP()); 60 | Serial.print("Signal Strength (dBm): "); 61 | Serial.println(WiFi.RSSI()); 62 | } 63 | 64 | void loop() { 65 | // if the client's not connected, connect: 66 | if (!client.connected()) { 67 | Serial.println("connecting"); 68 | Serial.println(server); 69 | Serial.println(portNum); 70 | client.connect(server, portNum); 71 | // skip the rest of the loop: 72 | return; 73 | } 74 | 75 | // once every interval, get a reading and send it: 76 | if (millis() - lastSend > interval) { 77 | // read sensor: 78 | int sensor = analogRead(A0); 79 | // format the message as JSON string: 80 | String message = "{\"device\": \"DEVICE\", \"sensor\": READING}"; 81 | // replace READING with the reading: 82 | message.replace("READING", String(sensor)); 83 | // and DEVICE with your device's name: 84 | message.replace("DEVICE", deviceName); 85 | // send the message: 86 | client.println(message); 87 | // update the timestamp: 88 | lastSend = millis(); 89 | } 90 | 91 | // check if there is incoming data available to be received 92 | int messageSize = client.available(); 93 | // if there's a string with length > 0: 94 | if (messageSize > 0) { 95 | Serial.println("Received a message:"); 96 | Serial.println(client.readString()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /DeviceDataDashboard/WiFiUDPClientLogger/WiFiUDPClientLogger.ino: -------------------------------------------------------------------------------- 1 | /* 2 | WiFi UDP Client 3 | UDP client for WiFiNINA and WiFi101 libraries. 4 | Reads a sensor once every five seconds, and sends 5 | a UDP message with the reading. 6 | 7 | You'll need to include an arduino_secrets.h file with the following info: 8 | #define SECRET_SSID "ssid" // your network name 9 | #define SECRET_PASS "password" // your network password 10 | 11 | Here's a test with netcat: 12 | char serverAddress[] = "x.x.x.x"; // replace with your computer's IP 13 | then on your computer, run netcat: 14 | $ nc -uklw 2 8080 | tee log.json 15 | This will send the output to the command line and to a file called log.json 16 | 17 | created 7 Jan 2025 18 | updated 27 Jan 2025 19 | by Tom Igoe 20 | */ 21 | 22 | // #include // use this for MKR1000 board 23 | #include // use this for Nano 33 IoT or MKR1010 boards 24 | // #include // use this for Nano ESP32 board 25 | #include "arduino_secrets.h" 26 | 27 | // Initialize the Wifi UDP library 28 | WiFiUDP Udp; 29 | char packetBuffer[256]; // buffer to hold incoming packet 30 | 31 | 32 | // replace with your host computer's IP address 33 | const char server[] = "0.0.0.0"; 34 | const int portNum = 8080; 35 | // change this to a unique name for the device: 36 | String deviceName = "myDevice"; 37 | // message sending interval, in ms: 38 | int interval = 5000; 39 | // last time a message was sent, in ms: 40 | long lastSend = 0; 41 | 42 | void setup() { 43 | //Initialize serial 44 | Serial.begin(9600); 45 | // if serial monitor's not open, wait 3 seconds: 46 | if (!Serial) delay(3000); 47 | 48 | // Connect to WPA/WPA2 network. 49 | WiFi.begin(SECRET_SSID, SECRET_PASS); 50 | 51 | // attempt to connect to Wifi network: 52 | while (WiFi.status() != WL_CONNECTED) { 53 | Serial.print("Attempting to connect to SSID: "); 54 | Serial.println(SECRET_SSID); 55 | // wait a second for connection: 56 | delay(1000); 57 | } 58 | Serial.print("Connected to to SSID: "); 59 | Serial.println(SECRET_SSID); 60 | Serial.print("IP: "); 61 | Serial.println(WiFi.localIP()); 62 | Serial.print("Signal Strength (dBm): "); 63 | Serial.println(WiFi.RSSI()); 64 | Udp.begin(portNum); 65 | } 66 | 67 | void loop() { 68 | // once every interval, get a reading and send it: 69 | if (millis() - lastSend > interval) { 70 | // read sensor: 71 | int sensor = analogRead(A0); 72 | // format the message as JSON string: 73 | String message = "{\"device\": \"DEVICE\", \"sensor\": READING}"; 74 | // replace READING with the reading: 75 | message.replace("READING", String(sensor)); 76 | // and DEVICE with your device's name: 77 | message.replace("DEVICE", deviceName); 78 | // send the message: 79 | Serial.print("sending to:"); 80 | Serial.println(server); 81 | Udp.beginPacket(server, portNum); 82 | Udp.println(message); 83 | Udp.endPacket(); 84 | // update the timestamp: 85 | lastSend = millis(); 86 | } 87 | 88 | // check if there is incoming data available to be received 89 | int packetSize = Udp.parsePacket(); 90 | // if so, read it: 91 | if (packetSize) { 92 | Serial.print("Received packet of size "); 93 | Serial.println(packetSize); 94 | Serial.print("From "); 95 | IPAddress remoteIp = Udp.remoteIP(); 96 | Serial.print(remoteIp); 97 | Serial.print(", port "); 98 | Serial.println(Udp.remotePort()); 99 | 100 | // read the packet into packetBuffer 101 | int len = Udp.read(packetBuffer, 255); 102 | if (len > 0) { 103 | packetBuffer[len] = 0; 104 | } 105 | Serial.println("Contents:"); 106 | Serial.println(packetBuffer); 107 | } 108 | } -------------------------------------------------------------------------------- /DeviceDataDashboard/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Readings 9 | 10 | 11 |

Sensor Readings

12 |
Waiting for the log.json file to load...
13 | 14 | -------------------------------------------------------------------------------- /DeviceDataDashboard/dashboard/log.json: -------------------------------------------------------------------------------- 1 | {"sensor": 367} 2 | {"sensor": 393} 3 | {"sensor": 398} -------------------------------------------------------------------------------- /DeviceDataDashboard/dashboard/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | Data fetch script. Uses Fetch to get a text file 3 | every five seconds, and fill its contents into 4 | a div on the HTML page. 5 | 6 | Based on my fetch example (https://tigoe.github.io/html-for-conndev/fetch/). 7 | 8 | created 30 Dec 2022 9 | by Tom Igoe 10 | */ 11 | 12 | // this function is called once on page load (see below): 13 | function setup() { 14 | // set an interval to run fetchText() every 5 seconds: 15 | setInterval(fetchText, 5000); 16 | } 17 | 18 | // make an HTTP call to get a text file: 19 | function fetchText() { 20 | // parameters for the HTTP/S call 21 | let params = { 22 | mode: 'cors', // if you need to turn off CORS, use no-cors 23 | headers: { // any HTTP headers you want can go here 24 | 'accept': 'application/text' 25 | } 26 | } 27 | // make the HTTP/S call: 28 | fetch('log.json', params) 29 | .then(response => response.text()) // convert response to text 30 | .then(data => getResponse(data)) // get the body of the response 31 | .catch(error => getResponse(error));// if there is an error 32 | } 33 | 34 | // function to call when you've got something to display: 35 | function getResponse(data) { 36 | document.getElementById('result').innerHTML = data; 37 | } 38 | 39 | // This is a listener for the page to load. 40 | // This is the command that actually starts the script: 41 | window.addEventListener('DOMContentLoaded', setup); 42 | -------------------------------------------------------------------------------- /DeviceDataDashboard/readme.md: -------------------------------------------------------------------------------- 1 | # Connected Device Data Dashboard 2 | 3 | The examples in [this directory](https://github.com/tigoe/html-for-conndev/blob/main/DeviceDataDashboard/) show how to connect a WiFi-connected microcontroller to a web page using a few command line tools. To make the most of this tutorial, you should understand: 4 | * DOM structure as shown in [this template example](../template/) 5 | * Arduino WiFiNINA (for Nano 33, Uno WiFi, MKR1010) or WiFi101 (for MKR1000) libraries. See the WiFi_Startup and WiFi_status videos in [this showcase](https://vimeo.com/showcase/6916443) for an intro to the WiFiNINA library. 6 | 7 | You'll need: 8 | * WiFi connected Arduino. Nano 33, Uno WiFi, MKR1010, or MKR1000 will work. 9 | * A computer with a POSIX-compatible command line interface. MacOS, Linux, or Window 10 or later running [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install) will work. 10 | 11 | ## TCP Socket or UDP Packet Connections 12 | 13 | The Arduino WiFi libraries are capable of making transport-layer connections to remote hosts via TCP and UDP. For TCP, the [WiFiTCPClientLogger example](https://github.com/tigoe/html-for-conndev/blob/main/DeviceDataDashboard/WiFiTCPClientLogger/WiFiTCPClientLogger.ino) shows how to make a TCP connection to a remote host on port 8080, and to send data as a JSON string once every five seconds. For UDP packets, the [WiFiUDPClientLogger example](https://github.com/tigoe/html-for-conndev/blob/main/DeviceDataDashboard/WiFiUDPClientLogger/WiFiUDPClientLogger.ino) does the same, but using UDP instead of TCP. You can use either one as long as you use the corresponding netcat command below. 14 | 15 | 16 | ### Connecting to Netcat 17 | 18 | To test this on your own computer, change the IP address in the example to your computer's IP address and then upload it to your board. Then open a command line interface (Terminal on MacOS or Linux, WSL on Windows) and run the netcat command. 19 | Netcat is a command that lets you read from or write to network connections, similar to how the [cat](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cat.html) command lets you read from or write to filestreams on your computer. 20 | 21 | Windows users, you may need to install netcat in the Linux shell on your Windows machine. 22 | 23 | To check that netcat is installed on any system, type: 24 | 25 | ```` 26 | $ which nc 27 | ```` 28 | 29 | _By convention, whenever you see `$` at the beginning of the line in these examples, it represents the command prompt. You don't need to type it._ 30 | 31 | You'll get a reply telling you what dierectory it's installed in, like this: 32 | 33 | ```` 34 | /bin/nc 35 | ```` 36 | 37 | If it's not installed, you can install it like so: 38 | 39 | ```` 40 | $ sudo apt install netcat 41 | ```` 42 | 43 | Once you know netcat is installed, you can run it to listen for incoming TCP connections on port 8080 like so: 44 | 45 | ```` 46 | $ nc -klw 2 8080 47 | ```` 48 | The -l flag will start netcat listening for incoming TCP connections on port 8080. The -k flag tells it to listen for multiple connections, and the -w flag gives each connection a 2 second idle timeout. 49 | 50 | If you're using UDP packets instead of TCP sockets, you call netCat like so: 51 | ```` 52 | $ nc -uklw 2 8080 53 | ```` 54 | The -u flag tells it to listen for UDP packets instead of TCP socket connections. 55 | 56 | After a few seconds, you should see the readings from the Arduino sketch coming in. They'll look like this: 57 | 58 | ```` 59 | {"sensor": 397} 60 | {"sensor": 400} 61 | {"sensor": 396} 62 | ```` 63 | Type `control-C` to stop netcat. This takes over your command line, so you can run it in the background like so: 64 | 65 | ```` 66 | $ nc -l 8080 & 67 | ```` 68 | 69 | This will cause the shell to start your process, print out the process ID (PID) number, and return to the command prompt. It will keep printing the output as well. If you want to kill the process now, type: 70 | 71 | ```` 72 | $ kill 32656 73 | ```` 74 | 75 | Replace 32656 with the process number of your process. 76 | 77 | You can get a list of all your currently running processes and their process numbers like this: 78 | 79 | ```` 80 | $ ps -a 81 | ```` 82 | 83 | It doesn't matter whether you're using TCP socket connections or UDP packets for the next section, because once the data is in the log file, the HTTP server and client don't care how the data got there. 84 | 85 | ### Logging To A File 86 | 87 | It would be useful to save the incoming sensor readings to a file. 88 | 89 | You can redirect the output of any process using the redirect operators `>` and `>>`. If you want to overwrite the file, use `>`. If you want to append to the file, use `>>`. So to run netcat and redirect its output to a file, you can do this: 90 | 91 | ```` 92 | $ nc -l 8080 >> log.json & 93 | ```` 94 | 95 | If you open the file with a text editor, you'll see the latest readings. It will look like [this example](dashboard/log.json). 96 | 97 | You can also fork the output from the program to both a file and to the command line using the `tee` command. Here's how: 98 | 99 | ```` 100 | $ nc -l 8080 | tee log.json 101 | ```` 102 | 103 | Of course, you can run it in the background with `&` like so: 104 | 105 | ```` 106 | $ nc -l 8080 | tee log.json & 107 | ```` 108 | 109 | ## Reading A File in JavaScript with Fetch 110 | 111 | JavaScript's `fetch()` command allows you to make HTTP calls from within a page, to add content to the page. If you've never used fetch before, [this example](fetch) may be helpful. MDN has a [page describing the fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as well. [This script](https://github.com/tigoe/html-for-conndev/blob/main/DeviceDataDashboard/dashboard/script.js) makes a fetch call every 5 seconds, retreiving the `log.json` file and displaying it in a div of the HTML page in which the script is embedded. 112 | 113 | To see this whole system in action: 114 | 115 | 1. Start an Arduino running with theWiFiTCPClientLogger script. 116 | 2. Download the index.html and script.js files into the same directory. 117 | 3. On the command line change directories (`cd`) to the directory. 118 | 4. Run netcat and python's http.server script like so (python 2, for older systems): 119 | 120 | ```` 121 | $ nc -l 8080 >> log.json & python -m SimpleHTTPServer 122 | ```` 123 | 124 | On MacOS Monterey or any other operating system running Python 3, you can use this instead: 125 | 126 | ```` 127 | $ nc -l 8080 >> log.json & python3 -m http.server 128 | ```` 129 | 130 | 5. open the index page in a browser by going to `http://localhost:8000`. 131 | 132 | Now you've got a rudimentary HTML dashboard for your data. Once you've fetched the data file, you can customize the display of it in HTML and JS as much as you wish. You can see the page in action at [this link](dashboard). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 tigoe 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 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /_includes/nav.html: -------------------------------------------------------------------------------- 1 |

2 | HTML, CSS, JS & the DOM
3 | Positioning & Layout with CSS
4 | HTML input types
5 | Responsive layout example
6 | HTML Project template
7 | JavaScript Fetch example
8 | Fetch example with node.js server
9 | QRCode example
10 | QRCode with custom Query String
11 | Local Web Server
12 | HTML Dashboard for incoming device data
13 | Communications Protocols
14 | Hardware APIs
15 | Web Bluetooth
16 | WebSocket Example
17 | Serialport example (p5.serial)
18 | WebSerial example (no p5.serial)
19 | Geolocation example
20 | Sensor API example
21 |

-------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% seo %} 8 | 9 | 10 | 11 | 14 | 15 | 16 |
17 |
18 |

{{ site.title | default: site.github.repository_name }}

19 |

{{ site.description | default: site.github.project_tagline }} 20 |

21 | {% include nav.html %} 22 | 23 | 30 | 31 | {% if site.github.is_project_page %} 32 |

This project is maintained by {{ site.github.owner_name }}

33 | {% endif %} 34 | 35 | {% if site.github.is_user_page %} 36 | 39 | {% endif %} 40 |
41 | 42 |
43 | {{ content }} 44 |
45 | 46 | 49 |
50 | 51 | {% if site.google_analytics %} 52 | 60 | {% endif %} 61 | 62 | -------------------------------------------------------------------------------- /bluetooth-p5ble/ButtonLED/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Document 11 | 12 | 13 | 14 |

15 | This page shows how to connect to a Bluetooth LE peripheral via Web Bluetooth, using the p5.ble library. This is an adaptation of the p5.ble Getting Started script. This script works with this example for the ArduinoBLE library. 16 |

17 |

Click the connect button to search for BLE peripherals.

18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /bluetooth-p5ble/ButtonLED/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | p5.ble without p5.js 3 | 4 | Uses the p5.ble library to make Bluetooth connections to a plain JavaScript page. This is an adaptation of the p5.ble Getting Started script at https://itpnyu.github.io/p5ble-website/docs/getstarted.html 5 | 6 | This script works with the ArduinoBLE library example called ButtonLED: 7 | https://github.com/arduino-libraries/ArduinoBLE/tree/master/examples/Peripheral/ButtonLED 8 | 9 | Library documentaton: 10 | https://www.arduino.cc/en/Reference/ArduinoBLE 11 | 12 | created 20 Feb 2021 13 | updated 25 Feb 2021 14 | by Tom Igoe 15 | */ 16 | 17 | // advertised service UUID of the to search for: 18 | const serviceUuid = "19b10010-e8f2-537e-4f6c-d104768a1214"; 19 | // characteristic that you plan to read: 20 | let myCharacteristic; 21 | // instance of p5.ble: 22 | let myBLE; 23 | // DOM elements to interact with: 24 | let connectButton; 25 | let textDiv; 26 | 27 | // this function is called when the page is loaded. 28 | // event listener functions are initialized here: 29 | function setup(event) { 30 | console.log('page is loaded'); 31 | // Create a p5ble instance: 32 | myBLE = new p5ble(); 33 | // put the DOM elements into global variables: 34 | connectButton = document.getElementById('connect'); 35 | connectButton.addEventListener('click', connectToBle); 36 | textDiv = document.getElementById('messages'); 37 | } 38 | 39 | function connectToBle() { 40 | // Connect to a device by passing the service UUID 41 | myBLE.connect(serviceUuid, gotCharacteristics); 42 | } 43 | 44 | // A function that will be called once got characteristics 45 | function gotCharacteristics(error, characteristics) { 46 | // if there's an error, 47 | // notify the user and quit the function: 48 | if (error) { 49 | textDiv.innerHTML = 'error: ' + error; 50 | return; 51 | } 52 | // if no error, update the page: 53 | textDiv.innerHTML = 'characteristics: ' + characteristics; 54 | // read the second characteristic 55 | // (the buttonCharacteristic in the Arduino example): 56 | myCharacteristic = characteristics[1]; 57 | // Read the value of the first characteristic 58 | myBLE.read(myCharacteristic, gotValue); 59 | } 60 | 61 | // This function will be called once you have a value: 62 | function gotValue(error, value) { 63 | if (error) console.log('error: ', error); 64 | textDiv.innerHTML = 'value: ' + value; 65 | // After getting a value, call p5ble.read() again to get the value again 66 | myBLE.read(myCharacteristic, gotValue); 67 | } 68 | 69 | // This is a listener for the page to load. 70 | // This is the command that actually starts the script: 71 | window.addEventListener('DOMContentLoaded', setup); -------------------------------------------------------------------------------- /bluetooth-p5ble/ButtonLED/styles.css: -------------------------------------------------------------------------------- 1 | input { 2 | position: sticky; 3 | left: 120px; 4 | } 5 | 6 | body { 7 | max-width: 800px; 8 | } -------------------------------------------------------------------------------- /bluetooth-p5ble/ReadOneCharacteristic/SensorCharacteristic/SensorCharacteristic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Write to one characteristic 3 | 4 | This example creates a BLE peripheral with service that contains a 5 | characteristic to read an analog input. 6 | 7 | The circuit: 8 | - Arduino MKR WiFi 1010 or Arduino Uno WiFi Rev2 board, or Nano 33 IoT 9 | - Analog sensor connected to pin A0 10 | 11 | */ 12 | 13 | #include 14 | 15 | const int ledPin = LED_BUILTIN; // set ledPin to on-board LED 16 | 17 | // create service: 18 | BLEService sensorService("19B10010-E8F2-537E-4F6C-D104768A1214"); 19 | // create sensor characteristic and allow remote device to get notifications: 20 | BLEIntCharacteristic sensorCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); 21 | 22 | int sensorValue = 0; 23 | int sensorPin = A0; // select the input pin for the potentiometer 24 | 25 | void setup() { 26 | Serial.begin(9600); 27 | while (!Serial); 28 | 29 | pinMode(ledPin, OUTPUT); // use the LED as an output 30 | 31 | // begin initialization 32 | if (!BLE.begin()) { 33 | Serial.println("starting BLE failed!"); 34 | while (true); 35 | } 36 | 37 | // set the local name peripheral advertises 38 | BLE.setLocalName("SensorPeripheral"); 39 | // set the UUID for the service this peripheral advertises: 40 | BLE.setAdvertisedService(sensorService); 41 | 42 | // add the characteristic to the service 43 | sensorService.addCharacteristic(sensorCharacteristic); 44 | 45 | // add the service 46 | BLE.addService(sensorService); 47 | sensorCharacteristic.writeValue(0); 48 | 49 | // start advertising 50 | BLE.advertise(); 51 | Serial.println("Bluetooth device active, waiting for connections..."); 52 | } 53 | 54 | void loop() { 55 | // poll for BLE events 56 | BLE.poll(); 57 | 58 | if (millis() % 1000 < 2) { 59 | // read the value from the sensor: 60 | sensorValue = analogRead(sensorPin); 61 | Serial.println(sensorValue / 4); 62 | sensorCharacteristic.writeValue(sensorValue / 4); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /bluetooth-p5ble/ReadOneCharacteristic/SensorCharacteristicBetter/SensorCharacteristicBetter.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Write to one characteristic 3 | 4 | This example creates a BLE peripheral with service that contains a 5 | characteristic to read an analog input. 6 | 7 | The circuit: 8 | - Arduino MKR WiFi 1010 or Arduino Uno WiFi Rev2 board, or Nano 33 IoT 9 | - Analog sensor connected to pin A0 10 | 11 | */ 12 | 13 | #include 14 | const int ledPin = LED_BUILTIN; // set ledPin to on-board LED 15 | 16 | // create service: 17 | BLEService sensorService("19B10010-E8F2-537E-4F6C-D104768A1214"); 18 | // create sensor characteristic and allow remote device to get notifications: 19 | BLEIntCharacteristic sensorCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); 20 | 21 | int sensorValue = 0; 22 | int sensorPin = A0; // select the input pin for the potentiometer 23 | 24 | void setup() { 25 | Serial.begin(9600); 26 | if (!Serial) delay(3000); 27 | 28 | pinMode(ledPin, OUTPUT); // use the LED as an output 29 | 30 | // begin initialization 31 | if (!BLE.begin()) { 32 | Serial.println("starting BLE failed!"); 33 | while (true); 34 | } 35 | 36 | // set the local name peripheral advertises 37 | BLE.setLocalName("SensorPeripheral"); 38 | // set the UUID for the service this peripheral advertises: 39 | BLE.setAdvertisedService(sensorService); 40 | 41 | // add the characteristic to the service 42 | sensorService.addCharacteristic(sensorCharacteristic); 43 | 44 | // add the service 45 | BLE.addService(sensorService); 46 | sensorCharacteristic.writeValue(0); 47 | 48 | // start advertising 49 | BLE.advertise(); 50 | Serial.println("Bluetooth device active, waiting for connections..."); 51 | } 52 | 53 | void loop() { 54 | // poll for BLE events 55 | BLE.poll(); 56 | 57 | if (millis() % 1000 < 2) { 58 | // read the value from the sensor: 59 | sensorValue = analogRead(sensorPin); 60 | Serial.println(sensorValue / 4); 61 | sensorCharacteristic.writeValue(sensorValue / 4); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /bluetooth-p5ble/ReadOneCharacteristic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Document 11 | 12 | 13 | 14 |

15 | This page shows how to connect to a Bluetooth LE peripheral via Web Bluetooth, using the p5.ble library. This is an adaptation of the p5.ble Read One characteristic script. This script works with this example for the ArduinoBLE library. 16 |

17 |

Click the connect button to search for BLE peripherals.

18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /bluetooth-p5ble/ReadOneCharacteristic/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | p5.ble without p5.js - read one characteristic 3 | 4 | Uses the p5.ble library to make Bluetooth connections to a plain JavaScript page. This is an adaptation of the p5.ble Read One characteristic script at https://itpnyu.github.io/p5ble-website/docs/read-one-char-callback 5 | 6 | This script works with this ArduinoBLE example: 7 | https://github.com/tigoe/html-for-conndev/tree/main/bluetooth-p5ble/ReadOneCharacteristic/SensorCharacteristic/ 8 | Library documentaton: 9 | https://www.arduino.cc/en/Reference/ArduinoBLE 10 | 11 | created 20 Feb 2021 12 | updated 25 Feb 2021 13 | by Tom Igoe 14 | */ 15 | 16 | // advertised service UUID of the to search for: 17 | const serviceUuid = "19b10010-e8f2-537e-4f6c-d104768a1214"; 18 | // characteristic that you plan to read: 19 | let myCharacteristic; 20 | // instance of p5.ble: 21 | let myBLE; 22 | // DOM elements to interact with: 23 | let connectButton; 24 | let textDiv; 25 | 26 | // this function is called when the page is loaded. 27 | // event listener functions are initialized here: 28 | function setup(event) { 29 | console.log('page is loaded'); 30 | // Create a p5ble instance: 31 | myBLE = new p5ble(); 32 | // Create a 'Connect' button 33 | const connectButton = document.getElementById('connect'); 34 | connectButton.addEventListener('click', connectToBle); 35 | textDiv = document.getElementById('messages'); 36 | } 37 | 38 | function connectToBle() { 39 | // Connect to a device by passing the service UUID 40 | myBLE.connect(serviceUuid, gotCharacteristics); 41 | } 42 | 43 | 44 | 45 | // A function that will be called once got characteristics 46 | function gotCharacteristics(error, characteristics) { 47 | // if there's an error, 48 | // notify the user and quit the function: 49 | if (error) { 50 | console.log(error); 51 | textDiv.innerHTML = 'error: ' + error; 52 | return; 53 | } 54 | 55 | console.log('characteristics: ', characteristics); 56 | myCharacteristic = characteristics[0]; 57 | // Read the value of the first characteristic 58 | myBLE.read(myCharacteristic, gotValue); 59 | } 60 | 61 | // A function that will be called once got values 62 | function gotValue(error, value) { 63 | // if there's an error, 64 | // notify the user and quit the function: 65 | if (error) { 66 | textDiv.innerHTML = 'error: ' + error; 67 | return; 68 | } 69 | 70 | // if you have a valid value, use it to set the 71 | // document properties: 72 | if (value || value === 0) { 73 | // set document background color: 74 | let myColor = 'rgb('+ value + ',255,255)'; 75 | document.body.style.backgroundColor = myColor; 76 | // Write value on the canvas 77 | textDiv.innerHTML = value; 78 | } 79 | // After getting a value, call p5ble.read() again to get the value again 80 | myBLE.read(myCharacteristic, gotValue); 81 | // You can also pass in the dataType 82 | // Options: 'unit8', 'uint16', 'uint32', 'int8', 'int16', 'int32', 'float32', 'float64', 'string' 83 | // myBLE.read(myCharacteristic, 'string', gotValue); 84 | } 85 | 86 | // This is a listener for the page to load. 87 | // This is the command that actually starts the script: 88 | window.addEventListener('DOMContentLoaded', setup); -------------------------------------------------------------------------------- /bluetooth-p5ble/ReadOneCharacteristic/styles.css: -------------------------------------------------------------------------------- 1 | input { 2 | position: sticky; 3 | left: 120px; 4 | } 5 | 6 | body { 7 | background-color:rgb(0,255,255); 8 | max-width: 800px; 9 | } -------------------------------------------------------------------------------- /fetch-with-server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tigoe-get-post", 3 | "version": "0.0.2", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "tigoe-get-post", 9 | "version": "0.0.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "express": "^4.17.1" 13 | }, 14 | "engines": { 15 | "node": "12.x" 16 | } 17 | }, 18 | "node_modules/accepts": { 19 | "version": "1.3.8", 20 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 21 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 22 | "dependencies": { 23 | "mime-types": "~2.1.34", 24 | "negotiator": "0.6.3" 25 | }, 26 | "engines": { 27 | "node": ">= 0.6" 28 | } 29 | }, 30 | "node_modules/array-flatten": { 31 | "version": "1.1.1", 32 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 33 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 34 | }, 35 | "node_modules/body-parser": { 36 | "version": "1.20.1", 37 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 38 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 39 | "dependencies": { 40 | "bytes": "3.1.2", 41 | "content-type": "~1.0.4", 42 | "debug": "2.6.9", 43 | "depd": "2.0.0", 44 | "destroy": "1.2.0", 45 | "http-errors": "2.0.0", 46 | "iconv-lite": "0.4.24", 47 | "on-finished": "2.4.1", 48 | "qs": "6.11.0", 49 | "raw-body": "2.5.1", 50 | "type-is": "~1.6.18", 51 | "unpipe": "1.0.0" 52 | }, 53 | "engines": { 54 | "node": ">= 0.8", 55 | "npm": "1.2.8000 || >= 1.4.16" 56 | } 57 | }, 58 | "node_modules/bytes": { 59 | "version": "3.1.2", 60 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 61 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 62 | "engines": { 63 | "node": ">= 0.8" 64 | } 65 | }, 66 | "node_modules/call-bind": { 67 | "version": "1.0.2", 68 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 69 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 70 | "dependencies": { 71 | "function-bind": "^1.1.1", 72 | "get-intrinsic": "^1.0.2" 73 | }, 74 | "funding": { 75 | "url": "https://github.com/sponsors/ljharb" 76 | } 77 | }, 78 | "node_modules/content-disposition": { 79 | "version": "0.5.4", 80 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 81 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 82 | "dependencies": { 83 | "safe-buffer": "5.2.1" 84 | }, 85 | "engines": { 86 | "node": ">= 0.6" 87 | } 88 | }, 89 | "node_modules/content-type": { 90 | "version": "1.0.5", 91 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 92 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 93 | "engines": { 94 | "node": ">= 0.6" 95 | } 96 | }, 97 | "node_modules/cookie": { 98 | "version": "0.5.0", 99 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 100 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 101 | "engines": { 102 | "node": ">= 0.6" 103 | } 104 | }, 105 | "node_modules/cookie-signature": { 106 | "version": "1.0.6", 107 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 108 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 109 | }, 110 | "node_modules/debug": { 111 | "version": "2.6.9", 112 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 113 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 114 | "dependencies": { 115 | "ms": "2.0.0" 116 | } 117 | }, 118 | "node_modules/depd": { 119 | "version": "2.0.0", 120 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 121 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 122 | "engines": { 123 | "node": ">= 0.8" 124 | } 125 | }, 126 | "node_modules/destroy": { 127 | "version": "1.2.0", 128 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 129 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 130 | "engines": { 131 | "node": ">= 0.8", 132 | "npm": "1.2.8000 || >= 1.4.16" 133 | } 134 | }, 135 | "node_modules/ee-first": { 136 | "version": "1.1.1", 137 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 138 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 139 | }, 140 | "node_modules/encodeurl": { 141 | "version": "1.0.2", 142 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 143 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 144 | "engines": { 145 | "node": ">= 0.8" 146 | } 147 | }, 148 | "node_modules/escape-html": { 149 | "version": "1.0.3", 150 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 151 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 152 | }, 153 | "node_modules/etag": { 154 | "version": "1.8.1", 155 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 156 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 157 | "engines": { 158 | "node": ">= 0.6" 159 | } 160 | }, 161 | "node_modules/express": { 162 | "version": "4.18.2", 163 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 164 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 165 | "dependencies": { 166 | "accepts": "~1.3.8", 167 | "array-flatten": "1.1.1", 168 | "body-parser": "1.20.1", 169 | "content-disposition": "0.5.4", 170 | "content-type": "~1.0.4", 171 | "cookie": "0.5.0", 172 | "cookie-signature": "1.0.6", 173 | "debug": "2.6.9", 174 | "depd": "2.0.0", 175 | "encodeurl": "~1.0.2", 176 | "escape-html": "~1.0.3", 177 | "etag": "~1.8.1", 178 | "finalhandler": "1.2.0", 179 | "fresh": "0.5.2", 180 | "http-errors": "2.0.0", 181 | "merge-descriptors": "1.0.1", 182 | "methods": "~1.1.2", 183 | "on-finished": "2.4.1", 184 | "parseurl": "~1.3.3", 185 | "path-to-regexp": "0.1.7", 186 | "proxy-addr": "~2.0.7", 187 | "qs": "6.11.0", 188 | "range-parser": "~1.2.1", 189 | "safe-buffer": "5.2.1", 190 | "send": "0.18.0", 191 | "serve-static": "1.15.0", 192 | "setprototypeof": "1.2.0", 193 | "statuses": "2.0.1", 194 | "type-is": "~1.6.18", 195 | "utils-merge": "1.0.1", 196 | "vary": "~1.1.2" 197 | }, 198 | "engines": { 199 | "node": ">= 0.10.0" 200 | } 201 | }, 202 | "node_modules/finalhandler": { 203 | "version": "1.2.0", 204 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 205 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 206 | "dependencies": { 207 | "debug": "2.6.9", 208 | "encodeurl": "~1.0.2", 209 | "escape-html": "~1.0.3", 210 | "on-finished": "2.4.1", 211 | "parseurl": "~1.3.3", 212 | "statuses": "2.0.1", 213 | "unpipe": "~1.0.0" 214 | }, 215 | "engines": { 216 | "node": ">= 0.8" 217 | } 218 | }, 219 | "node_modules/forwarded": { 220 | "version": "0.2.0", 221 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 222 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 223 | "engines": { 224 | "node": ">= 0.6" 225 | } 226 | }, 227 | "node_modules/fresh": { 228 | "version": "0.5.2", 229 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 230 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 231 | "engines": { 232 | "node": ">= 0.6" 233 | } 234 | }, 235 | "node_modules/function-bind": { 236 | "version": "1.1.1", 237 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 238 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 239 | }, 240 | "node_modules/get-intrinsic": { 241 | "version": "1.2.0", 242 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 243 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 244 | "dependencies": { 245 | "function-bind": "^1.1.1", 246 | "has": "^1.0.3", 247 | "has-symbols": "^1.0.3" 248 | }, 249 | "funding": { 250 | "url": "https://github.com/sponsors/ljharb" 251 | } 252 | }, 253 | "node_modules/has": { 254 | "version": "1.0.3", 255 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 256 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 257 | "dependencies": { 258 | "function-bind": "^1.1.1" 259 | }, 260 | "engines": { 261 | "node": ">= 0.4.0" 262 | } 263 | }, 264 | "node_modules/has-symbols": { 265 | "version": "1.0.3", 266 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 267 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 268 | "engines": { 269 | "node": ">= 0.4" 270 | }, 271 | "funding": { 272 | "url": "https://github.com/sponsors/ljharb" 273 | } 274 | }, 275 | "node_modules/http-errors": { 276 | "version": "2.0.0", 277 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 278 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 279 | "dependencies": { 280 | "depd": "2.0.0", 281 | "inherits": "2.0.4", 282 | "setprototypeof": "1.2.0", 283 | "statuses": "2.0.1", 284 | "toidentifier": "1.0.1" 285 | }, 286 | "engines": { 287 | "node": ">= 0.8" 288 | } 289 | }, 290 | "node_modules/iconv-lite": { 291 | "version": "0.4.24", 292 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 293 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 294 | "dependencies": { 295 | "safer-buffer": ">= 2.1.2 < 3" 296 | }, 297 | "engines": { 298 | "node": ">=0.10.0" 299 | } 300 | }, 301 | "node_modules/inherits": { 302 | "version": "2.0.4", 303 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 304 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 305 | }, 306 | "node_modules/ipaddr.js": { 307 | "version": "1.9.1", 308 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 309 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 310 | "engines": { 311 | "node": ">= 0.10" 312 | } 313 | }, 314 | "node_modules/media-typer": { 315 | "version": "0.3.0", 316 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 317 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 318 | "engines": { 319 | "node": ">= 0.6" 320 | } 321 | }, 322 | "node_modules/merge-descriptors": { 323 | "version": "1.0.1", 324 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 325 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 326 | }, 327 | "node_modules/methods": { 328 | "version": "1.1.2", 329 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 330 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 331 | "engines": { 332 | "node": ">= 0.6" 333 | } 334 | }, 335 | "node_modules/mime": { 336 | "version": "1.6.0", 337 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 338 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 339 | "bin": { 340 | "mime": "cli.js" 341 | }, 342 | "engines": { 343 | "node": ">=4" 344 | } 345 | }, 346 | "node_modules/mime-db": { 347 | "version": "1.52.0", 348 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 349 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 350 | "engines": { 351 | "node": ">= 0.6" 352 | } 353 | }, 354 | "node_modules/mime-types": { 355 | "version": "2.1.35", 356 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 357 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 358 | "dependencies": { 359 | "mime-db": "1.52.0" 360 | }, 361 | "engines": { 362 | "node": ">= 0.6" 363 | } 364 | }, 365 | "node_modules/ms": { 366 | "version": "2.0.0", 367 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 368 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 369 | }, 370 | "node_modules/negotiator": { 371 | "version": "0.6.3", 372 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 373 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 374 | "engines": { 375 | "node": ">= 0.6" 376 | } 377 | }, 378 | "node_modules/object-inspect": { 379 | "version": "1.12.3", 380 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 381 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 382 | "funding": { 383 | "url": "https://github.com/sponsors/ljharb" 384 | } 385 | }, 386 | "node_modules/on-finished": { 387 | "version": "2.4.1", 388 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 389 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 390 | "dependencies": { 391 | "ee-first": "1.1.1" 392 | }, 393 | "engines": { 394 | "node": ">= 0.8" 395 | } 396 | }, 397 | "node_modules/parseurl": { 398 | "version": "1.3.3", 399 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 400 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 401 | "engines": { 402 | "node": ">= 0.8" 403 | } 404 | }, 405 | "node_modules/path-to-regexp": { 406 | "version": "0.1.7", 407 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 408 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 409 | }, 410 | "node_modules/proxy-addr": { 411 | "version": "2.0.7", 412 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 413 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 414 | "dependencies": { 415 | "forwarded": "0.2.0", 416 | "ipaddr.js": "1.9.1" 417 | }, 418 | "engines": { 419 | "node": ">= 0.10" 420 | } 421 | }, 422 | "node_modules/qs": { 423 | "version": "6.11.0", 424 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 425 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 426 | "dependencies": { 427 | "side-channel": "^1.0.4" 428 | }, 429 | "engines": { 430 | "node": ">=0.6" 431 | }, 432 | "funding": { 433 | "url": "https://github.com/sponsors/ljharb" 434 | } 435 | }, 436 | "node_modules/range-parser": { 437 | "version": "1.2.1", 438 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 439 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 440 | "engines": { 441 | "node": ">= 0.6" 442 | } 443 | }, 444 | "node_modules/raw-body": { 445 | "version": "2.5.1", 446 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 447 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 448 | "dependencies": { 449 | "bytes": "3.1.2", 450 | "http-errors": "2.0.0", 451 | "iconv-lite": "0.4.24", 452 | "unpipe": "1.0.0" 453 | }, 454 | "engines": { 455 | "node": ">= 0.8" 456 | } 457 | }, 458 | "node_modules/safe-buffer": { 459 | "version": "5.2.1", 460 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 461 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 462 | "funding": [ 463 | { 464 | "type": "github", 465 | "url": "https://github.com/sponsors/feross" 466 | }, 467 | { 468 | "type": "patreon", 469 | "url": "https://www.patreon.com/feross" 470 | }, 471 | { 472 | "type": "consulting", 473 | "url": "https://feross.org/support" 474 | } 475 | ] 476 | }, 477 | "node_modules/safer-buffer": { 478 | "version": "2.1.2", 479 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 480 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 481 | }, 482 | "node_modules/send": { 483 | "version": "0.18.0", 484 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 485 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 486 | "dependencies": { 487 | "debug": "2.6.9", 488 | "depd": "2.0.0", 489 | "destroy": "1.2.0", 490 | "encodeurl": "~1.0.2", 491 | "escape-html": "~1.0.3", 492 | "etag": "~1.8.1", 493 | "fresh": "0.5.2", 494 | "http-errors": "2.0.0", 495 | "mime": "1.6.0", 496 | "ms": "2.1.3", 497 | "on-finished": "2.4.1", 498 | "range-parser": "~1.2.1", 499 | "statuses": "2.0.1" 500 | }, 501 | "engines": { 502 | "node": ">= 0.8.0" 503 | } 504 | }, 505 | "node_modules/send/node_modules/ms": { 506 | "version": "2.1.3", 507 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 508 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 509 | }, 510 | "node_modules/serve-static": { 511 | "version": "1.15.0", 512 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 513 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 514 | "dependencies": { 515 | "encodeurl": "~1.0.2", 516 | "escape-html": "~1.0.3", 517 | "parseurl": "~1.3.3", 518 | "send": "0.18.0" 519 | }, 520 | "engines": { 521 | "node": ">= 0.8.0" 522 | } 523 | }, 524 | "node_modules/setprototypeof": { 525 | "version": "1.2.0", 526 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 527 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 528 | }, 529 | "node_modules/side-channel": { 530 | "version": "1.0.4", 531 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 532 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 533 | "dependencies": { 534 | "call-bind": "^1.0.0", 535 | "get-intrinsic": "^1.0.2", 536 | "object-inspect": "^1.9.0" 537 | }, 538 | "funding": { 539 | "url": "https://github.com/sponsors/ljharb" 540 | } 541 | }, 542 | "node_modules/statuses": { 543 | "version": "2.0.1", 544 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 545 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 546 | "engines": { 547 | "node": ">= 0.8" 548 | } 549 | }, 550 | "node_modules/toidentifier": { 551 | "version": "1.0.1", 552 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 553 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 554 | "engines": { 555 | "node": ">=0.6" 556 | } 557 | }, 558 | "node_modules/type-is": { 559 | "version": "1.6.18", 560 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 561 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 562 | "dependencies": { 563 | "media-typer": "0.3.0", 564 | "mime-types": "~2.1.24" 565 | }, 566 | "engines": { 567 | "node": ">= 0.6" 568 | } 569 | }, 570 | "node_modules/unpipe": { 571 | "version": "1.0.0", 572 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 573 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 574 | "engines": { 575 | "node": ">= 0.8" 576 | } 577 | }, 578 | "node_modules/utils-merge": { 579 | "version": "1.0.1", 580 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 581 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 582 | "engines": { 583 | "node": ">= 0.4.0" 584 | } 585 | }, 586 | "node_modules/vary": { 587 | "version": "1.1.2", 588 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 589 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 590 | "engines": { 591 | "node": ">= 0.8" 592 | } 593 | } 594 | }, 595 | "dependencies": { 596 | "accepts": { 597 | "version": "1.3.8", 598 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 599 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 600 | "requires": { 601 | "mime-types": "~2.1.34", 602 | "negotiator": "0.6.3" 603 | } 604 | }, 605 | "array-flatten": { 606 | "version": "1.1.1", 607 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 608 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 609 | }, 610 | "body-parser": { 611 | "version": "1.20.1", 612 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 613 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 614 | "requires": { 615 | "bytes": "3.1.2", 616 | "content-type": "~1.0.4", 617 | "debug": "2.6.9", 618 | "depd": "2.0.0", 619 | "destroy": "1.2.0", 620 | "http-errors": "2.0.0", 621 | "iconv-lite": "0.4.24", 622 | "on-finished": "2.4.1", 623 | "qs": "6.11.0", 624 | "raw-body": "2.5.1", 625 | "type-is": "~1.6.18", 626 | "unpipe": "1.0.0" 627 | } 628 | }, 629 | "bytes": { 630 | "version": "3.1.2", 631 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 632 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 633 | }, 634 | "call-bind": { 635 | "version": "1.0.2", 636 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 637 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 638 | "requires": { 639 | "function-bind": "^1.1.1", 640 | "get-intrinsic": "^1.0.2" 641 | } 642 | }, 643 | "content-disposition": { 644 | "version": "0.5.4", 645 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 646 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 647 | "requires": { 648 | "safe-buffer": "5.2.1" 649 | } 650 | }, 651 | "content-type": { 652 | "version": "1.0.5", 653 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 654 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" 655 | }, 656 | "cookie": { 657 | "version": "0.5.0", 658 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 659 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" 660 | }, 661 | "cookie-signature": { 662 | "version": "1.0.6", 663 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 664 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 665 | }, 666 | "debug": { 667 | "version": "2.6.9", 668 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 669 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 670 | "requires": { 671 | "ms": "2.0.0" 672 | } 673 | }, 674 | "depd": { 675 | "version": "2.0.0", 676 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 677 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 678 | }, 679 | "destroy": { 680 | "version": "1.2.0", 681 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 682 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 683 | }, 684 | "ee-first": { 685 | "version": "1.1.1", 686 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 687 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 688 | }, 689 | "encodeurl": { 690 | "version": "1.0.2", 691 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 692 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 693 | }, 694 | "escape-html": { 695 | "version": "1.0.3", 696 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 697 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 698 | }, 699 | "etag": { 700 | "version": "1.8.1", 701 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 702 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 703 | }, 704 | "express": { 705 | "version": "4.18.2", 706 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 707 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 708 | "requires": { 709 | "accepts": "~1.3.8", 710 | "array-flatten": "1.1.1", 711 | "body-parser": "1.20.1", 712 | "content-disposition": "0.5.4", 713 | "content-type": "~1.0.4", 714 | "cookie": "0.5.0", 715 | "cookie-signature": "1.0.6", 716 | "debug": "2.6.9", 717 | "depd": "2.0.0", 718 | "encodeurl": "~1.0.2", 719 | "escape-html": "~1.0.3", 720 | "etag": "~1.8.1", 721 | "finalhandler": "1.2.0", 722 | "fresh": "0.5.2", 723 | "http-errors": "2.0.0", 724 | "merge-descriptors": "1.0.1", 725 | "methods": "~1.1.2", 726 | "on-finished": "2.4.1", 727 | "parseurl": "~1.3.3", 728 | "path-to-regexp": "0.1.7", 729 | "proxy-addr": "~2.0.7", 730 | "qs": "6.11.0", 731 | "range-parser": "~1.2.1", 732 | "safe-buffer": "5.2.1", 733 | "send": "0.18.0", 734 | "serve-static": "1.15.0", 735 | "setprototypeof": "1.2.0", 736 | "statuses": "2.0.1", 737 | "type-is": "~1.6.18", 738 | "utils-merge": "1.0.1", 739 | "vary": "~1.1.2" 740 | } 741 | }, 742 | "finalhandler": { 743 | "version": "1.2.0", 744 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 745 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 746 | "requires": { 747 | "debug": "2.6.9", 748 | "encodeurl": "~1.0.2", 749 | "escape-html": "~1.0.3", 750 | "on-finished": "2.4.1", 751 | "parseurl": "~1.3.3", 752 | "statuses": "2.0.1", 753 | "unpipe": "~1.0.0" 754 | } 755 | }, 756 | "forwarded": { 757 | "version": "0.2.0", 758 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 759 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 760 | }, 761 | "fresh": { 762 | "version": "0.5.2", 763 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 764 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 765 | }, 766 | "function-bind": { 767 | "version": "1.1.1", 768 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 769 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 770 | }, 771 | "get-intrinsic": { 772 | "version": "1.2.0", 773 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 774 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 775 | "requires": { 776 | "function-bind": "^1.1.1", 777 | "has": "^1.0.3", 778 | "has-symbols": "^1.0.3" 779 | } 780 | }, 781 | "has": { 782 | "version": "1.0.3", 783 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 784 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 785 | "requires": { 786 | "function-bind": "^1.1.1" 787 | } 788 | }, 789 | "has-symbols": { 790 | "version": "1.0.3", 791 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 792 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 793 | }, 794 | "http-errors": { 795 | "version": "2.0.0", 796 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 797 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 798 | "requires": { 799 | "depd": "2.0.0", 800 | "inherits": "2.0.4", 801 | "setprototypeof": "1.2.0", 802 | "statuses": "2.0.1", 803 | "toidentifier": "1.0.1" 804 | } 805 | }, 806 | "iconv-lite": { 807 | "version": "0.4.24", 808 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 809 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 810 | "requires": { 811 | "safer-buffer": ">= 2.1.2 < 3" 812 | } 813 | }, 814 | "inherits": { 815 | "version": "2.0.4", 816 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 817 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 818 | }, 819 | "ipaddr.js": { 820 | "version": "1.9.1", 821 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 822 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 823 | }, 824 | "media-typer": { 825 | "version": "0.3.0", 826 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 827 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 828 | }, 829 | "merge-descriptors": { 830 | "version": "1.0.1", 831 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 832 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 833 | }, 834 | "methods": { 835 | "version": "1.1.2", 836 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 837 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 838 | }, 839 | "mime": { 840 | "version": "1.6.0", 841 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 842 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 843 | }, 844 | "mime-db": { 845 | "version": "1.52.0", 846 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 847 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 848 | }, 849 | "mime-types": { 850 | "version": "2.1.35", 851 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 852 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 853 | "requires": { 854 | "mime-db": "1.52.0" 855 | } 856 | }, 857 | "ms": { 858 | "version": "2.0.0", 859 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 860 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 861 | }, 862 | "negotiator": { 863 | "version": "0.6.3", 864 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 865 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 866 | }, 867 | "object-inspect": { 868 | "version": "1.12.3", 869 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 870 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" 871 | }, 872 | "on-finished": { 873 | "version": "2.4.1", 874 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 875 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 876 | "requires": { 877 | "ee-first": "1.1.1" 878 | } 879 | }, 880 | "parseurl": { 881 | "version": "1.3.3", 882 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 883 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 884 | }, 885 | "path-to-regexp": { 886 | "version": "0.1.7", 887 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 888 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 889 | }, 890 | "proxy-addr": { 891 | "version": "2.0.7", 892 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 893 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 894 | "requires": { 895 | "forwarded": "0.2.0", 896 | "ipaddr.js": "1.9.1" 897 | } 898 | }, 899 | "qs": { 900 | "version": "6.11.0", 901 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 902 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 903 | "requires": { 904 | "side-channel": "^1.0.4" 905 | } 906 | }, 907 | "range-parser": { 908 | "version": "1.2.1", 909 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 910 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 911 | }, 912 | "raw-body": { 913 | "version": "2.5.1", 914 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 915 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 916 | "requires": { 917 | "bytes": "3.1.2", 918 | "http-errors": "2.0.0", 919 | "iconv-lite": "0.4.24", 920 | "unpipe": "1.0.0" 921 | } 922 | }, 923 | "safe-buffer": { 924 | "version": "5.2.1", 925 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 926 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 927 | }, 928 | "safer-buffer": { 929 | "version": "2.1.2", 930 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 931 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 932 | }, 933 | "send": { 934 | "version": "0.18.0", 935 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 936 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 937 | "requires": { 938 | "debug": "2.6.9", 939 | "depd": "2.0.0", 940 | "destroy": "1.2.0", 941 | "encodeurl": "~1.0.2", 942 | "escape-html": "~1.0.3", 943 | "etag": "~1.8.1", 944 | "fresh": "0.5.2", 945 | "http-errors": "2.0.0", 946 | "mime": "1.6.0", 947 | "ms": "2.1.3", 948 | "on-finished": "2.4.1", 949 | "range-parser": "~1.2.1", 950 | "statuses": "2.0.1" 951 | }, 952 | "dependencies": { 953 | "ms": { 954 | "version": "2.1.3", 955 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 956 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 957 | } 958 | } 959 | }, 960 | "serve-static": { 961 | "version": "1.15.0", 962 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 963 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 964 | "requires": { 965 | "encodeurl": "~1.0.2", 966 | "escape-html": "~1.0.3", 967 | "parseurl": "~1.3.3", 968 | "send": "0.18.0" 969 | } 970 | }, 971 | "setprototypeof": { 972 | "version": "1.2.0", 973 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 974 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 975 | }, 976 | "side-channel": { 977 | "version": "1.0.4", 978 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 979 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 980 | "requires": { 981 | "call-bind": "^1.0.0", 982 | "get-intrinsic": "^1.0.2", 983 | "object-inspect": "^1.9.0" 984 | } 985 | }, 986 | "statuses": { 987 | "version": "2.0.1", 988 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 989 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 990 | }, 991 | "toidentifier": { 992 | "version": "1.0.1", 993 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 994 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 995 | }, 996 | "type-is": { 997 | "version": "1.6.18", 998 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 999 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1000 | "requires": { 1001 | "media-typer": "0.3.0", 1002 | "mime-types": "~2.1.24" 1003 | } 1004 | }, 1005 | "unpipe": { 1006 | "version": "1.0.0", 1007 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1008 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 1009 | }, 1010 | "utils-merge": { 1011 | "version": "1.0.1", 1012 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1013 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 1014 | }, 1015 | "vary": { 1016 | "version": "1.1.2", 1017 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1018 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 1019 | } 1020 | } 1021 | } 1022 | -------------------------------------------------------------------------------- /fetch-with-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tigoe-get-post", 3 | "version": "0.0.2", 4 | "description": "a node.js example showing how to handle GET and POST requests.", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.17.1" 11 | }, 12 | "engines": { 13 | "node": "12.x" 14 | }, 15 | "repository": { 16 | "url": "https://github.com/tigoe/html-for-conndev" 17 | }, 18 | "license": "MIT", 19 | "keywords": [ 20 | "node", 21 | "glitch", 22 | "express" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /fetch-with-server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Fetch Examples 8 | 9 | 10 |

This page demonstrates JavaScript's fetch() function. Fetch uses JavaScript's async/await interface. This version also has an associated node.js and express.js server.

11 | 12 |

It works by making an HTTP request in a Promise, 13 | then awaiting a response. A successful response returns a Response object. 14 | You usually convert the response to either text or JSON, and then use that result in your script. You might also need to catch an error if there is one, 15 | or you might need to set some parameters, like the headers of your HTTP request, or whether or not to permit CORS requests, and so forth. 16 | You can see examples of these in this page's script. 17 |

18 |

This page and server is also available at Glitch.com 19 |

20 |

For more info, see 21 | MDN's documentation on using fetch. 22 |

23 | 24 |
25 | Temperature (makes a POST request with fetch): 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /fetch-with-server/public/script.js: -------------------------------------------------------------------------------- 1 | 2 | function fetchJSON() { 3 | // make the HTTP/HTTPS call: 4 | fetch('/json') 5 | .then(response => response.json()) // convert response to JSON 6 | .then(data => getResponse(JSON.stringify(data))) // get the body of the response 7 | .catch(error => getResponse(error));// if there is an error 8 | } 9 | 10 | function fetchText() { 11 | // make the HTTP/S call: 12 | fetch('/text') 13 | .then(response => response.text()) // convert response to text 14 | .then(data => getResponse(data)) // get the body of the response 15 | .catch(error => getResponse(error));// if there is an error 16 | } 17 | 18 | function postJson(value) { 19 | // parameters for the HTTP/S call 20 | let postData = {'temperature': value}; 21 | let params = { 22 | method: 'POST', // HTTP method 23 | headers: { // any HTTP headers you want can go here 24 | 'Content-Type': 'application/JSON' 25 | }, 26 | body: JSON.stringify(postData) 27 | } 28 | // make the HTTP/S call: 29 | fetch('/data', params) 30 | .then(response => response.json()) // convert response to text 31 | .then(data => getResponse(JSON.stringify(data))) // get the body of the response 32 | .catch(error => getResponse(error));// if there is an error 33 | } 34 | 35 | // function to call when you've got something to display: 36 | function getResponse(data) { 37 | document.getElementById('result').innerHTML = data; 38 | } -------------------------------------------------------------------------------- /fetch-with-server/public/style.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 600px) { 2 | body { 3 | font-family: 'Helvetica', 'Arial', sans-serif; 4 | /* calculate the font size based on the view width: */ 5 | font-size: calc(18px + 6 * ((100vw) / 320)); 6 | /* for comparison, see what 24px looks like: */ 7 | /* font-size: 24px; */ 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fetch-with-server/server.js: -------------------------------------------------------------------------------- 1 | // server.js 2 | const express = require("express"); 3 | const server = express(); 4 | const bodyParser = require("body-parser"); 5 | 6 | // JSON data to serve as a response, and to modify when 7 | // you get a POST request: 8 | var myData = { 9 | date: new Date(), 10 | temperature: 17 11 | }; 12 | // serve all the static files in the public folder: 13 | server.use("/", express.static("public")); 14 | // use the body parser middleware: 15 | server.use(bodyParser.json()); 16 | 17 | // handler for GET /json request: 18 | function getJson(request, response) { 19 | response.json(myData); 20 | } 21 | 22 | // handler for GET /text request: 23 | function getText(request, response) { 24 | let textString = "The time is " + 25 | new Date().toLocaleString() + 26 | " and the temperature is " + 27 | myData.temperature; 28 | response.send(textString); 29 | } 30 | 31 | // handler for POST /data request: 32 | function postData(request, response) { 33 | console.log("got a post request"); 34 | console.log(request.body); 35 | // if there is a temperature value in the body of the request: 36 | if (request.body.temperature) { 37 | // update the temperature value in myData: 38 | myData.temperature = request.body.temperature; 39 | } 40 | // if there is a date value in the body of the request: 41 | if (request.body.date) { 42 | // update the date value in myData: 43 | myData.date = request.body.date; 44 | } 45 | // respond with updated myValue: 46 | response.json(myData); 47 | } 48 | // server routes: 49 | server.get("/json", getJson); 50 | server.get("/text", getText); 51 | server.post("/data", postData); 52 | 53 | // listen for requests: 54 | const listener = server.listen(8080, () => { 55 | console.log("Your app is listening on port " + listener.address().port); 56 | }); 57 | -------------------------------------------------------------------------------- /fetch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Fetch Examples 8 | 9 | 10 | 11 |

This page demonstrates JavaScript's fetch() function. Fetch uses JavaScript's async/await interface. It works by making an HTTP request in a Promise, then awaiting a response. A successful response returns a Response object. You usually convert the response to either text or JSON, and then use that result in your script. You might also need to catch an error if there is one, or you might need to set some parameters, like the headers of your HTTP request, or whether or not to permit CORS requests, and so forth. You can see examples of these in this page's script. 12 | 13 | For more info, see 14 | MDN's documentation on using fetch. 15 |

16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /fetch/script.js: -------------------------------------------------------------------------------- 1 | 2 | function fetchJSON() { 3 | // parameters for the HTTP/S call 4 | let params = { 5 | mode: 'cors', // if you need to turn off CORS, use no-cors 6 | headers: { // any HTTP headers you want can go here 7 | 'accept': 'application/json', 8 | 'connection': 'keep-alive' 9 | } 10 | } 11 | // make the HTTP/HTTPS call: 12 | fetch('https://dweet.io/get/dweets/for/my-thing-name') 13 | .then(response => response.json()) // convert response to JSON 14 | .then(data => getResponse(JSON.stringify(data))) // get the body of the response 15 | .catch(error => getResponse(error));// if there is an error 16 | } 17 | 18 | function fetchText() { 19 | // parameters for the HTTP/S call 20 | let params = { 21 | mode: 'cors', // if you need to turn off CORS, use no-cors 22 | headers: { // any HTTP headers you want can go here 23 | 'accept': 'application/text', 24 | 'connection': 'keep-alive' 25 | } 26 | } 27 | // make the HTTP/S call: 28 | fetch('https://httpbin.org/encoding/utf8', params) 29 | .then(response => response.text()) // convert response to text 30 | .then(data => getResponse(data)) // get the body of the response 31 | .catch(error => getResponse(error));// if there is an error 32 | } 33 | 34 | // function to call when you've got something to display: 35 | function getResponse(data) { 36 | document.getElementById('result').innerHTML = data; 37 | } -------------------------------------------------------------------------------- /geolocation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |

Geolocation

11 | For more on geolocation in a browser, see the Mozilla Developer Network notes on the Geolocation API. 12 | 13 | -------------------------------------------------------------------------------- /geolocation/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | Geolocation script. Gets your location, puts it in a header 3 | 4 | created 7 Jan 2021 5 | by Tom Igoe 6 | */ 7 | 8 | let header; 9 | 10 | // this function is called when the page is loaded. 11 | // element event listeners are added here: 12 | function setup(event) { 13 | header = document.getElementById('location'); 14 | if (navigator.geolocation) { 15 | header.innerHTML = 'Looking for you...'; 16 | navigator.geolocation.getCurrentPosition(showPosition, showError); 17 | } else { 18 | header.innerHTML = 'Your browser does not support geolocation.'; 19 | } 20 | } 21 | 22 | function showError(error) { 23 | header.innerHTML = error.message; 24 | } 25 | 26 | function showPosition(position) { 27 | let latitude = position.coords.latitude; 28 | let longitude = position.coords.longitude; 29 | header.innerHTML = "You are at: " + latitude + ", " + longitude; 30 | } 31 | 32 | // add a listener for the page to load: 33 | window.addEventListener('load', setup); -------------------------------------------------------------------------------- /input-types/img/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tigoe/html-for-conndev/70eeb7d8afe212f1c1bc1da76c3eee0b303dcf1b/input-types/img/button.png -------------------------------------------------------------------------------- /input-types/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Input Types 8 | 9 | 10 | 11 |

All the HTML5 Input Types

12 |

This page shows all the HTML5 input types. When you change any of them, its value will fill into a span to the 13 | right of the input.

14 |

The submit button will clear the form.

15 |

See the MDN page on input elements for more information,

16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 |
77 | 78 |
79 |
image: 80 | 81 |
82 |
83 | 84 |
85 |
86 | 87 | 88 | -------------------------------------------------------------------------------- /input-types/script.js: -------------------------------------------------------------------------------- 1 | function getValue(target) { 2 | // get the span associated with this element 3 | // and put the element's value in the span's innerHTML 4 | // unless it's a radio orcheckbox, in which case use the checked value: 5 | if (target.type == 'radio' || target.type == 'checkbox') { 6 | document.getElementById(target.type + "Val").innerHTML = target.checked; 7 | } else { 8 | document.getElementById(target.type + "Val").innerHTML = target.value; 9 | } 10 | } 11 | 12 | function clearValues() { 13 | // get all the span elements: 14 | let spans = document.getElementsByTagName("span"); 15 | // iterate over the spans, clear their HTML: 16 | for (let s of spans) { 17 | s.innerHTML = ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /input-types/styles.css: -------------------------------------------------------------------------------- 1 | div { 2 | height: 32px; 3 | } 4 | /* image input needs its own height because of the image */ 5 | #imageInput { 6 | height: 48px; 7 | } 8 | input { 9 | position: sticky; 10 | left: 120px; 11 | } 12 | 13 | span { 14 | position: sticky; 15 | left: 400px; 16 | } -------------------------------------------------------------------------------- /purifier.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 20 |
21 | Power on
22 | Fan speedmedium
23 | Air quality: 80%
24 | Last filter Change: Jan 20, 2019 25 |
26 |
27 |
28 | Turn on at:
29 | Turn off at:
30 |
31 | 32 | -------------------------------------------------------------------------------- /qr-code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QR Code Generator 5 | 6 | 7 | 8 | 9 |

QR Code Example

10 |

This page will display a QR code of whatever text you type into the box below. To see the code, scan it on your mobile device after it appears.

11 |

Enter some text here to generate a QR code:

12 | 13 |

14 |

15 |

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /qr-code/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | QR Code generator 3 | Draws a QR code using a text string. Uses 4 | https://github.com/kazuhikoarase/qrcode-generator 5 | as the QR Code generator library. It's hosted at this CDN: 6 | https://unpkg.com/qrcode-generator@1.4.4/qrcode.js 7 | created 7 Jan 2021 8 | by Tom Igoe 9 | */ 10 | 11 | function getQrCode() { 12 | // get the div element for the QR code image: 13 | let qrDiv = document.getElementById('qrCode'); 14 | // get th text of the text field: 15 | let qrText = document.getElementById('textField').value; 16 | // make the QR code: 17 | let qr = qrcode(0, 'L'); 18 | qr.addData(qrText); 19 | qr.make(); 20 | // create an image from it: 21 | let qrImg = qr.createImgTag(2, 8, "qr code of " + qrText); 22 | // add it to the div: 23 | qrDiv.innerHTML = qrImg; 24 | } -------------------------------------------------------------------------------- /queryString/SSD1306_QRCode_redirect/SSD1306_QRCode_redirect.ino: -------------------------------------------------------------------------------- 1 | /* 2 | QR Code generator for SSD1306 Display 3 | URL generator with query string. 4 | 5 | Displays a QR code on a SSD1306 128x64 pixel display 6 | Uses Adafruit EPD library: http://librarymanager/All#Adafruit_SSD1306 7 | and Richard Moore's qrcode library: http://librarymanager/All#qrcode 8 | Code is based on qrcode library example and Adafruit_SSD1306 example. 9 | Circuit: 10 | - 128x64 SSD1306 OLED display connected to I2C pins. 11 | 12 | Uses the WiFi macAddress as a unique ID to send as a query string. This 13 | can be used to generate a QR code URL which is unique to the microcontroller, 14 | using the query string, while still connecting to a common site. 15 | 16 | created 8 Jan 2021 17 | modified 23 Jan 2022 18 | by Tom Igoe 19 | */ 20 | 21 | #include "qrcode.h" 22 | #include 23 | #include 24 | #include 25 | #include "arduino_secrets.h" 26 | 27 | const int SCREEN_WIDTH = 128; // OLED display width, in pixels 28 | const int SCREEN_HEIGHT = 64; // OLED display height, in pixels 29 | const int OLED_RESET = 0; // Reset pin for display (0 or -1 if no reset pin) 30 | // colors for a monochrome display: 31 | const int foregroundColor = 0x01; // white 32 | const int backgroundColor = 0x00; // black 33 | String urlString = "https://tigoe.github.io/html-for-conndev/queryString/?mac="; 34 | 35 | // initialize the display library instance: 36 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 37 | 38 | // initialize WiFi connection: 39 | WiFiClient wifi; 40 | 41 | void setup() { 42 | // initialize serial: 43 | Serial.begin(9600); 44 | // wait 3 sec. for serial monitor to open: 45 | if (!Serial) delay(3000); 46 | // start the display: 47 | display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 48 | // fill with the background color: 49 | display.fillScreen(backgroundColor); 50 | // update the display: 51 | display.display(); 52 | 53 | // get your MAC address: 54 | byte mac[6]; 55 | WiFi.macAddress(mac); 56 | // add it to the urlString: 57 | for (int i = 5; i >= 0; i--) { 58 | if (mac[i] < 16) urlString += "0"; 59 | urlString += String(mac[i], HEX); 60 | } 61 | Serial.println(urlString); 62 | displayQrCode(urlString); 63 | } 64 | 65 | void loop() { 66 | 67 | } 68 | 69 | void displayQrCode(String message) { 70 | Serial.print("Message length: "); 71 | Serial.println(message.length()); 72 | 73 | // Create the QR code 74 | QRCode qrcode; 75 | // See table at https://github.com/ricmoo/QRCode 76 | // or https://www.qrcode.com/en/about/version.html for 77 | // calculation of data capacity of a QR code. Current 78 | // settings will support a string of about 100 bytes: 79 | int qrVersion = 5; 80 | // can be ECC_LOW, ECC_MEDIUM, ECC_QUARTILE and ECC_HIGH (0-3, respectively): 81 | int qrErrorLevel = ECC_LOW; 82 | 83 | // allocate QR code memory: 84 | byte qrcodeBytes[qrcode_getBufferSize(qrVersion)]; 85 | qrcode_initText(&qrcode, qrcodeBytes, qrVersion, qrErrorLevel, message.c_str()); 86 | 87 | // QR Code block characteristics will depend on the display: 88 | // QR code needs a "quiet zone" of background color around it, hence the offset: 89 | int offset = 2; 90 | int blockSize = (display.height() - (offset * 2)) / qrcode.size; 91 | // fill with the background color: 92 | display.fillScreen(backgroundColor); 93 | 94 | // read the bytes of the QR code and set the blocks light or dark, accordingly: 95 | // vertical loop: 96 | for (byte y = 0; y < qrcode.size; y++) { 97 | // horizontal loop: 98 | for (byte x = 0; x < qrcode.size; x++) { 99 | // caculate the block's X and Y positions: 100 | int blockX = (x * blockSize) + offset; 101 | int blockY = (y * blockSize) + offset; 102 | // read the block value from the QRcode: 103 | int blockValue = qrcode_getModule(&qrcode, x, y); 104 | // set the default block color: 105 | int blockColor = backgroundColor; 106 | // if the block value is 1, set color to foreground color instead: 107 | if (blockValue == 1) { 108 | blockColor = foregroundColor; 109 | } 110 | // display the block on the screen: 111 | display.fillRect(blockX, blockY, blockSize, blockSize, blockColor); 112 | } 113 | } 114 | // print the message and display it: 115 | Serial.println(message); 116 | display.display(); 117 | } 118 | -------------------------------------------------------------------------------- /queryString/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 |

Using the Query String to Customize a Page

13 |

Let's say you want to make a browser-based interface for a device. Here's a way to get load the interface in your browser from your device using a QR code.

14 | 15 |

Step 1: generate an HTML page and serve it from a server. It's easier to maintain this page if it's a static page that doesn't change for each device that wants to use it as an interface.

16 | 17 |

Step 2: generate the page's URL on the device's screen, so you can scan it with your phone. Here's an Arduino sketch for a SSD1306 OLED Screen that'll do just that.

18 | 19 |

Step 3: add any parameters that are specific to the device (like its IP address or other ID) to the query string of the URL before you generate the QR code.

20 | 21 |

Step 4: modify the HTML page using client-side JavaScript to that it can extract the query string and its parameters and adapt itself to the specific device that you want to interact with. Here's the source code of this page.

22 | 23 |

Here are the query parameters from your call for this page: 24 |

25 |

26 | 27 | -------------------------------------------------------------------------------- /queryString/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | For more on this, see https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams 4 | by Tom Igoe 5 | */ 6 | 7 | function setup(event) { 8 | // get the div into which you'll put the query string parameters: 9 | let paramsDiv = document.getElementById('queryParams'); 10 | // get the query string: 11 | const queryString = window.location.search; 12 | // make it into an object: 13 | const query = new URLSearchParams(queryString); 14 | // iterate over the entries, extract the key and value: 15 | for (var pair of query.entries()) { 16 | paramsDiv.innerHTML += pair[0] + ': ' + pair[1] + '
'; 17 | } 18 | // if there's a query for 'url', say so: 19 | if (query.get('url') != null) { 20 | paramsDiv.innerHTML += 'url parameter value is: ' + query.get('url'); 21 | } 22 | } 23 | 24 | window.addEventListener('DOMContentLoaded', setup); -------------------------------------------------------------------------------- /queryString/styles.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 600px) { 2 | body { 3 | font-family: 'Helvetica', 'Arial', sans-serif; 4 | /* calculate the font size based on the view width: */ 5 | font-size: calc(18px + 6 * ((100vw) / 320)); 6 | /* for comparison, see what 24px looks like: */ 7 | /* font-size: 24px; */ 8 | } 9 | } 10 | 11 | body { 12 | max-width: 600px; 13 | font-family: "Noto Sans", "Arial", sans-serif; 14 | font-size: 12pt; 15 | line-height: normal; 16 | width: calc((100vw - 40px); 17 | } 18 | 19 | input { 20 | position: sticky; 21 | left: 250px; 22 | } 23 | 24 | span { 25 | position: sticky; 26 | left: 250px; 27 | } 28 | 29 | section#controls { 30 | padding: 20px; 31 | } 32 | 33 | figcaption { 34 | font-style: italic; 35 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # HTML for Connected Devices 3 | Operating a digital device without a screen is a challenge. Screens are expensive components to add if you're designing a household device like a connected speaker or an air purifier, so many device designers use a connection between the device and your mobile device or personal computer to provide a screen interface. This has its own complications, most importantly: 4 | 5 | * How do the devices connect? 6 | * With multiple operating systems, how do you program the device with the screen? 7 | 8 | Most screen-based computers have a web browser on them, making HTML and web connections an easy way to approach this problem. What follows is an introduction to just enough web development to make screen-based interfaces for many connected devices, and a collection of techniques for quick development of those interfaces. 9 | 10 | ## HTML, CSS, JavaScript, and the DOM 11 | 12 | A web application is typically a combination of HTML to define the structural elements of the page (paragraphs, headings, input elements, etc.), Cascading Style Sheets (CSS) to define the styles and layout of the elements, and JavaScript to define their behavior. These three technologies (HTML, CSS, and JavaScript) comprise the **Document Object Model**, or **DOM** of a web application. The objects are the various HTML elements and CSS styles. JavaScript is the programming language used to manipulate the DOM and to communicate with other devices. 13 | 14 | You can build a user interface using HTML, CSS and JavaScript and communications protocols to communicate changes betwen that interface and the devices it's designed to control. 15 | 16 | There are thousands of tutorials to introduce web development, so there's no need to repeat that here. The [Mozilla Developer Network](https://developer.mozilla.org/) site is a good reference. This site is heavily indebted to (and heavily linked to pages on) MDN. Covered below is enough information to send and receive data from devices to a web page. 17 | 18 | A minimal browser-based interface for a connected device might include a few input elements to change the device's properties, and a few text fields to display those properties. For example, imagine a connected air purifier. It would need controls to turn the device on and off, to set the fan speed, and perhaps set a schedule for when it should turn and off next, and a few text fields to report the state of the purifier and perhaps the last time the filter was changed. If the purifier includes an air quality sensor, perhaps it will report the sensor's readings as well. The interface might look something like [this page](purifier.html). 19 | 20 | By using JavaScript to manipulate the elements of the page, you can make the various text elements, like the spans, responsive to the values of the inputs, like the button or the slider. You can send those values to a web server using the JavaScript `fetch()` command, or you can use other communications protocols to send the values to connected devices. 21 | 22 | ### Client-Side JavaScript Structure 23 | 24 | A typical client-side JavaScript is structured like this: 25 | 26 | * define functions to listen for DOM events 27 | * add a listener function for the page load event 28 | 29 | There's not typically a main loop, as you might be used to for C or Java; all the action happens on user-generated events. You can make some timed functions using `setInterval()` or `setTimeout()`, but these are typically less common. 30 | 31 | Here's a [plain Javascript template](template/script.js). It goes with this [HTML page](template/index.html). This [style sheet](template/styles.css) is used to set the positions of the elements. 32 | 33 | ### HTML Input Elements 34 | 35 | There are many input elements defined in the HTML5 specification, and you can use them to gather all sorts of structured data. You can both get and set the value of an input element in JavaScript. 36 | 37 | Here's a [page with all of the input elements](input-types/index.html). Changing any of them will put its value in a `` next to the input. 38 | 39 | In addition to these, there are a few other forms of input that do not use the `` tag, like`