├── README.md ├── example └── micropython-oled-chatgpt.py ├── hero.png └── lib └── ssd1306.py /README.md: -------------------------------------------------------------------------------- 1 | # upython-chat-gpt 2 | 3 | ChatGPT for MicroPython. 4 | 5 | ![ChatGPT on a GIGA R1 WiFi](hero.png) 6 | 7 | ## Overview 8 | 9 | This repository contains an example that allows you to connect to ChatGPT using MicroPython. It makes a POST request to the OpenAI API with a question and receives a response that is then printed on an OLED display. 10 | 11 | This example has been tested to work with the [Giga R1 WiFi](https://store.arduino.cc/products/giga-r1-wifi) board and a generic ESP32 board and should be compatible with other boards supporting MicroPython. 12 | 13 | To connect to OpenAI, you will need an account with a payment plan as this is not a free service. However, you can play around with this for days without spending more than a dollar. 14 | 15 | ## Hardware Requirements 16 | - [GIGA R1 WiFi](https://store.arduino.cc/products/giga-r1-wifi) 17 | - [OLED SSD1315 (I2C)](https://wiki.seeedstudio.com/Grove-OLED-Display-0.96-SSD1315/) display.* 18 | 19 | \*Other similar OLED displays may work but have not yet been tested with this setup. 20 | 21 | ## Hardware Setup 22 | 23 | Wire the display to the GIGA R1 WiFi using the following chart: 24 | 25 | | GIGA R1 WiFi | OLED SSD1315 | 26 | | ------------ | ------------ | 27 | | SDA | SDA | 28 | | SCL | SCL | 29 | | GND | GND | 30 | | 5V | VCC | 31 | 32 | ## Software Setup 33 | 34 | 1. Install MicroPython on your board. To install on the GIGA R1 WiFi, follow [this guide](https://docs.arduino.cc/tutorials/giga-r1-wifi/giga-micropython). 35 | 2. Install a MicroPython-compatible editor (like [Thonny](https://thonny.org/) or [Arduino Lab for MP](https://labs.arduino.cc/en/labs/micropython)) 36 | 3. Install the SSD1306 OLED driver on your board, either by installing directly via Thonny's built-in package manager or by copying over the `ssd1306.py` file in this repository to the `lib` folder of your board. 37 | 4. Create an account with [OpenAI](https://openai.com/). Once created, you will need to register a credit card or another payment method. You should then be able to create an API key. 38 | 5. In the example script, add your API key in the `api_key` field. 39 | 6. In the example script, you will also need to add your WiFi network SSID + PASS. 40 | 41 | You should now have met all the requirements to use the example script provided in this repository. 42 | 43 | ## Usage 44 | 45 | When you have set it up properly, anytime you launch the script, the following will happen: 46 | - The content of `open_ai_question` is sent in a POST request to the OpenAI API, which will return a response. This uses the `urequests` & `ujson` modules. 47 | - The response is printed on the OLED display, letter by letter for extra dramatic effect.\* 48 | 49 | >\*I couldn't find a text wrap function in the `ssd1306.py` module so I made a quick and dirty improvisation (the `print_oled()` function). -------------------------------------------------------------------------------- /example/micropython-oled-chatgpt.py: -------------------------------------------------------------------------------- 1 | import network, socket 2 | from machine import Pin, SoftI2C 3 | import ssd1306 4 | import urequests as requests 5 | import ujson 6 | 7 | # WiFi network 8 | SSID='' # Network SSID 9 | KEY='' # Network key 10 | 11 | # GIGA R1's I2C pins 12 | SDA_PIN = 'PB11' 13 | SCL_PIN = 'PH4' 14 | 15 | i2c = SoftI2C(sda=Pin(SDA_PIN), scl=Pin(SCL_PIN)) 16 | 17 | display = ssd1306.SSD1306_I2C(128, 64, i2c) 18 | 19 | # Init wlan module and connect to network 20 | print("Trying to connect. Note this may take a while...") 21 | 22 | wlan = network.WLAN(network.STA_IF) 23 | wlan.active(True) 24 | wlan.connect(SSID, KEY) 25 | 26 | # POST request 27 | # Change the "api_key" to your own 28 | # Change the "open_ai_question" to what you want to ask 29 | 30 | url = "https://api.openai.com/v1/chat/completions" 31 | open_ai_question = "Who are you?" 32 | max_words = "Max 80 characters" 33 | api_key = "YOUR_API_KEY" 34 | 35 | payload = ujson.dumps({ 36 | "model": "gpt-3.5-turbo", 37 | "messages": [ 38 | { 39 | "role": "user", 40 | "content": open_ai_question + max_words 41 | }, 42 | ], 43 | "temperature": 1, 44 | "top_p": 1, 45 | "n": 1, 46 | "stream": False, 47 | "max_tokens": 100, 48 | "presence_penalty": 0, 49 | "frequency_penalty": 0 50 | }) 51 | 52 | headers = { 53 | 'Content-Type': 'application/json', 54 | 'Accept': 'application/json', 55 | 'Authorization': 'Bearer ' + api_key 56 | } 57 | 58 | # Custom function that prints letter by letter 59 | # to the OLED SSD1306 display. 60 | def print_oled(msg): 61 | display.fill(0) 62 | x=0 63 | y=0 64 | z=0 65 | row = 0 66 | maxlength = (len(msg)) 67 | print(maxlength) 68 | 69 | for line in range(maxlength/20): 70 | for row in range(20): 71 | if (row+z == maxlength): 72 | break 73 | display.text(msg[row+z], x, y) 74 | x = x+6 75 | display.show() 76 | 77 | z = z+20 78 | y = y+11 79 | x = 0 80 | x=0 81 | y=0 82 | z=0 83 | row=0 84 | 85 | # Print the question (msg) 86 | print(open_ai_question) 87 | print_oled(open_ai_question) 88 | 89 | # Post Data 90 | response = requests.post(url, headers=headers, data=payload) 91 | response_data = response.json() 92 | 93 | # Access JSON object 94 | open_ai_message = response_data["choices"][0]["message"]["content"] 95 | 96 | # Close the connection 97 | response.close() 98 | 99 | # Print the response (open_ai_message) 100 | print(open_ai_message) 101 | print_oled(open_ai_message) 102 | 103 | 104 | -------------------------------------------------------------------------------- /hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlsoderby/upython-chat-gpt/816bb3f253427ca1c3f63234d1ad2e43365b42de/hero.png -------------------------------------------------------------------------------- /lib/ssd1306.py: -------------------------------------------------------------------------------- 1 | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces 2 | 3 | from micropython import const 4 | import framebuf 5 | 6 | 7 | # register definitions 8 | SET_CONTRAST = const(0x81) 9 | SET_ENTIRE_ON = const(0xA4) 10 | SET_NORM_INV = const(0xA6) 11 | SET_DISP = const(0xAE) 12 | SET_MEM_ADDR = const(0x20) 13 | SET_COL_ADDR = const(0x21) 14 | SET_PAGE_ADDR = const(0x22) 15 | SET_DISP_START_LINE = const(0x40) 16 | SET_SEG_REMAP = const(0xA0) 17 | SET_MUX_RATIO = const(0xA8) 18 | SET_COM_OUT_DIR = const(0xC0) 19 | SET_DISP_OFFSET = const(0xD3) 20 | SET_COM_PIN_CFG = const(0xDA) 21 | SET_DISP_CLK_DIV = const(0xD5) 22 | SET_PRECHARGE = const(0xD9) 23 | SET_VCOM_DESEL = const(0xDB) 24 | SET_CHARGE_PUMP = const(0x8D) 25 | 26 | # Subclassing FrameBuffer provides support for graphics primitives 27 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 28 | class SSD1306(framebuf.FrameBuffer): 29 | def __init__(self, width, height, external_vcc): 30 | self.width = width 31 | self.height = height 32 | self.external_vcc = external_vcc 33 | self.pages = self.height // 8 34 | self.buffer = bytearray(self.pages * self.width) 35 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 36 | self.init_display() 37 | 38 | def init_display(self): 39 | for cmd in ( 40 | SET_DISP | 0x00, # off 41 | # address setting 42 | SET_MEM_ADDR, 43 | 0x00, # horizontal 44 | # resolution and layout 45 | SET_DISP_START_LINE | 0x00, 46 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 47 | SET_MUX_RATIO, 48 | self.height - 1, 49 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 50 | SET_DISP_OFFSET, 51 | 0x00, 52 | SET_COM_PIN_CFG, 53 | 0x02 if self.width > 2 * self.height else 0x12, 54 | # timing and driving scheme 55 | SET_DISP_CLK_DIV, 56 | 0x80, 57 | SET_PRECHARGE, 58 | 0x22 if self.external_vcc else 0xF1, 59 | SET_VCOM_DESEL, 60 | 0x30, # 0.83*Vcc 61 | # display 62 | SET_CONTRAST, 63 | 0xFF, # maximum 64 | SET_ENTIRE_ON, # output follows RAM contents 65 | SET_NORM_INV, # not inverted 66 | # charge pump 67 | SET_CHARGE_PUMP, 68 | 0x10 if self.external_vcc else 0x14, 69 | SET_DISP | 0x01, 70 | ): # on 71 | self.write_cmd(cmd) 72 | self.fill(0) 73 | self.show() 74 | 75 | def poweroff(self): 76 | self.write_cmd(SET_DISP | 0x00) 77 | 78 | def poweron(self): 79 | self.write_cmd(SET_DISP | 0x01) 80 | 81 | def contrast(self, contrast): 82 | self.write_cmd(SET_CONTRAST) 83 | self.write_cmd(contrast) 84 | 85 | def invert(self, invert): 86 | self.write_cmd(SET_NORM_INV | (invert & 1)) 87 | 88 | def show(self): 89 | x0 = 0 90 | x1 = self.width - 1 91 | if self.width == 64: 92 | # displays with width of 64 pixels are shifted by 32 93 | x0 += 32 94 | x1 += 32 95 | self.write_cmd(SET_COL_ADDR) 96 | self.write_cmd(x0) 97 | self.write_cmd(x1) 98 | self.write_cmd(SET_PAGE_ADDR) 99 | self.write_cmd(0) 100 | self.write_cmd(self.pages - 1) 101 | self.write_data(self.buffer) 102 | 103 | 104 | class SSD1306_I2C(SSD1306): 105 | def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): 106 | self.i2c = i2c 107 | self.addr = addr 108 | self.temp = bytearray(2) 109 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 110 | super().__init__(width, height, external_vcc) 111 | 112 | def write_cmd(self, cmd): 113 | self.temp[0] = 0x80 # Co=1, D/C#=0 114 | self.temp[1] = cmd 115 | self.i2c.writeto(self.addr, self.temp) 116 | 117 | def write_data(self, buf): 118 | self.write_list[1] = buf 119 | self.i2c.writevto(self.addr, self.write_list) 120 | 121 | 122 | class SSD1306_SPI(SSD1306): 123 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 124 | self.rate = 10 * 1024 * 1024 125 | dc.init(dc.OUT, value=0) 126 | res.init(res.OUT, value=0) 127 | cs.init(cs.OUT, value=1) 128 | self.spi = spi 129 | self.dc = dc 130 | self.res = res 131 | self.cs = cs 132 | import time 133 | 134 | self.res(1) 135 | time.sleep_ms(1) 136 | self.res(0) 137 | time.sleep_ms(10) 138 | self.res(1) 139 | super().__init__(width, height, external_vcc) 140 | 141 | def write_cmd(self, cmd): 142 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 143 | self.cs(1) 144 | self.dc(0) 145 | self.cs(0) 146 | self.spi.write(bytearray([cmd])) 147 | self.cs(1) 148 | 149 | def write_data(self, buf): 150 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 151 | self.cs(1) 152 | self.dc(1) 153 | self.cs(0) 154 | self.spi.write(buf) 155 | self.cs(1) 156 | --------------------------------------------------------------------------------