|
| 1 | + |
| 2 | + |
| 3 | + |
1 | 4 | # MicroPython TMP117 Web Server Example
|
| 5 | +The mpy_tmp117_web_server demo application configures a MicroPython device or Raspberry Pi as a wireless access point that publishes TMP117 temperature data. The Microdot python web framework is used to set up a web server on your MicroPython or Python board. Static web elements are served to create a Web UI to display data and deliver commands to the device. |
| 6 | + |
| 7 | +While this simple demo is for the TMP117 and serves temperature data, use it as a starting point to develop your own web application for interfacing with any of our other [MicroPython supported Qwiic Devices](https://github.com/topics/sparkfun-python)! |
| 8 | + |
| 9 | +## Contents |
| 10 | + |
| 11 | +* [Hardware](#hardware) |
| 12 | +* [Installation](#installation) |
| 13 | +* [Running the Example](#running-the-example) |
| 14 | +* [Using the Webpage](#using-the-webpage) |
| 15 | +* [Code Explanation](#code-explanation) |
| 16 | +* [References and More](#references-and-more) |
| 17 | + |
| 18 | +## Hardware |
| 19 | +In order to run this demo you will need: |
| 20 | + |
| 21 | +* [Qwiic Cable](https://www.sparkfun.com/sparkfun-qwiic-cable-kit.html) |
| 22 | +* [TMP117 Temperature Sensor](https://www.sparkfun.com/sparkfun-high-precision-temperature-sensor-tmp117-qwiic.html) |
| 23 | +* A client computer, phone, tablet, etc. to view the web app. |
| 24 | +* EITHER: |
| 25 | + 1) MicroPython capable board with a WLAN interface and a [Qwiic Connector](https://www.sparkfun.com/qwiic) or broken-out I2C pins (we reccomend our [IoT RedBoard ESP32](https://www.sparkfun.com/sparkfun-iot-redboard-esp32-development-board.html) or [IoT RedBoard RP2350](https://www.sparkfun.com/sparkfun-iot-redboard-rp2350.html)). |
| 26 | + |
| 27 | + OR |
| 28 | + |
| 29 | + 2) A Raspberry Pi with the [Qwiic Shim](https://www.sparkfun.com/sparkfun-qwiic-shim-for-raspberry-pi.html) or a [Qwiic Cable Female Jumper](https://www.sparkfun.com/flexible-qwiic-cable-female-jumper-4-pin.html). Testing was done with a RaspberryPi 4 Model B Running Kernel v6.6, Debian GNU/Linux 12 (bookworm). |
| 30 | + |
| 31 | +Connect the TMP117 to your board with your chosen qwiic method and you're ready to go. |
| 32 | + |
| 33 | +## Installation |
| 34 | + |
| 35 | +### MicroPython |
| 36 | +If you are running the demo on a MicroPython board (and you did not purchase one of our boards with MicroPython pre-loaded on the board), first flash your board with MicroPython firmware. See the [most recent release of Sparkfun MicroPython](https://github.com/sparkfun/micropython/releases) and install the .uf2 (for RP2 boards) or .bin files (for ESP32 boards) corresponding to your board. |
2 | 37 |
|
3 |
| -## Overview |
| 38 | +Next, add the demo files to your board. You can do this manually, by copying the files from this directory (and from [qwiic_i2c_py](https://github.com/sparkfun/Qwiic_I2C_Py) and [qwiic_tmp117](https://github.com/sparkfun/Qwiic_TMP117_Py)) to your board with mpremote or with Thonny or another IDE. Alternatively, you can issue the below command to automatically install the files to the correct location on your board: |
4 | 39 |
|
5 |
| -## Hardware Hookup |
| 40 | +```mpremote mip install github:sparkfun/sparkfun-python``` |
6 | 41 |
|
7 |
| -## Install |
| 42 | +In either case, after installing, the file structure on your board should look like this: |
| 43 | +``` |
| 44 | +/ |
| 45 | + | |
| 46 | + +--- lib/ |
| 47 | + | |--- qwiic_i2c |
| 48 | + | |--- __init__.py |
| 49 | + | |--- micropython_i2c.py |
| 50 | + | `--- i2c_driver.py |
| 51 | + | |--- microdot |
| 52 | + | |--- __init__.py |
| 53 | + | |--- helpers.py |
| 54 | + | |--- microdot.py |
| 55 | + | `--- websocket.py |
| 56 | + | |--- wlan_ap |
| 57 | + | |--- __init__.py |
| 58 | + | |--- config_ap_micropython.py |
| 59 | + | `--- config_ap_linux.py |
| 60 | + | |--- qwiic_tmp117.py |
| 61 | + | |
| 62 | + +--- static/ |
| 63 | + | |--- index.css |
| 64 | + | |--- index.html |
| 65 | + | `--- logo.png |
| 66 | + | |
| 67 | + `--- tmp117_server_ap.py |
| 68 | +``` |
| 69 | + |
| 70 | +### Raspberry Pi |
| 71 | +On a Raspberry Pi running Linux, manually copy the files from this directory as well as those from [qwiic_i2c_py](https://github.com/sparkfun/Qwiic_I2C_Py) and [qwiic_tmp117_py](https://github.com/sparkfun/qwiic_tmp117_py) into the same directory. Alternatively, you can set up a virtual environment and install qwiic_i2c_py and qwiic_tmp117_py in the same venv path with pip3 as instructed in the READMEs for [qwiic_i2c_py](https://github.com/sparkfun/Qwiic_I2C_Py?tab=readme-ov-file#python) and [qwiic_tmp117_py](https://github.com/sparkfun/qwiic_tmp117_py/tree/master?tab=readme-ov-file#python). |
| 72 | + |
| 73 | +If you manually copy the files, your directory structure should look like this: |
| 74 | +``` |
| 75 | +/your-directory-name |
| 76 | + | |
| 77 | + +--- qwiic_i2c |
| 78 | + | |--- __init__.py |
| 79 | + | |--- micropython_i2c.py |
| 80 | + | `--- i2c_driver.py |
| 81 | + +--- microdot |
| 82 | + | |--- __init__.py |
| 83 | + | |--- helpers.py |
| 84 | + | |--- microdot.py |
| 85 | + | `--- websocket.py |
| 86 | + +--- wlan_ap |
| 87 | + | |--- __init__.py |
| 88 | + | |--- config_ap_micropython.py |
| 89 | + | `--- config_ap_linux.py |
| 90 | + +--- qwiic_tmp117.py |
| 91 | + | |
| 92 | + +--- static/ |
| 93 | + | |--- index.css |
| 94 | + | |--- index.html |
| 95 | + | `--- logo.png |
| 96 | + | |
| 97 | + `--- tmp117_server_ap.py |
| 98 | +``` |
| 99 | + |
| 100 | +If you used pip3 to install qwiic_i2c and qwiic_tmp117, you won't need them in the local directory as shown above (but you will need to run from the virtual environment where you installed them). |
8 | 101 |
|
9 | 102 | ## Running the Example
|
10 | 103 |
|
11 |
| -## In-Depth Code Explanation |
12 |
| -### Setting Up WLAN as an Access Point |
| 104 | +### MicroPython |
| 105 | + |
| 106 | +#### Option 1: Thonny/Other IDE |
| 107 | +If using Thonny, connect your board, open the ```tmp117_server_ap.py``` file, and click the green arrow button (run current script). |
| 108 | + |
| 109 | +#### Option 2: Command Line |
| 110 | +If using the command line: |
| 111 | +1) Execute ```mpremote``` to connect to your board |
| 112 | +2) From the REPL, execute the following command to run the example: |
| 113 | +```python |
| 114 | +>>> exec(open("tmp117_server_ap.py").read()) |
| 115 | +``` |
| 116 | + |
| 117 | +### Raspberry Pi |
| 118 | +Run the file with sudo privileges: |
| 119 | +```bash |
| 120 | +sudo python3 tmp117_server_ap.py |
| 121 | +``` |
| 122 | + |
| 123 | +## Using the Webpage |
| 124 | +### Connecting |
| 125 | +When you start the application, it should print the IP address and port that you should use to connect. For example: |
| 126 | + |
| 127 | +```Navigate to http://192.168.4.1:5000/ to view the TMP117 temperature readings``` |
| 128 | + |
| 129 | +The application will broadcast a wireless network called ```iot_redboard_tmp117``` with the password ```thermo_wave2``` (or whatever you have set as ```kApSsid``` or ```kApPass``` in the constants at the top of the tmp117_server_ap.py file). Connect to this wireless network with the WiFi manager on your client device. |
| 130 | + |
| 131 | +Next, copy and paste (or ctrl+click) the connection link from above into your web browser (or enter it manually in a mobile device). |
| 132 | + |
| 133 | +### Thermometer |
| 134 | +The Web Page should pop up and display a thermometer, some input boxes, and some indicator LEDs. The thermometer will show the temperature on a scale of 0 to 100 degrees F. Try breathing on your TMP117 to watch the temperature increase. |
| 135 | + |
| 136 | +### Limit LEDs and Boxes |
| 137 | +To change one of the limit values, enter a number in the corresponding "Set Limit" box and press enter. |
| 138 | +After changing one of the values, give several seconds for the server to receive your request, |
| 139 | +set the limit on the device, and respond with the value read back from the device to update the "Read Limit" box. |
| 140 | + |
| 141 | +If the temperature drops below the low limit or above the high limit, triggering an alert on the device, |
| 142 | +the corresponding alert LED will turn red. |
| 143 | +> [!NOTE] |
| 144 | +> This simple version of the webserver is only designed to handle a single connection at a time and should be restarted after a client device disconnects. |
| 145 | +
|
| 146 | +## Code Explanation |
| 147 | + |
| 148 | +### Setting Up WLAN as an Access Point (MicroPython) |
| 149 | +```python |
| 150 | +def config_wlan_as_ap(ssid = kDefaultSsid, password = kDefaultPassword): |
| 151 | + ap = network.WLAN(network.AP_IF) |
| 152 | + ap.active(True) |
| 153 | + ap.config(essid=ssid, password=password) |
| 154 | + |
| 155 | + while ap.active() == False: |
| 156 | + pass |
| 157 | + |
| 158 | + config = ap.ifconfig() |
| 159 | + |
| 160 | + return str(config[0]) |
| 161 | +``` |
| 162 | + |
| 163 | +```tmp117_server_ap.py``` uses the config_wlan_as_ap() function to configure the WLAN as an access point. Notice how simple it is to create the object using the ```network``` module. By passing ```AP_IF``` we choose to set up the WLAN interface |
| 164 | + |
| 165 | + |
| 166 | +See the ```wlan_ap/config_ap_linux.py``` file for the analog for Raspberry Pi. It makes use of [nmcli](https://networkmanager.dev/docs/api/latest/nmcli.html) to configure the Raspberry Pi WLAN0 as an access point. We pass our ssid and password to set up our network credentials. We return our IP so we know where to navigate to view our webpage. |
13 | 167 |
|
14 | 168 | ### Setting Up the TMP117
|
| 169 | +First we ensure the TMP117 is properly connected by calling ```tmp117Device.is_connected()```. Then we perform initialization by calling ```tmp117Device.begin()```. Finally, we set up alerts using ```tmp117Device.set_high_limit()```, ```tmp117Device.set_low_limit()```, and ```set_alert_function_mode()```. |
15 | 170 |
|
16 | 171 | ### The MicroDot Web Server Framework
|
| 172 | +[Microdot](https://github.com/miguelgrinberg/microdot) is a minimal Python and MicroPython web framework that allows us to quickly make web apps that can run on platforms with limited resources. It is based around the idea of "routes", such that we can call different asynchronous Python functions when we receive client http requests to different paths. See the [Microdot README](https://github.com/miguelgrinberg/microdot/blob/main/README.md) for more information. |
17 | 173 |
|
18 | 174 | ### Serving Static Web Elements
|
| 175 | +When a client first connects, it will be to the root path "/" of our web app. We create a route to service this path: |
| 176 | +```python |
| 177 | +@app.route('/') |
| 178 | +async def index(request): |
| 179 | + return send_file('static/index.html') |
| 180 | +``` |
| 181 | + |
| 182 | +Using the Microdot ```send_file()``` function, we choose display our hompage in ```index.html``` when the user first connects. |
19 | 183 |
|
20 |
| -### Client-Server Communication with Websocket |
| 184 | +We also want to be able to serve an arbitrary number of static web elements for example, the sparkfun logo as well as some styling using css. For this, we create a route to service the "static" path: |
| 185 | +```python |
| 186 | +@app.route('/static/<path:path>') |
| 187 | +async def static(request, path): |
| 188 | + if '..' in path: |
| 189 | + # directory traversal is not allowed |
| 190 | + return 'Not found', 404 |
| 191 | + return send_file('static/' + path) |
| 192 | +``` |
| 193 | +This creates a mapping from client requests to all ```static/``` paths and the files on our server in the "static" folder. |
| 194 | + |
| 195 | +### Client-Server Communication with WebSocket |
| 196 | +Microdot also allows for easy interfacing with WebSockets. In our ```index.html``` client code, we create a WebSocket at the ```'/temperature``` path. |
| 197 | +In our server code, we create a route for this path and specify that it will be a WebSocket using the ```@with_websocket``` decorator. |
| 198 | +```python |
| 199 | +@app.route('/temperature') |
| 200 | +@with_websocket |
| 201 | +async def handle_limits(request, ws): |
| 202 | +``` |
| 203 | + |
| 204 | +Now we will have access to the `ws` WebSocket access and can send and receive messages between the server and client using it's `send()` and `receive()` methods. |
21 | 205 |
|
22 | 206 | ### Reading and Publishing Temperature
|
| 207 | +We can read from the TMP117 using the functions defined in the qwiic_tmp117_py library. Often when conveying data over a WebSocket, the JSON format is used because it keeps our messages organized, and there is library suppport for JSON in most programming langauges. So we store our data in a dictionary and use the ```json.dumps()``` and ```tempSocket.send()``` functions to write it over the WebSocket as a JSON string where it can be caught by the client. |
| 208 | + |
| 209 | +```python |
| 210 | +async def send_temperature(tempSocket): |
| 211 | + while True: |
| 212 | + if myTMP117.data_ready(): |
| 213 | + |
| 214 | + data = {"tempF": 0, "tempC": 0, "limitH": 75, "limitL": 65, "alertH": False, "alertL": False} |
| 215 | + data['tempC'] = myTMP117.read_temp_c() |
| 216 | + data['tempF'] = myTMP117.read_temp_f() |
| 217 | + |
| 218 | + if kDoAlerts: |
| 219 | + await asyncio.sleep(1.5) |
| 220 | + alertFlags = myTMP117.get_high_low_alert() |
| 221 | + data['alertL'] = bool(alertFlags[myTMP117.kLowAlertIdx]) |
| 222 | + data['alertH'] = bool(alertFlags[myTMP117.kHighAlertIdx]) |
| 223 | + data['limitL'] = c_to_f(myTMP117.get_low_limit()) |
| 224 | + data['limitH'] = c_to_f(myTMP117.get_high_limit()) |
| 225 | + |
| 226 | + data = json.dumps(data) |
| 227 | + await tempSocket.send(data) |
| 228 | + await asyncio.sleep(0.5) |
| 229 | +``` |
| 230 | + |
| 231 | +This asynchronous task is created by the handle_limits function when a client first connects and creates the websocket. |
| 232 | + |
| 233 | +```python |
| 234 | +asyncio.create_task(send_temperature(ws)) |
| 235 | +``` |
| 236 | + |
| 237 | + |
| 238 | + |
| 239 | +## References and More |
| 240 | +Special thanks to the creators of the MIT-licensed elements below: |
| 241 | +* [Miguel Grinberg and Microdot](https://github.com/miguelgrinberg/microdot) for the Microdot Web Framework. |
| 242 | +* [Arkellys](https://codepen.io/Arkellys/details/rgpNBK) for the Thermometer HTML/CSS element. |
| 243 | +* [Johnny Berkmans](https://codepen.io/berkmansjohnny/details/LzXbPV) for the Indicator LED HTML/CSS elements. |
23 | 244 |
|
24 |
| -## References and More |
| 245 | +Find a bug or want a feature? [Let us know here](https://github.com/sparkfun/sparkfun-python/issues). Have fun and happy hacking! |
0 commit comments