├── MICROPYTHON_GEMINI ├── GEMINI.py ├── boot.py ├── font │ └── vga2_8x8.py └── lib │ ├── config.py │ ├── keyboard_module.py │ ├── micropython_urequests-0.6.dist-info │ ├── METADATA │ └── RECORD │ ├── st7789py.py │ └── urequests.py ├── Gemini-Development ├── Gemini.ino ├── bg.h ├── error.h ├── generate.h ├── remixed.h ├── sd_img.h └── wifi_img.h ├── Gemini ├── Gemini.ino └── config.txt ├── Not used ├── bg.h ├── bg.png ├── error.h ├── error.png ├── generate.h ├── generate.png ├── remixed.h ├── remixed.png ├── sd.png ├── sd_img.h ├── wifi.png └── wifi_img.h └── README.md / MICROPYTHON_GEMINI/GEMINI.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, SPI 2 | import st7789py as st7789 3 | from font import vga2_8x8 as font # Using the VGA 2 8x8 font 4 | from keyboard_module import KeyBoard 5 | import urequests 6 | import ujson 7 | import network 8 | import time 9 | import config 10 | 11 | # Customization variables 12 | # Line numbers for input and responses 13 | LINE_START_INPUT = 10 # Starting line for user input 14 | LINE_START_RESPONSE = 2 # Starting line for response 15 | 16 | # Colors 17 | COLOR_BACKGROUND = st7789.BLACK # Background color 18 | COLOR_INPUT = st7789.BLUE # Color for user input 19 | COLOR_RESPONSE = st7789.WHITE # Color for responses 20 | COLOR_ERROR = st7789.RED # Color for error messages 21 | 22 | # Messages 23 | MSG_INPUT_PROMPT = "Model Ready for Input " 24 | MSG_SENDING_INPUT = "Sending Input... " 25 | MSG_SERVER_ERROR = "Server Error 500" 26 | 27 | # Define the display parameters and initialize the display 28 | tft = st7789.ST7789( 29 | SPI(2, baudrate=40000000, sck=Pin(36), mosi=Pin(35), miso=None), 30 | 135, # Width 31 | 240, # Height 32 | reset=Pin(33, Pin.OUT), 33 | cs=Pin(37, Pin.OUT), 34 | dc=Pin(34, Pin.OUT), 35 | backlight=Pin(38, Pin.OUT), 36 | rotation=1, 37 | color_order=st7789.BGR 38 | ) 39 | 40 | # Start with a black background 41 | tft.fill(COLOR_BACKGROUND) 42 | 43 | # Define your Wi-Fi network credentials 44 | ssid = config.ssid 45 | password = config.password 46 | 47 | # Define the Google API key and URL 48 | api_key = config.api_key 49 | url = f'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={api_key}' 50 | 51 | # Define the request headers 52 | headers = { 53 | 'Content-Type': 'application/json' 54 | } 55 | 56 | # Function to connect to the Wi-Fi network 57 | def connect_to_wifi(ssid, password): 58 | wlan = network.WLAN(network.STA_IF) 59 | wlan.active(True) 60 | wlan.connect(ssid, password) 61 | 62 | # Retry connection a few times with a delay 63 | retries = 5 64 | while not wlan.isconnected() and retries > 0: 65 | print('Connecting to Wi-Fi...') 66 | time.sleep(5) 67 | retries -= 1 68 | 69 | if wlan.isconnected(): 70 | print('Connected to Wi-Fi:', wlan.ifconfig()) 71 | return True 72 | else: 73 | print('Failed to connect to Wi-Fi') 74 | return False 75 | 76 | # Function to display text on the display 77 | def display_text(line, text, color=COLOR_RESPONSE): 78 | # Calculate x, y coordinates based on the line number 79 | x = 0 80 | y = line * font.HEIGHT 81 | print(f'Displaying text: "{text}" on line {line}') 82 | tft.text(font, text, x, y, color) 83 | 84 | # Function to clear a single character from the display at a specific x, y position 85 | def clear_char(x, y): 86 | tft.fill_rect(x, y, font.WIDTH, font.HEIGHT, COLOR_BACKGROUND) 87 | 88 | # Function to split long text into lines based on the display width 89 | def split_text(text): 90 | # Calculate the maximum number of characters per line 91 | max_chars_per_line = tft.width // font.WIDTH 92 | 93 | # Split the text into lines based on the maximum characters per line 94 | lines = [text[i:i + max_chars_per_line] for i in range(0, len(text), max_chars_per_line)] 95 | print(f'Split text into lines: {lines}') 96 | return lines 97 | 98 | # Main function to handle the conversation and display 99 | def main(): 100 | # Connect to Wi-Fi 101 | if connect_to_wifi(ssid, password): 102 | # Initialize the keyboard 103 | kb = KeyBoard() 104 | old_keys = set() 105 | user_input = "" # Store the current input string 106 | initial_request_sent = False # Flag to track if the initial request has been sent 107 | 108 | current_line = 0 # Track the current line in the response 109 | response_lines = [] # List of lines in the response text 110 | input_lines = [] # List of lines in the user input text 111 | 112 | # Keep track of user input on the screen 113 | display_text(0, MSG_INPUT_PROMPT, COLOR_INPUT) 114 | 115 | while True: 116 | # Get pressed keys from the keyboard 117 | keys = set(kb.get_pressed_keys()) 118 | 119 | # If the set of keys changes, process the new keys 120 | if keys != old_keys: 121 | print(f'Keys changed. New keys: {keys}') 122 | 123 | # Find new keys pressed 124 | new_keys = keys - old_keys 125 | 126 | for key in new_keys: 127 | print(f'Key pressed: {key}') 128 | 129 | if key == 'ENT': # Enter key pressed 130 | print('Enter key pressed') 131 | 132 | # Clear the whole screen once the first input is detected 133 | if not initial_request_sent: 134 | tft.fill(COLOR_BACKGROUND) 135 | initial_request_sent = True 136 | 137 | display_text(0, MSG_SENDING_INPUT, COLOR_INPUT) 138 | 139 | # Create request payload 140 | data = { 141 | "contents": [ 142 | { 143 | "role": "user", 144 | "parts": [{"text": user_input}] 145 | } 146 | ] 147 | } 148 | 149 | # Convert to JSON format 150 | json_data = ujson.dumps(data) 151 | 152 | # Make the HTTP POST request 153 | retry_count = 0 154 | while retry_count < 3: 155 | try: 156 | print('Sending HTTP POST request...') 157 | response = urequests.post(url, headers=headers, data=json_data) 158 | print(f'Received HTTP response: {response.status_code}') 159 | 160 | if response.status_code == 200: 161 | # Handle successful response 162 | response_data = response.json() 163 | tft.fill(COLOR_BACKGROUND) 164 | 165 | # Parse response 166 | candidates = response_data.get('candidates', []) 167 | if candidates: 168 | first_candidate = candidates[0] 169 | content_parts = first_candidate.get('content', {}).get('parts', []) 170 | if content_parts: 171 | model_response_text = content_parts[0].get('text', '') 172 | print(f'Received model response text: "{model_response_text}"') 173 | 174 | # Split response into lines 175 | response_lines = split_text(model_response_text) 176 | 177 | # Reset current line index 178 | current_line = 0 179 | 180 | # Display the first few lines of response on lines 2 and onward 181 | for i in range(LINE_START_RESPONSE, len(response_lines) + LINE_START_RESPONSE): 182 | if i > LINE_START_RESPONSE + 6: 183 | break 184 | display_text(i, response_lines[i - LINE_START_RESPONSE], COLOR_RESPONSE) 185 | 186 | elif response.status_code == 500: 187 | # Handle server error 500 188 | print('Server Error 500') 189 | tft.fill(COLOR_BACKGROUND) 190 | display_text(0, MSG_SERVER_ERROR, COLOR_ERROR) 191 | 192 | # Retry sending the request after a delay 193 | print('Retrying in 5 seconds...') 194 | time.sleep(5) # Wait for 5 seconds 195 | retry_count += 1 196 | continue # Retry the loop 197 | 198 | else: 199 | print(f'HTTP Error: {response.status_code}') 200 | response.close() 201 | break # Exit the retry loop if request was successful 202 | 203 | except Exception as e: 204 | print(f'Error: {e}') 205 | retry_count += 1 206 | print('Retrying in 5 seconds...') 207 | time.sleep(5) # Retry after a delay 208 | continue # Retry the loop 209 | 210 | # Reset user input and clear the input display 211 | user_input = "" 212 | input_lines = [] # Reset input lines 213 | display_text(0, MSG_INPUT_PROMPT, COLOR_INPUT) 214 | 215 | elif key == 'BSPC': # Backspace key pressed 216 | print('Backspace key pressed') 217 | if user_input: 218 | # Calculate the position of the last character 219 | last_char_index = len(user_input) - 1 220 | line_index = last_char_index // (tft.width // font.WIDTH) 221 | char_index_in_line = last_char_index % (tft.width // font.WIDTH) 222 | 223 | # Calculate the x, y coordinates for the last character 224 | x = char_index_in_line * font.WIDTH 225 | y = (line_index + LINE_START_INPUT) * font.HEIGHT 226 | 227 | # Clear the last character from the display 228 | clear_char(x, y) 229 | 230 | # Remove the last character from user input 231 | user_input = user_input[:-1] 232 | 233 | # Split the user input 234 | input_lines = split_text(user_input) 235 | 236 | # Clear the input display 237 | display_text(0, MSG_INPUT_PROMPT, COLOR_INPUT) 238 | 239 | # Display split input text on line LINE_START_INPUT 240 | for i, line in enumerate(input_lines): 241 | display_text(i + LINE_START_INPUT, line, COLOR_INPUT) 242 | 243 | elif key == 'SPC': # Space key pressed 244 | print('Space key pressed') 245 | user_input += ' ' 246 | 247 | # Split the user input 248 | input_lines = split_text(user_input) 249 | 250 | # Clear the input display 251 | display_text(0, MSG_INPUT_PROMPT, COLOR_INPUT) 252 | 253 | # Display split input text on line LINE_START_INPUT 254 | for i, line in enumerate(input_lines): 255 | display_text(i + LINE_START_INPUT, line, COLOR_INPUT) 256 | 257 | elif key == 'LEFT': # UP arrow key pressed 258 | print('LEFT key pressed') 259 | # Scroll up in response 260 | if current_line > 0: 261 | current_line -= 1 262 | for i in range(LINE_START_RESPONSE, len(response_lines) + LINE_START_RESPONSE): 263 | if i > LINE_START_RESPONSE + 6: 264 | break 265 | display_text(i, response_lines[current_line + i - LINE_START_RESPONSE], COLOR_RESPONSE) 266 | 267 | elif key == 'RIGHT': # DOWN arrow key pressed 268 | print('RIGHT key pressed') 269 | # Scroll down in response 270 | if current_line < len(response_lines) - 7: 271 | current_line += 1 272 | for i in range(LINE_START_RESPONSE, len(response_lines) + LINE_START_RESPONSE): 273 | if i > LINE_START_RESPONSE + 6: 274 | break 275 | display_text(i, response_lines[current_line + i - LINE_START_RESPONSE], COLOR_RESPONSE) 276 | 277 | else: 278 | # Add the pressed key to user input 279 | user_input += key 280 | print(f'Added key to user input: "{key}"') 281 | 282 | # Split the user input 283 | input_lines = split_text(user_input) 284 | 285 | # Clear the input display 286 | display_text(0, MSG_INPUT_PROMPT, COLOR_INPUT) 287 | 288 | # Display split input text on line LINE_START_INPUT 289 | for i, line in enumerate(input_lines): 290 | display_text(i + LINE_START_INPUT, line, COLOR_INPUT) 291 | 292 | # Update old keys 293 | old_keys = keys 294 | 295 | # Add a short delay to avoid excessive polling 296 | time.sleep(0.1) 297 | 298 | else: 299 | print("Unable to establish Wi-Fi connection.") 300 | 301 | # Run the main function 302 | main() 303 | -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | #import esp 3 | #esp.osdebug(None) 4 | #import webrepl 5 | #webrepl.start() 6 | #import displaytest.py 7 | -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/font/vga2_8x8.py: -------------------------------------------------------------------------------- 1 | """converted from vga_8x8.bin """ 2 | WIDTH = 8 3 | HEIGHT = 8 4 | FIRST = 0x00 5 | LAST = 0xff 6 | _FONT =\ 7 | b'\x00\x00\x00\x00\x00\x00\x00\x00'\ 8 | b'\x7e\x81\xa5\x81\xbd\x99\x81\x7e'\ 9 | b'\x7e\xff\xdb\xff\xc3\xe7\xff\x7e'\ 10 | b'\x6c\xfe\xfe\xfe\x7c\x38\x10\x00'\ 11 | b'\x10\x38\x7c\xfe\x7c\x38\x10\x00'\ 12 | b'\x38\x7c\x38\xfe\xfe\xd6\x10\x38'\ 13 | b'\x10\x38\x7c\xfe\xfe\x7c\x10\x38'\ 14 | b'\x00\x00\x18\x3c\x3c\x18\x00\x00'\ 15 | b'\xff\xff\xe7\xc3\xc3\xe7\xff\xff'\ 16 | b'\x00\x3c\x66\x42\x42\x66\x3c\x00'\ 17 | b'\xff\xc3\x99\xbd\xbd\x99\xc3\xff'\ 18 | b'\x0f\x07\x0f\x7d\xcc\xcc\xcc\x78'\ 19 | b'\x3c\x66\x66\x66\x3c\x18\x7e\x18'\ 20 | b'\x3f\x33\x3f\x30\x30\x70\xf0\xe0'\ 21 | b'\x7f\x63\x7f\x63\x63\x67\xe6\xc0'\ 22 | b'\x18\xdb\x3c\xe7\xe7\x3c\xdb\x18'\ 23 | b'\x80\xe0\xf8\xfe\xf8\xe0\x80\x00'\ 24 | b'\x02\x0e\x3e\xfe\x3e\x0e\x02\x00'\ 25 | b'\x18\x3c\x7e\x18\x18\x7e\x3c\x18'\ 26 | b'\x66\x66\x66\x66\x66\x00\x66\x00'\ 27 | b'\x7f\xdb\xdb\x7b\x1b\x1b\x1b\x00'\ 28 | b'\x3e\x61\x3c\x66\x66\x3c\x86\x7c'\ 29 | b'\x00\x00\x00\x00\x7e\x7e\x7e\x00'\ 30 | b'\x18\x3c\x7e\x18\x7e\x3c\x18\xff'\ 31 | b'\x18\x3c\x7e\x18\x18\x18\x18\x00'\ 32 | b'\x18\x18\x18\x18\x7e\x3c\x18\x00'\ 33 | b'\x00\x18\x0c\xfe\x0c\x18\x00\x00'\ 34 | b'\x00\x30\x60\xfe\x60\x30\x00\x00'\ 35 | b'\x00\x00\xc0\xc0\xc0\xfe\x00\x00'\ 36 | b'\x00\x24\x66\xff\x66\x24\x00\x00'\ 37 | b'\x00\x18\x3c\x7e\xff\xff\x00\x00'\ 38 | b'\x00\xff\xff\x7e\x3c\x18\x00\x00'\ 39 | b'\x00\x00\x00\x00\x00\x00\x00\x00'\ 40 | b'\x18\x3c\x3c\x18\x18\x00\x18\x00'\ 41 | b'\x66\x66\x24\x00\x00\x00\x00\x00'\ 42 | b'\x6c\x6c\xfe\x6c\xfe\x6c\x6c\x00'\ 43 | b'\x18\x3e\x60\x3c\x06\x7c\x18\x00'\ 44 | b'\x00\xc6\xcc\x18\x30\x66\xc6\x00'\ 45 | b'\x38\x6c\x38\x76\xdc\xcc\x76\x00'\ 46 | b'\x18\x18\x30\x00\x00\x00\x00\x00'\ 47 | b'\x0c\x18\x30\x30\x30\x18\x0c\x00'\ 48 | b'\x30\x18\x0c\x0c\x0c\x18\x30\x00'\ 49 | b'\x00\x66\x3c\xff\x3c\x66\x00\x00'\ 50 | b'\x00\x18\x18\x7e\x18\x18\x00\x00'\ 51 | b'\x00\x00\x00\x00\x00\x18\x18\x30'\ 52 | b'\x00\x00\x00\x7e\x00\x00\x00\x00'\ 53 | b'\x00\x00\x00\x00\x00\x18\x18\x00'\ 54 | b'\x06\x0c\x18\x30\x60\xc0\x80\x00'\ 55 | b'\x38\x6c\xc6\xd6\xc6\x6c\x38\x00'\ 56 | b'\x18\x38\x18\x18\x18\x18\x7e\x00'\ 57 | b'\x7c\xc6\x06\x1c\x30\x66\xfe\x00'\ 58 | b'\x7c\xc6\x06\x3c\x06\xc6\x7c\x00'\ 59 | b'\x1c\x3c\x6c\xcc\xfe\x0c\x1e\x00'\ 60 | b'\xfe\xc0\xc0\xfc\x06\xc6\x7c\x00'\ 61 | b'\x38\x60\xc0\xfc\xc6\xc6\x7c\x00'\ 62 | b'\xfe\xc6\x0c\x18\x30\x30\x30\x00'\ 63 | b'\x7c\xc6\xc6\x7c\xc6\xc6\x7c\x00'\ 64 | b'\x7c\xc6\xc6\x7e\x06\x0c\x78\x00'\ 65 | b'\x00\x18\x18\x00\x00\x18\x18\x00'\ 66 | b'\x00\x18\x18\x00\x00\x18\x18\x30'\ 67 | b'\x06\x0c\x18\x30\x18\x0c\x06\x00'\ 68 | b'\x00\x00\x7e\x00\x00\x7e\x00\x00'\ 69 | b'\x60\x30\x18\x0c\x18\x30\x60\x00'\ 70 | b'\x7c\xc6\x0c\x18\x18\x00\x18\x00'\ 71 | b'\x7c\xc6\xde\xde\xde\xc0\x78\x00'\ 72 | b'\x38\x6c\xc6\xfe\xc6\xc6\xc6\x00'\ 73 | b'\xfc\x66\x66\x7c\x66\x66\xfc\x00'\ 74 | b'\x3c\x66\xc0\xc0\xc0\x66\x3c\x00'\ 75 | b'\xf8\x6c\x66\x66\x66\x6c\xf8\x00'\ 76 | b'\xfe\x62\x68\x78\x68\x62\xfe\x00'\ 77 | b'\xfe\x62\x68\x78\x68\x60\xf0\x00'\ 78 | b'\x3c\x66\xc0\xc0\xce\x66\x3a\x00'\ 79 | b'\xc6\xc6\xc6\xfe\xc6\xc6\xc6\x00'\ 80 | b'\x3c\x18\x18\x18\x18\x18\x3c\x00'\ 81 | b'\x1e\x0c\x0c\x0c\xcc\xcc\x78\x00'\ 82 | b'\xe6\x66\x6c\x78\x6c\x66\xe6\x00'\ 83 | b'\xf0\x60\x60\x60\x62\x66\xfe\x00'\ 84 | b'\xc6\xee\xfe\xfe\xd6\xc6\xc6\x00'\ 85 | b'\xc6\xe6\xf6\xde\xce\xc6\xc6\x00'\ 86 | b'\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00'\ 87 | b'\xfc\x66\x66\x7c\x60\x60\xf0\x00'\ 88 | b'\x7c\xc6\xc6\xc6\xc6\xce\x7c\x0e'\ 89 | b'\xfc\x66\x66\x7c\x6c\x66\xe6\x00'\ 90 | b'\x3c\x66\x30\x18\x0c\x66\x3c\x00'\ 91 | b'\x7e\x7e\x5a\x18\x18\x18\x3c\x00'\ 92 | b'\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00'\ 93 | b'\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00'\ 94 | b'\xc6\xc6\xc6\xd6\xd6\xfe\x6c\x00'\ 95 | b'\xc6\xc6\x6c\x38\x6c\xc6\xc6\x00'\ 96 | b'\x66\x66\x66\x3c\x18\x18\x3c\x00'\ 97 | b'\xfe\xc6\x8c\x18\x32\x66\xfe\x00'\ 98 | b'\x3c\x30\x30\x30\x30\x30\x3c\x00'\ 99 | b'\xc0\x60\x30\x18\x0c\x06\x02\x00'\ 100 | b'\x3c\x0c\x0c\x0c\x0c\x0c\x3c\x00'\ 101 | b'\x10\x38\x6c\xc6\x00\x00\x00\x00'\ 102 | b'\x00\x00\x00\x00\x00\x00\x00\xff'\ 103 | b'\x30\x18\x0c\x00\x00\x00\x00\x00'\ 104 | b'\x00\x00\x78\x0c\x7c\xcc\x76\x00'\ 105 | b'\xe0\x60\x7c\x66\x66\x66\xdc\x00'\ 106 | b'\x00\x00\x7c\xc6\xc0\xc6\x7c\x00'\ 107 | b'\x1c\x0c\x7c\xcc\xcc\xcc\x76\x00'\ 108 | b'\x00\x00\x7c\xc6\xfe\xc0\x7c\x00'\ 109 | b'\x3c\x66\x60\xf8\x60\x60\xf0\x00'\ 110 | b'\x00\x00\x76\xcc\xcc\x7c\x0c\xf8'\ 111 | b'\xe0\x60\x6c\x76\x66\x66\xe6\x00'\ 112 | b'\x18\x00\x38\x18\x18\x18\x3c\x00'\ 113 | b'\x06\x00\x06\x06\x06\x66\x66\x3c'\ 114 | b'\xe0\x60\x66\x6c\x78\x6c\xe6\x00'\ 115 | b'\x38\x18\x18\x18\x18\x18\x3c\x00'\ 116 | b'\x00\x00\xec\xfe\xd6\xd6\xd6\x00'\ 117 | b'\x00\x00\xdc\x66\x66\x66\x66\x00'\ 118 | b'\x00\x00\x7c\xc6\xc6\xc6\x7c\x00'\ 119 | b'\x00\x00\xdc\x66\x66\x7c\x60\xf0'\ 120 | b'\x00\x00\x76\xcc\xcc\x7c\x0c\x1e'\ 121 | b'\x00\x00\xdc\x76\x60\x60\xf0\x00'\ 122 | b'\x00\x00\x7e\xc0\x7c\x06\xfc\x00'\ 123 | b'\x30\x30\xfc\x30\x30\x36\x1c\x00'\ 124 | b'\x00\x00\xcc\xcc\xcc\xcc\x76\x00'\ 125 | b'\x00\x00\xc6\xc6\xc6\x6c\x38\x00'\ 126 | b'\x00\x00\xc6\xd6\xd6\xfe\x6c\x00'\ 127 | b'\x00\x00\xc6\x6c\x38\x6c\xc6\x00'\ 128 | b'\x00\x00\xc6\xc6\xc6\x7e\x06\xfc'\ 129 | b'\x00\x00\x7e\x4c\x18\x32\x7e\x00'\ 130 | b'\x0e\x18\x18\x70\x18\x18\x0e\x00'\ 131 | b'\x18\x18\x18\x18\x18\x18\x18\x00'\ 132 | b'\x70\x18\x18\x0e\x18\x18\x70\x00'\ 133 | b'\x76\xdc\x00\x00\x00\x00\x00\x00'\ 134 | b'\x00\x10\x38\x6c\xc6\xc6\xfe\x00'\ 135 | b'\x7c\xc6\xc0\xc0\xc6\x7c\x0c\x78'\ 136 | b'\xcc\x00\xcc\xcc\xcc\xcc\x76\x00'\ 137 | b'\x0c\x18\x7c\xc6\xfe\xc0\x7c\x00'\ 138 | b'\x7c\x82\x78\x0c\x7c\xcc\x76\x00'\ 139 | b'\xc6\x00\x78\x0c\x7c\xcc\x76\x00'\ 140 | b'\x30\x18\x78\x0c\x7c\xcc\x76\x00'\ 141 | b'\x30\x30\x78\x0c\x7c\xcc\x76\x00'\ 142 | b'\x00\x00\x7e\xc0\xc0\x7e\x0c\x38'\ 143 | b'\x7c\x82\x7c\xc6\xfe\xc0\x7c\x00'\ 144 | b'\xc6\x00\x7c\xc6\xfe\xc0\x7c\x00'\ 145 | b'\x30\x18\x7c\xc6\xfe\xc0\x7c\x00'\ 146 | b'\x66\x00\x38\x18\x18\x18\x3c\x00'\ 147 | b'\x7c\x82\x38\x18\x18\x18\x3c\x00'\ 148 | b'\x30\x18\x00\x38\x18\x18\x3c\x00'\ 149 | b'\xc6\x38\x6c\xc6\xfe\xc6\xc6\x00'\ 150 | b'\x38\x6c\x7c\xc6\xfe\xc6\xc6\x00'\ 151 | b'\x18\x30\xfe\xc0\xf8\xc0\xfe\x00'\ 152 | b'\x00\x00\x7e\x18\x7e\xd8\x7e\x00'\ 153 | b'\x3e\x6c\xcc\xfe\xcc\xcc\xce\x00'\ 154 | b'\x7c\x82\x7c\xc6\xc6\xc6\x7c\x00'\ 155 | b'\xc6\x00\x7c\xc6\xc6\xc6\x7c\x00'\ 156 | b'\x30\x18\x7c\xc6\xc6\xc6\x7c\x00'\ 157 | b'\x78\x84\x00\xcc\xcc\xcc\x76\x00'\ 158 | b'\x60\x30\xcc\xcc\xcc\xcc\x76\x00'\ 159 | b'\xc6\x00\xc6\xc6\xc6\x7e\x06\xfc'\ 160 | b'\xc6\x38\x6c\xc6\xc6\x6c\x38\x00'\ 161 | b'\xc6\x00\xc6\xc6\xc6\xc6\x7c\x00'\ 162 | b'\x18\x18\x7e\xc0\xc0\x7e\x18\x18'\ 163 | b'\x38\x6c\x64\xf0\x60\x66\xfc\x00'\ 164 | b'\x66\x66\x3c\x7e\x18\x7e\x18\x18'\ 165 | b'\xf8\xcc\xcc\xfa\xc6\xcf\xc6\xc7'\ 166 | b'\x0e\x1b\x18\x3c\x18\xd8\x70\x00'\ 167 | b'\x18\x30\x78\x0c\x7c\xcc\x76\x00'\ 168 | b'\x0c\x18\x00\x38\x18\x18\x3c\x00'\ 169 | b'\x0c\x18\x7c\xc6\xc6\xc6\x7c\x00'\ 170 | b'\x18\x30\xcc\xcc\xcc\xcc\x76\x00'\ 171 | b'\x76\xdc\x00\xdc\x66\x66\x66\x00'\ 172 | b'\x76\xdc\x00\xe6\xf6\xde\xce\x00'\ 173 | b'\x3c\x6c\x6c\x3e\x00\x7e\x00\x00'\ 174 | b'\x38\x6c\x6c\x38\x00\x7c\x00\x00'\ 175 | b'\x18\x00\x18\x18\x30\x63\x3e\x00'\ 176 | b'\x00\x00\x00\xfe\xc0\xc0\x00\x00'\ 177 | b'\x00\x00\x00\xfe\x06\x06\x00\x00'\ 178 | b'\x63\xe6\x6c\x7e\x33\x66\xcc\x0f'\ 179 | b'\x63\xe6\x6c\x7a\x36\x6a\xdf\x06'\ 180 | b'\x18\x00\x18\x18\x3c\x3c\x18\x00'\ 181 | b'\x00\x33\x66\xcc\x66\x33\x00\x00'\ 182 | b'\x00\xcc\x66\x33\x66\xcc\x00\x00'\ 183 | b'\x22\x88\x22\x88\x22\x88\x22\x88'\ 184 | b'\x55\xaa\x55\xaa\x55\xaa\x55\xaa'\ 185 | b'\x77\xdd\x77\xdd\x77\xdd\x77\xdd'\ 186 | b'\x18\x18\x18\x18\x18\x18\x18\x18'\ 187 | b'\x18\x18\x18\x18\xf8\x18\x18\x18'\ 188 | b'\x18\x18\xf8\x18\xf8\x18\x18\x18'\ 189 | b'\x36\x36\x36\x36\xf6\x36\x36\x36'\ 190 | b'\x00\x00\x00\x00\xfe\x36\x36\x36'\ 191 | b'\x00\x00\xf8\x18\xf8\x18\x18\x18'\ 192 | b'\x36\x36\xf6\x06\xf6\x36\x36\x36'\ 193 | b'\x36\x36\x36\x36\x36\x36\x36\x36'\ 194 | b'\x00\x00\xfe\x06\xf6\x36\x36\x36'\ 195 | b'\x36\x36\xf6\x06\xfe\x00\x00\x00'\ 196 | b'\x36\x36\x36\x36\xfe\x00\x00\x00'\ 197 | b'\x18\x18\xf8\x18\xf8\x00\x00\x00'\ 198 | b'\x00\x00\x00\x00\xf8\x18\x18\x18'\ 199 | b'\x18\x18\x18\x18\x1f\x00\x00\x00'\ 200 | b'\x18\x18\x18\x18\xff\x00\x00\x00'\ 201 | b'\x00\x00\x00\x00\xff\x18\x18\x18'\ 202 | b'\x18\x18\x18\x18\x1f\x18\x18\x18'\ 203 | b'\x00\x00\x00\x00\xff\x00\x00\x00'\ 204 | b'\x18\x18\x18\x18\xff\x18\x18\x18'\ 205 | b'\x18\x18\x1f\x18\x1f\x18\x18\x18'\ 206 | b'\x36\x36\x36\x36\x37\x36\x36\x36'\ 207 | b'\x36\x36\x37\x30\x3f\x00\x00\x00'\ 208 | b'\x00\x00\x3f\x30\x37\x36\x36\x36'\ 209 | b'\x36\x36\xf7\x00\xff\x00\x00\x00'\ 210 | b'\x00\x00\xff\x00\xf7\x36\x36\x36'\ 211 | b'\x36\x36\x37\x30\x37\x36\x36\x36'\ 212 | b'\x00\x00\xff\x00\xff\x00\x00\x00'\ 213 | b'\x36\x36\xf7\x00\xf7\x36\x36\x36'\ 214 | b'\x18\x18\xff\x00\xff\x00\x00\x00'\ 215 | b'\x36\x36\x36\x36\xff\x00\x00\x00'\ 216 | b'\x00\x00\xff\x00\xff\x18\x18\x18'\ 217 | b'\x00\x00\x00\x00\xff\x36\x36\x36'\ 218 | b'\x36\x36\x36\x36\x3f\x00\x00\x00'\ 219 | b'\x18\x18\x1f\x18\x1f\x00\x00\x00'\ 220 | b'\x00\x00\x1f\x18\x1f\x18\x18\x18'\ 221 | b'\x00\x00\x00\x00\x3f\x36\x36\x36'\ 222 | b'\x36\x36\x36\x36\xff\x36\x36\x36'\ 223 | b'\x18\x18\xff\x18\xff\x18\x18\x18'\ 224 | b'\x18\x18\x18\x18\xf8\x00\x00\x00'\ 225 | b'\x00\x00\x00\x00\x1f\x18\x18\x18'\ 226 | b'\xff\xff\xff\xff\xff\xff\xff\xff'\ 227 | b'\x00\x00\x00\x00\xff\xff\xff\xff'\ 228 | b'\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0'\ 229 | b'\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'\ 230 | b'\xff\xff\xff\xff\x00\x00\x00\x00'\ 231 | b'\x00\x00\x76\xdc\xc8\xdc\x76\x00'\ 232 | b'\x78\xcc\xcc\xd8\xcc\xc6\xcc\x00'\ 233 | b'\xfe\xc6\xc0\xc0\xc0\xc0\xc0\x00'\ 234 | b'\x00\x00\xfe\x6c\x6c\x6c\x6c\x00'\ 235 | b'\xfe\xc6\x60\x30\x60\xc6\xfe\x00'\ 236 | b'\x00\x00\x7e\xd8\xd8\xd8\x70\x00'\ 237 | b'\x00\x00\x66\x66\x66\x66\x7c\xc0'\ 238 | b'\x00\x76\xdc\x18\x18\x18\x18\x00'\ 239 | b'\x7e\x18\x3c\x66\x66\x3c\x18\x7e'\ 240 | b'\x38\x6c\xc6\xfe\xc6\x6c\x38\x00'\ 241 | b'\x38\x6c\xc6\xc6\x6c\x6c\xee\x00'\ 242 | b'\x0e\x18\x0c\x3e\x66\x66\x3c\x00'\ 243 | b'\x00\x00\x7e\xdb\xdb\x7e\x00\x00'\ 244 | b'\x06\x0c\x7e\xdb\xdb\x7e\x60\xc0'\ 245 | b'\x1e\x30\x60\x7e\x60\x30\x1e\x00'\ 246 | b'\x00\x7c\xc6\xc6\xc6\xc6\xc6\x00'\ 247 | b'\x00\xfe\x00\xfe\x00\xfe\x00\x00'\ 248 | b'\x18\x18\x7e\x18\x18\x00\x7e\x00'\ 249 | b'\x30\x18\x0c\x18\x30\x00\x7e\x00'\ 250 | b'\x0c\x18\x30\x18\x0c\x00\x7e\x00'\ 251 | b'\x0e\x1b\x1b\x18\x18\x18\x18\x18'\ 252 | b'\x18\x18\x18\x18\x18\xd8\xd8\x70'\ 253 | b'\x00\x18\x00\x7e\x00\x18\x00\x00'\ 254 | b'\x00\x76\xdc\x00\x76\xdc\x00\x00'\ 255 | b'\x38\x6c\x6c\x38\x00\x00\x00\x00'\ 256 | b'\x00\x00\x00\x18\x18\x00\x00\x00'\ 257 | b'\x00\x00\x00\x18\x00\x00\x00\x00'\ 258 | b'\x0f\x0c\x0c\x0c\xec\x6c\x3c\x1c'\ 259 | b'\x6c\x36\x36\x36\x36\x00\x00\x00'\ 260 | b'\x78\x0c\x18\x30\x7c\x00\x00\x00'\ 261 | b'\x00\x00\x3c\x3c\x3c\x3c\x00\x00'\ 262 | b'\x00\x00\x00\x00\x00\x00\x00\x00'\ 263 | 264 | FONT = memoryview(_FONT) -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/lib/config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | # Define Wi-Fi network credentials 3 | ssid = 'YOUR_SSID' 4 | password = 'YOUR_PASS' 5 | 6 | # Define the Google API key 7 | api_key = 'API_KEY' 8 | -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/lib/keyboard_module.py: -------------------------------------------------------------------------------- 1 | from machine import Pin 2 | import time 3 | 4 | 5 | #lookup values for our keyboard 6 | kc_shift = const(61) 7 | kc_fn = const(65) 8 | 9 | keymap = { 10 | 67:'`', 63:'1', 57:'2', 53:'3', 47:'4', 43:'5', 37:'6', 33:'7', 27:'8', 23:'9', 17:'0', 13:'_', 7:'=', 3:'BSPC', 11 | 12 | 66:'TAB',62:'q', 56:'w', 52:'e', 46:'r', 42:'t', 36:'y', 32:'u', 26:'i', 22:'o', 16:'p', 12:'[', 6:']', 2:'\\', 13 | 14 | 55:'a', 51:'s', 45:'d', 41:'f', 35:'g', 31:'h', 25:'j', 21:'k', 15:'l', 11:';', 5:"'", 1:'ENT', 15 | 16 | 64:'CTL',60:'OPT',54:'ALT',50:'z', 44:'x', 40:'c', 34:'v', 30:'b', 24:'n', 20:'m', 14:',', 10:'.', 4:'/', 0:'SPC', 17 | } 18 | 19 | keymap_shift = { 20 | 67:'~', 63:'!', 57:'@', 53:'#', 47:'$', 43:'%', 37:'^', 33:'&', 27:'*', 23:'(', 17:')', 13:'-', 7:'+', 3:'BSPC', 21 | 22 | 66:'TAB',62:'Q', 56:'W', 52:'E', 46:'R', 42:'T', 36:'Y', 32:'U', 26:'I', 22:'O', 16:'P', 12:'{', 6:'}', 2:'|', 23 | 24 | 55:'A', 51:'S', 45:'D', 41:'F', 35:'G', 31:'H', 25:'J', 21:'K', 15:'L', 11:':', 5:'"', 1:'ENT', 25 | 26 | 64:'CTL',60:'OPT',54:'ALT',50:'Z', 44:'X', 40:'C', 34:'V', 30:'B', 24:'N', 20:'M', 14:'<', 10:'>', 4:'?', 0:'SPC', 27 | } 28 | 29 | keymap_fn = { 30 | 67:'ESC',63:'F1', 57:'F2', 53:'F3',47:'F4',43:'F5',37:'F6',33:'F7',27:'F8',23:'F9',17:'F10',13:'_',7:'=', 3:'DEL', 31 | 32 | 66:'TAB',62:'q', 56:'w', 52:'e', 46:'r', 42:'t', 36:'y', 32:'u', 26:'i', 22:'o', 16:'p', 12:'[', 6:']', 2:'\\', 33 | 34 | 55:'a', 51:'s', 45:'d', 41:'f', 35:'g', 31:'h', 25:'j', 21:'k', 15:'l', 11:'UP',5:"'", 1:'ENT', 35 | 36 | 64:'CTL',60:'OPT',54:'ALT',50:'z', 44:'x', 40:'c', 34:'v', 30:'b', 24:'n',20:'m',14:'LEFT',10:'DOWN',4:'RIGHT',0:'SPC', 37 | } 38 | 39 | 40 | class KeyBoard(): 41 | def __init__(self): 42 | self._key_list_buffer = [] 43 | 44 | #setup column pins. These are read as inputs. 45 | c0 = Pin(13, Pin.IN, Pin.PULL_UP) 46 | c1 = Pin(15, Pin.IN, Pin.PULL_UP) 47 | c2 = Pin(3, Pin.IN, Pin.PULL_UP) 48 | c3 = Pin(4, Pin.IN, Pin.PULL_UP) 49 | c4 = Pin(5, Pin.IN, Pin.PULL_UP) 50 | c5 = Pin(6, Pin.IN, Pin.PULL_UP) 51 | c6 = Pin(7, Pin.IN, Pin.PULL_UP) 52 | 53 | #setup row pins. These are given to a 74hc138 "demultiplexer", which lets us turn 3 output pins into 8 outputs (8 rows) 54 | a0 = Pin(8, Pin.OUT) 55 | a1 = Pin(9, Pin.OUT) 56 | a2 = Pin(11, Pin.OUT) 57 | 58 | self.pinMap = { 59 | 'C0': c0, 60 | 'C1': c1, 61 | 'C2': c2, 62 | 'C3': c3, 63 | 'C4': c4, 64 | 'C5': c5, 65 | 'C6': c6, 66 | 'A0': a0, 67 | 'A1': a1, 68 | 'A2': a2, 69 | } 70 | 71 | self.key_state = [] 72 | 73 | 74 | def scan(self): 75 | """scan through the matrix to see what keys are pressed.""" 76 | 77 | self._key_list_buffer = [] 78 | 79 | #this for loop iterates through the 8 rows of our matrix 80 | for row in range(0,8): 81 | self.pinMap['A0'].value(row & 0b001) 82 | self.pinMap['A1'].value( ( row & 0b010 ) >> 1) 83 | self.pinMap['A2'].value( ( row & 0b100 ) >> 2) 84 | 85 | 86 | #iterate through each column 87 | columns = [] 88 | for i, col in enumerate(['C6', 'C5', 'C4', 'C3', 'C2', 'C1', 'C0']): 89 | val = self.pinMap[col].value() 90 | 91 | if not val: # button pressed 92 | key_address = (i * 10) + row 93 | self._key_list_buffer.append(key_address) 94 | 95 | return self._key_list_buffer 96 | 97 | 98 | def get_pressed_keys(self): 99 | """Get a readable list of currently held keys.""" 100 | 101 | #update our scan results 102 | self.scan() 103 | 104 | self.key_state = [] 105 | 106 | if not self._key_list_buffer: # if nothing is pressed, we can return an empty list 107 | return self.key_state 108 | 109 | 110 | 111 | if kc_fn in self._key_list_buffer: 112 | 113 | #remove modifier keys which are already accounted for 114 | self._key_list_buffer.remove(kc_fn) 115 | if kc_shift in self._key_list_buffer: 116 | self._key_list_buffer.remove(kc_shift) 117 | 118 | for keycode in self._key_list_buffer: 119 | self.key_state.append(keymap_fn[keycode]) 120 | 121 | elif kc_shift in self._key_list_buffer: 122 | 123 | #remove modifier keys which are already accounted for 124 | self._key_list_buffer.remove(kc_shift) 125 | 126 | for keycode in self._key_list_buffer: 127 | self.key_state.append(keymap_shift[keycode]) 128 | 129 | else: 130 | for keycode in self._key_list_buffer: 131 | self.key_state.append(keymap[keycode]) 132 | 133 | return self.key_state 134 | 135 | 136 | 137 | 138 | if __name__ == "__main__": 139 | # Test the KeyBoard class 140 | kb = KeyBoard() 141 | old_keys = [] 142 | 143 | while True: 144 | keys = kb.get_pressed_keys() 145 | if keys != old_keys: 146 | print(keys) 147 | old_keys = keys 148 | time.sleep(0.1) 149 | -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/lib/micropython_urequests-0.6.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: micropython-urequests 3 | Version: 0.6 4 | Summary: urequests module for MicroPython 5 | Home-page: https://github.com/micropython/micropython-lib 6 | Author: micropython-lib Developers 7 | Author-email: micro-python@googlegroups.com 8 | License: MIT 9 | 10 | -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/lib/micropython_urequests-0.6.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | micropython_urequests-0.6.dist-info/METADATA,, 2 | urequests.py,, 3 | micropython_urequests-0.6.dist-info/RECORD,, -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/lib/st7789py.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2020-2023 Russ Hughes 5 | 6 | Copyright (c) 2019 Ivan Belokobylskiy 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | The driver is based on devbis' st7789py_mpy module from 27 | https://github.com/devbis/st7789py_mpy. 28 | 29 | This driver supports: 30 | 31 | - 320x240, 240x240, 135x240 and 128x128 pixel displays 32 | - Display rotation 33 | - RGB and BGR color orders 34 | - Hardware based scrolling 35 | - Drawing text using 8 and 16 bit wide bitmap fonts with heights that are 36 | multiples of 8. Included are 12 bitmap fonts derived from classic pc 37 | BIOS text mode fonts. 38 | - Drawing text using converted TrueType fonts. 39 | - Drawing converted bitmaps 40 | - Named color constants 41 | 42 | - BLACK 43 | - BLUE 44 | - RED 45 | - GREEN 46 | - CYAN 47 | - MAGENTA 48 | - YELLOW 49 | - WHITE 50 | 51 | """ 52 | 53 | from math import sin, cos 54 | 55 | # 56 | # This allows sphinx to build the docs 57 | # 58 | 59 | try: 60 | from time import sleep_ms 61 | except ImportError: 62 | sleep_ms = lambda ms: None 63 | uint = int 64 | const = lambda x: x 65 | 66 | class micropython: 67 | @staticmethod 68 | def viper(func): 69 | return func 70 | 71 | @staticmethod 72 | def native(func): 73 | return func 74 | 75 | 76 | # 77 | # If you don't need to build the docs, you can remove all of the lines between 78 | # here and the comment above except for the "from time import sleep_ms" line. 79 | # 80 | 81 | import struct 82 | 83 | # ST7789 commands 84 | _ST7789_SWRESET = b"\x01" 85 | _ST7789_SLPIN = b"\x10" 86 | _ST7789_SLPOUT = b"\x11" 87 | _ST7789_NORON = b"\x13" 88 | _ST7789_INVOFF = b"\x20" 89 | _ST7789_INVON = b"\x21" 90 | _ST7789_DISPOFF = b"\x28" 91 | _ST7789_DISPON = b"\x29" 92 | _ST7789_CASET = b"\x2a" 93 | _ST7789_RASET = b"\x2b" 94 | _ST7789_RAMWR = b"\x2c" 95 | _ST7789_VSCRDEF = b"\x33" 96 | _ST7789_COLMOD = b"\x3a" 97 | _ST7789_MADCTL = b"\x36" 98 | _ST7789_VSCSAD = b"\x37" 99 | _ST7789_RAMCTL = b"\xb0" 100 | 101 | # MADCTL bits 102 | _ST7789_MADCTL_MY = const(0x80) 103 | _ST7789_MADCTL_MX = const(0x40) 104 | _ST7789_MADCTL_MV = const(0x20) 105 | _ST7789_MADCTL_ML = const(0x10) 106 | _ST7789_MADCTL_BGR = const(0x08) 107 | _ST7789_MADCTL_MH = const(0x04) 108 | _ST7789_MADCTL_RGB = const(0x00) 109 | 110 | RGB = 0x00 111 | BGR = 0x08 112 | 113 | # Color modes 114 | _COLOR_MODE_65K = const(0x50) 115 | _COLOR_MODE_262K = const(0x60) 116 | _COLOR_MODE_12BIT = const(0x03) 117 | _COLOR_MODE_16BIT = const(0x05) 118 | _COLOR_MODE_18BIT = const(0x06) 119 | _COLOR_MODE_16M = const(0x07) 120 | 121 | # Color definitions 122 | BLACK = const(0x0000) 123 | BLUE = const(0x001F) 124 | RED = const(0xF800) 125 | GREEN = const(0x07E0) 126 | CYAN = const(0x07FF) 127 | MAGENTA = const(0xF81F) 128 | YELLOW = const(0xFFE0) 129 | WHITE = const(0xFFFF) 130 | 131 | _ENCODE_PIXEL = const(">H") 132 | _ENCODE_PIXEL_SWAPPED = const("HH") 134 | _ENCODE_POS_16 = const("> 3 225 | 226 | 227 | class ST7789: 228 | """ 229 | ST7789 driver class 230 | 231 | Args: 232 | spi (spi): spi object **Required** 233 | width (int): display width **Required** 234 | height (int): display height **Required** 235 | reset (pin): reset pin 236 | dc (pin): dc pin **Required** 237 | cs (pin): cs pin 238 | backlight(pin): backlight pin 239 | rotation (int): 240 | 241 | - 0-Portrait 242 | - 1-Landscape 243 | - 2-Inverted Portrait 244 | - 3-Inverted Landscape 245 | 246 | color_order (int): 247 | 248 | - RGB: Red, Green Blue, default 249 | - BGR: Blue, Green, Red 250 | 251 | custom_init (tuple): custom initialization commands 252 | 253 | - ((b'command', b'data', delay_ms), ...) 254 | 255 | custom_rotations (tuple): custom rotation definitions 256 | 257 | - ((width, height, xstart, ystart, madctl, needs_swap), ...) 258 | 259 | """ 260 | 261 | def __init__( 262 | self, 263 | spi, 264 | width, 265 | height, 266 | reset=None, 267 | dc=None, 268 | cs=None, 269 | backlight=None, 270 | rotation=0, 271 | color_order=BGR, 272 | custom_init=None, 273 | custom_rotations=None, 274 | ): 275 | """ 276 | Initialize display. 277 | """ 278 | self.rotations = custom_rotations or self._find_rotations(width, height) 279 | if not self.rotations: 280 | supported_displays = ", ".join( 281 | [f"{display[0]}x{display[1]}" for display in _SUPPORTED_DISPLAYS] 282 | ) 283 | raise ValueError( 284 | f"Unsupported {width}x{height} display. Supported displays: {supported_displays}" 285 | ) 286 | 287 | if dc is None: 288 | raise ValueError("dc pin is required.") 289 | 290 | self.physical_width = self.width = width 291 | self.physical_height = self.height = height 292 | self.xstart = 0 293 | self.ystart = 0 294 | self.spi = spi 295 | self.reset = reset 296 | self.dc = dc 297 | self.cs = cs 298 | self.backlight = backlight 299 | self._rotation = rotation % 4 300 | self.color_order = color_order 301 | self.init_cmds = custom_init or _ST7789_INIT_CMDS 302 | self.hard_reset() 303 | # yes, twice, once is not always enough 304 | self.init(self.init_cmds) 305 | self.init(self.init_cmds) 306 | self.rotation(self._rotation) 307 | self.needs_swap = False 308 | self.fill(0x0) 309 | 310 | if backlight is not None: 311 | backlight.value(1) 312 | 313 | @staticmethod 314 | def _find_rotations(width, height): 315 | for display in _SUPPORTED_DISPLAYS: 316 | if display[0] == width and display[1] == height: 317 | return display[2] 318 | return None 319 | 320 | def init(self, commands): 321 | """ 322 | Initialize display. 323 | """ 324 | for command, data, delay in commands: 325 | self._write(command, data) 326 | sleep_ms(delay) 327 | 328 | def _write(self, command=None, data=None): 329 | """SPI write to the device: commands and data.""" 330 | if self.cs: 331 | self.cs.off() 332 | if command is not None: 333 | self.dc.off() 334 | self.spi.write(command) 335 | if data is not None: 336 | self.dc.on() 337 | self.spi.write(data) 338 | if self.cs: 339 | self.cs.on() 340 | 341 | def hard_reset(self): 342 | """ 343 | Hard reset display. 344 | """ 345 | if self.cs: 346 | self.cs.off() 347 | if self.reset: 348 | self.reset.on() 349 | sleep_ms(10) 350 | if self.reset: 351 | self.reset.off() 352 | sleep_ms(10) 353 | if self.reset: 354 | self.reset.on() 355 | sleep_ms(120) 356 | if self.cs: 357 | self.cs.on() 358 | 359 | def soft_reset(self): 360 | """ 361 | Soft reset display. 362 | """ 363 | self._write(_ST7789_SWRESET) 364 | sleep_ms(150) 365 | 366 | def sleep_mode(self, value): 367 | """ 368 | Enable or disable display sleep mode. 369 | 370 | Args: 371 | value (bool): if True enable sleep mode. if False disable sleep 372 | mode 373 | """ 374 | if value: 375 | self._write(_ST7789_SLPIN) 376 | else: 377 | self._write(_ST7789_SLPOUT) 378 | 379 | def inversion_mode(self, value): 380 | """ 381 | Enable or disable display inversion mode. 382 | 383 | Args: 384 | value (bool): if True enable inversion mode. if False disable 385 | inversion mode 386 | """ 387 | if value: 388 | self._write(_ST7789_INVON) 389 | else: 390 | self._write(_ST7789_INVOFF) 391 | 392 | def rotation(self, rotation): 393 | """ 394 | Set display rotation. 395 | 396 | Args: 397 | rotation (int): 398 | - 0-Portrait 399 | - 1-Landscape 400 | - 2-Inverted Portrait 401 | - 3-Inverted Landscape 402 | 403 | custom_rotations can have any number of rotations 404 | """ 405 | rotation %= len(self.rotations) 406 | self._rotation = rotation 407 | ( 408 | madctl, 409 | self.width, 410 | self.height, 411 | self.xstart, 412 | self.ystart, 413 | self.needs_swap, 414 | ) = self.rotations[rotation] 415 | 416 | if self.color_order == BGR: 417 | madctl |= _ST7789_MADCTL_BGR 418 | else: 419 | madctl &= ~_ST7789_MADCTL_BGR 420 | 421 | self._write(_ST7789_MADCTL, bytes([madctl])) 422 | 423 | def _set_window(self, x0, y0, x1, y1): 424 | """ 425 | Set window to column and row address. 426 | 427 | Args: 428 | x0 (int): column start address 429 | y0 (int): row start address 430 | x1 (int): column end address 431 | y1 (int): row end address 432 | """ 433 | if x0 <= x1 <= self.width and y0 <= y1 <= self.height: 434 | self._write( 435 | _ST7789_CASET, 436 | struct.pack(_ENCODE_POS, x0 + self.xstart, x1 + self.xstart), 437 | ) 438 | self._write( 439 | _ST7789_RASET, 440 | struct.pack(_ENCODE_POS, y0 + self.ystart, y1 + self.ystart), 441 | ) 442 | self._write(_ST7789_RAMWR) 443 | 444 | def vline(self, x, y, length, color): 445 | """ 446 | Draw vertical line at the given location and color. 447 | 448 | Args: 449 | x (int): x coordinate 450 | Y (int): y coordinate 451 | length (int): length of line 452 | color (int): 565 encoded color 453 | """ 454 | self.fill_rect(x, y, 1, length, color) 455 | 456 | def hline(self, x, y, length, color): 457 | """ 458 | Draw horizontal line at the given location and color. 459 | 460 | Args: 461 | x (int): x coordinate 462 | Y (int): y coordinate 463 | length (int): length of line 464 | color (int): 565 encoded color 465 | """ 466 | self.fill_rect(x, y, length, 1, color) 467 | 468 | def pixel(self, x, y, color): 469 | """ 470 | Draw a pixel at the given location and color. 471 | 472 | Args: 473 | x (int): x coordinate 474 | Y (int): y coordinate 475 | color (int): 565 encoded color 476 | """ 477 | self._set_window(x, y, x, y) 478 | self._write( 479 | None, 480 | struct.pack( 481 | _ENCODE_PIXEL_SWAPPED if self.needs_swap else _ENCODE_PIXEL, color 482 | ), 483 | ) 484 | 485 | def blit_buffer(self, buffer, x, y, width, height): 486 | """ 487 | Copy buffer to display at the given location. 488 | 489 | Args: 490 | buffer (bytes): Data to copy to display 491 | x (int): Top left corner x coordinate 492 | Y (int): Top left corner y coordinate 493 | width (int): Width 494 | height (int): Height 495 | """ 496 | self._set_window(x, y, x + width - 1, y + height - 1) 497 | self._write(None, buffer) 498 | 499 | def rect(self, x, y, w, h, color): 500 | """ 501 | Draw a rectangle at the given location, size and color. 502 | 503 | Args: 504 | x (int): Top left corner x coordinate 505 | y (int): Top left corner y coordinate 506 | width (int): Width in pixels 507 | height (int): Height in pixels 508 | color (int): 565 encoded color 509 | """ 510 | self.hline(x, y, w, color) 511 | self.vline(x, y, h, color) 512 | self.vline(x + w - 1, y, h, color) 513 | self.hline(x, y + h - 1, w, color) 514 | 515 | def fill_rect(self, x, y, width, height, color): 516 | """ 517 | Draw a rectangle at the given location, size and filled with color. 518 | 519 | Args: 520 | x (int): Top left corner x coordinate 521 | y (int): Top left corner y coordinate 522 | width (int): Width in pixels 523 | height (int): Height in pixels 524 | color (int): 565 encoded color 525 | """ 526 | self._set_window(x, y, x + width - 1, y + height - 1) 527 | chunks, rest = divmod(width * height, _BUFFER_SIZE) 528 | pixel = struct.pack( 529 | _ENCODE_PIXEL_SWAPPED if self.needs_swap else _ENCODE_PIXEL, color 530 | ) 531 | self.dc.on() 532 | if chunks: 533 | data = pixel * _BUFFER_SIZE 534 | for _ in range(chunks): 535 | self._write(None, data) 536 | if rest: 537 | self._write(None, pixel * rest) 538 | 539 | def fill(self, color): 540 | """ 541 | Fill the entire FrameBuffer with the specified color. 542 | 543 | Args: 544 | color (int): 565 encoded color 545 | """ 546 | self.fill_rect(0, 0, self.width, self.height, color) 547 | 548 | def line(self, x0, y0, x1, y1, color): 549 | """ 550 | Draw a single pixel wide line starting at x0, y0 and ending at x1, y1. 551 | 552 | Args: 553 | x0 (int): Start point x coordinate 554 | y0 (int): Start point y coordinate 555 | x1 (int): End point x coordinate 556 | y1 (int): End point y coordinate 557 | color (int): 565 encoded color 558 | """ 559 | steep = abs(y1 - y0) > abs(x1 - x0) 560 | if steep: 561 | x0, y0 = y0, x0 562 | x1, y1 = y1, x1 563 | if x0 > x1: 564 | x0, x1 = x1, x0 565 | y0, y1 = y1, y0 566 | dx = x1 - x0 567 | dy = abs(y1 - y0) 568 | err = dx // 2 569 | ystep = 1 if y0 < y1 else -1 570 | while x0 <= x1: 571 | if steep: 572 | self.pixel(y0, x0, color) 573 | else: 574 | self.pixel(x0, y0, color) 575 | err -= dy 576 | if err < 0: 577 | y0 += ystep 578 | err += dx 579 | x0 += 1 580 | 581 | def vscrdef(self, tfa, vsa, bfa): 582 | """ 583 | Set Vertical Scrolling Definition. 584 | 585 | To scroll a 135x240 display these values should be 40, 240, 40. 586 | There are 40 lines above the display that are not shown followed by 587 | 240 lines that are shown followed by 40 more lines that are not shown. 588 | You could write to these areas off display and scroll them into view by 589 | changing the TFA, VSA and BFA values. 590 | 591 | Args: 592 | tfa (int): Top Fixed Area 593 | vsa (int): Vertical Scrolling Area 594 | bfa (int): Bottom Fixed Area 595 | """ 596 | self._write(_ST7789_VSCRDEF, struct.pack(">HHH", tfa, vsa, bfa)) 597 | 598 | def vscsad(self, vssa): 599 | """ 600 | Set Vertical Scroll Start Address of RAM. 601 | 602 | Defines which line in the Frame Memory will be written as the first 603 | line after the last line of the Top Fixed Area on the display 604 | 605 | Example: 606 | 607 | for line in range(40, 280, 1): 608 | tft.vscsad(line) 609 | utime.sleep(0.01) 610 | 611 | Args: 612 | vssa (int): Vertical Scrolling Start Address 613 | 614 | """ 615 | self._write(_ST7789_VSCSAD, struct.pack(">H", vssa)) 616 | 617 | @micropython.viper 618 | @staticmethod 619 | def _pack8(glyphs, idx: uint, fg_color: uint, bg_color: uint): 620 | buffer = bytearray(128) 621 | bitmap = ptr16(buffer) 622 | glyph = ptr8(glyphs) 623 | 624 | for i in range(0, 64, 8): 625 | byte = glyph[idx] 626 | bitmap[i] = fg_color if byte & _BIT7 else bg_color 627 | bitmap[i + 1] = fg_color if byte & _BIT6 else bg_color 628 | bitmap[i + 2] = fg_color if byte & _BIT5 else bg_color 629 | bitmap[i + 3] = fg_color if byte & _BIT4 else bg_color 630 | bitmap[i + 4] = fg_color if byte & _BIT3 else bg_color 631 | bitmap[i + 5] = fg_color if byte & _BIT2 else bg_color 632 | bitmap[i + 6] = fg_color if byte & _BIT1 else bg_color 633 | bitmap[i + 7] = fg_color if byte & _BIT0 else bg_color 634 | idx += 1 635 | 636 | return buffer 637 | 638 | @micropython.viper 639 | @staticmethod 640 | def _pack16(glyphs, idx: uint, fg_color: uint, bg_color: uint): 641 | """ 642 | Pack a character into a byte array. 643 | 644 | Args: 645 | char (str): character to pack 646 | 647 | Returns: 648 | 128 bytes: character bitmap in color565 format 649 | """ 650 | 651 | buffer = bytearray(256) 652 | bitmap = ptr16(buffer) 653 | glyph = ptr8(glyphs) 654 | 655 | for i in range(0, 128, 16): 656 | byte = glyph[idx] 657 | 658 | bitmap[i] = fg_color if byte & _BIT7 else bg_color 659 | bitmap[i + 1] = fg_color if byte & _BIT6 else bg_color 660 | bitmap[i + 2] = fg_color if byte & _BIT5 else bg_color 661 | bitmap[i + 3] = fg_color if byte & _BIT4 else bg_color 662 | bitmap[i + 4] = fg_color if byte & _BIT3 else bg_color 663 | bitmap[i + 5] = fg_color if byte & _BIT2 else bg_color 664 | bitmap[i + 6] = fg_color if byte & _BIT1 else bg_color 665 | bitmap[i + 7] = fg_color if byte & _BIT0 else bg_color 666 | idx += 1 667 | 668 | byte = glyph[idx] 669 | bitmap[i + 8] = fg_color if byte & _BIT7 else bg_color 670 | bitmap[i + 9] = fg_color if byte & _BIT6 else bg_color 671 | bitmap[i + 10] = fg_color if byte & _BIT5 else bg_color 672 | bitmap[i + 11] = fg_color if byte & _BIT4 else bg_color 673 | bitmap[i + 12] = fg_color if byte & _BIT3 else bg_color 674 | bitmap[i + 13] = fg_color if byte & _BIT2 else bg_color 675 | bitmap[i + 14] = fg_color if byte & _BIT1 else bg_color 676 | bitmap[i + 15] = fg_color if byte & _BIT0 else bg_color 677 | idx += 1 678 | 679 | return buffer 680 | 681 | def _text8(self, font, text, x0, y0, fg_color=WHITE, bg_color=BLACK): 682 | """ 683 | Internal method to write characters with width of 8 and 684 | heights of 8 or 16. 685 | 686 | Args: 687 | font (module): font module to use 688 | text (str): text to write 689 | x0 (int): column to start drawing at 690 | y0 (int): row to start drawing at 691 | color (int): 565 encoded color to use for characters 692 | background (int): 565 encoded color to use for background 693 | """ 694 | 695 | for char in text: 696 | ch = ord(char) 697 | if ( 698 | font.FIRST <= ch < font.LAST 699 | and x0 + font.WIDTH <= self.width 700 | and y0 + font.HEIGHT <= self.height 701 | ): 702 | if font.HEIGHT == 8: 703 | passes = 1 704 | size = 8 705 | each = 0 706 | else: 707 | passes = 2 708 | size = 16 709 | each = 8 710 | 711 | for line in range(passes): 712 | idx = (ch - font.FIRST) * size + (each * line) 713 | buffer = self._pack8(font.FONT, idx, fg_color, bg_color) 714 | self.blit_buffer(buffer, x0, y0 + 8 * line, 8, 8) 715 | 716 | x0 += 8 717 | 718 | def _text16(self, font, text, x0, y0, fg_color=WHITE, bg_color=BLACK): 719 | """ 720 | Internal method to draw characters with width of 16 and heights of 16 721 | or 32. 722 | 723 | Args: 724 | font (module): font module to use 725 | text (str): text to write 726 | x0 (int): column to start drawing at 727 | y0 (int): row to start drawing at 728 | color (int): 565 encoded color to use for characters 729 | background (int): 565 encoded color to use for background 730 | """ 731 | 732 | for char in text: 733 | ch = ord(char) 734 | if ( 735 | font.FIRST <= ch < font.LAST 736 | and x0 + font.WIDTH <= self.width 737 | and y0 + font.HEIGHT <= self.height 738 | ): 739 | each = 16 740 | if font.HEIGHT == 16: 741 | passes = 2 742 | size = 32 743 | else: 744 | passes = 4 745 | size = 64 746 | 747 | for line in range(passes): 748 | idx = (ch - font.FIRST) * size + (each * line) 749 | buffer = self._pack16(font.FONT, idx, fg_color, bg_color) 750 | self.blit_buffer(buffer, x0, y0 + 8 * line, 16, 8) 751 | x0 += 16 752 | 753 | def text(self, font, text, x0, y0, color=WHITE, background=BLACK): 754 | """ 755 | Draw text on display in specified font and colors. 8 and 16 bit wide 756 | fonts are supported. 757 | 758 | Args: 759 | font (module): font module to use. 760 | text (str): text to write 761 | x0 (int): column to start drawing at 762 | y0 (int): row to start drawing at 763 | color (int): 565 encoded color to use for characters 764 | background (int): 565 encoded color to use for background 765 | """ 766 | fg_color = color if self.needs_swap else ((color << 8) & 0xFF00) | (color >> 8) 767 | bg_color = ( 768 | background 769 | if self.needs_swap 770 | else ((background << 8) & 0xFF00) | (background >> 8) 771 | ) 772 | 773 | if font.WIDTH == 8: 774 | self._text8(font, text, x0, y0, fg_color, bg_color) 775 | else: 776 | self._text16(font, text, x0, y0, fg_color, bg_color) 777 | 778 | def bitmap(self, bitmap, x, y, index=0): 779 | """ 780 | Draw a bitmap on display at the specified column and row 781 | 782 | Args: 783 | bitmap (bitmap_module): The module containing the bitmap to draw 784 | x (int): column to start drawing at 785 | y (int): row to start drawing at 786 | index (int): Optional index of bitmap to draw from multiple bitmap 787 | module 788 | """ 789 | width = bitmap.WIDTH 790 | height = bitmap.HEIGHT 791 | to_col = x + width - 1 792 | to_row = y + height - 1 793 | if self.width <= to_col or self.height <= to_row: 794 | return 795 | 796 | bitmap_size = height * width 797 | buffer_len = bitmap_size * 2 798 | bpp = bitmap.BPP 799 | bs_bit = bpp * bitmap_size * index # if index > 0 else 0 800 | palette = bitmap.PALETTE 801 | needs_swap = self.needs_swap 802 | buffer = bytearray(buffer_len) 803 | 804 | for i in range(0, buffer_len, 2): 805 | color_index = 0 806 | for _ in range(bpp): 807 | color_index = (color_index << 1) | ( 808 | (bitmap.BITMAP[bs_bit >> 3] >> (7 - (bs_bit & 7))) & 1 809 | ) 810 | bs_bit += 1 811 | 812 | color = palette[color_index] 813 | if needs_swap: 814 | buffer[i] = color & 0xFF 815 | buffer[i + 1] = color >> 8 816 | else: 817 | buffer[i] = color >> 8 818 | buffer[i + 1] = color & 0xFF 819 | 820 | self._set_window(x, y, to_col, to_row) 821 | self._write(None, buffer) 822 | 823 | def pbitmap(self, bitmap, x, y, index=0): 824 | """ 825 | Draw a bitmap on display at the specified column and row one row at a time 826 | 827 | Args: 828 | bitmap (bitmap_module): The module containing the bitmap to draw 829 | x (int): column to start drawing at 830 | y (int): row to start drawing at 831 | index (int): Optional index of bitmap to draw from multiple bitmap 832 | module 833 | 834 | """ 835 | width = bitmap.WIDTH 836 | height = bitmap.HEIGHT 837 | bitmap_size = height * width 838 | bpp = bitmap.BPP 839 | bs_bit = bpp * bitmap_size * index # if index > 0 else 0 840 | palette = bitmap.PALETTE 841 | needs_swap = self.needs_swap 842 | buffer = bytearray(bitmap.WIDTH * 2) 843 | 844 | for row in range(height): 845 | for col in range(width): 846 | color_index = 0 847 | for _ in range(bpp): 848 | color_index <<= 1 849 | color_index |= ( 850 | bitmap.BITMAP[bs_bit // 8] & 1 << (7 - (bs_bit % 8)) 851 | ) > 0 852 | bs_bit += 1 853 | color = palette[color_index] 854 | if needs_swap: 855 | buffer[col * 2] = color & 0xFF 856 | buffer[col * 2 + 1] = color >> 8 & 0xFF 857 | else: 858 | buffer[col * 2] = color >> 8 & 0xFF 859 | buffer[col * 2 + 1] = color & 0xFF 860 | 861 | to_col = x + width - 1 862 | to_row = y + row 863 | if self.width > to_col and self.height > to_row: 864 | self._set_window(x, y + row, to_col, to_row) 865 | self._write(None, buffer) 866 | 867 | def write(self, font, string, x, y, fg=WHITE, bg=BLACK): 868 | """ 869 | Write a string using a converted true-type font on the display starting 870 | at the specified column and row 871 | 872 | Args: 873 | font (font): The module containing the converted true-type font 874 | s (string): The string to write 875 | x (int): column to start writing 876 | y (int): row to start writing 877 | fg (int): foreground color, optional, defaults to WHITE 878 | bg (int): background color, optional, defaults to BLACK 879 | """ 880 | buffer_len = font.HEIGHT * font.MAX_WIDTH * 2 881 | buffer = bytearray(buffer_len) 882 | fg_hi = fg >> 8 883 | fg_lo = fg & 0xFF 884 | 885 | bg_hi = bg >> 8 886 | bg_lo = bg & 0xFF 887 | 888 | for character in string: 889 | try: 890 | char_index = font.MAP.index(character) 891 | offset = char_index * font.OFFSET_WIDTH 892 | bs_bit = font.OFFSETS[offset] 893 | if font.OFFSET_WIDTH > 1: 894 | bs_bit = (bs_bit << 8) + font.OFFSETS[offset + 1] 895 | 896 | if font.OFFSET_WIDTH > 2: 897 | bs_bit = (bs_bit << 8) + font.OFFSETS[offset + 2] 898 | 899 | char_width = font.WIDTHS[char_index] 900 | buffer_needed = char_width * font.HEIGHT * 2 901 | 902 | for i in range(0, buffer_needed, 2): 903 | if font.BITMAPS[bs_bit // 8] & 1 << (7 - (bs_bit % 8)) > 0: 904 | buffer[i] = fg_hi 905 | buffer[i + 1] = fg_lo 906 | else: 907 | buffer[i] = bg_hi 908 | buffer[i + 1] = bg_lo 909 | 910 | bs_bit += 1 911 | 912 | to_col = x + char_width - 1 913 | to_row = y + font.HEIGHT - 1 914 | if self.width > to_col and self.height > to_row: 915 | self._set_window(x, y, to_col, to_row) 916 | self._write(None, buffer[:buffer_needed]) 917 | 918 | x += char_width 919 | 920 | except ValueError: 921 | pass 922 | 923 | def write_width(self, font, string): 924 | """ 925 | Returns the width in pixels of the string if it was written with the 926 | specified font 927 | 928 | Args: 929 | font (font): The module containing the converted true-type font 930 | string (string): The string to measure 931 | 932 | Returns: 933 | int: The width of the string in pixels 934 | 935 | """ 936 | width = 0 937 | for character in string: 938 | try: 939 | char_index = font.MAP.index(character) 940 | width += font.WIDTHS[char_index] 941 | except ValueError: 942 | pass 943 | 944 | return width 945 | 946 | @micropython.native 947 | def polygon(self, points, x, y, color, angle=0, center_x=0, center_y=0): 948 | """ 949 | Draw a polygon on the display. 950 | 951 | Args: 952 | points (list): List of points to draw. 953 | x (int): X-coordinate of the polygon's position. 954 | y (int): Y-coordinate of the polygon's position. 955 | color (int): 565 encoded color. 956 | angle (float): Rotation angle in radians (default: 0). 957 | center_x (int): X-coordinate of the rotation center (default: 0). 958 | center_y (int): Y-coordinate of the rotation center (default: 0). 959 | 960 | Raises: 961 | ValueError: If the polygon has less than 3 points. 962 | """ 963 | if len(points) < 3: 964 | raise ValueError("Polygon must have at least 3 points.") 965 | 966 | if angle: 967 | cos_a = cos(angle) 968 | sin_a = sin(angle) 969 | rotated = [ 970 | ( 971 | x 972 | + center_x 973 | + int( 974 | (point[0] - center_x) * cos_a - (point[1] - center_y) * sin_a 975 | ), 976 | y 977 | + center_y 978 | + int( 979 | (point[0] - center_x) * sin_a + (point[1] - center_y) * cos_a 980 | ), 981 | ) 982 | for point in points 983 | ] 984 | else: 985 | rotated = [(x + int((point[0])), y + int((point[1]))) for point in points] 986 | 987 | for i in range(1, len(rotated)): 988 | self.line( 989 | rotated[i - 1][0], 990 | rotated[i - 1][1], 991 | rotated[i][0], 992 | rotated[i][1], 993 | color, 994 | ) 995 | -------------------------------------------------------------------------------- / MICROPYTHON_GEMINI/lib/urequests.py: -------------------------------------------------------------------------------- 1 | import usocket 2 | 3 | class Response: 4 | 5 | def __init__(self, f): 6 | self.raw = f 7 | self.encoding = "utf-8" 8 | self._cached = None 9 | 10 | def close(self): 11 | if self.raw: 12 | self.raw.close() 13 | self.raw = None 14 | self._cached = None 15 | 16 | @property 17 | def content(self): 18 | if self._cached is None: 19 | try: 20 | self._cached = self.raw.read() 21 | finally: 22 | self.raw.close() 23 | self.raw = None 24 | return self._cached 25 | 26 | @property 27 | def text(self): 28 | return str(self.content, self.encoding) 29 | 30 | def json(self): 31 | import ujson 32 | return ujson.loads(self.content) 33 | 34 | 35 | def request(method, url, data=None, json=None, headers={}, stream=None): 36 | try: 37 | proto, dummy, host, path = url.split("/", 3) 38 | except ValueError: 39 | proto, dummy, host = url.split("/", 2) 40 | path = "" 41 | if proto == "http:": 42 | port = 80 43 | elif proto == "https:": 44 | import ussl 45 | port = 443 46 | else: 47 | raise ValueError("Unsupported protocol: " + proto) 48 | 49 | if ":" in host: 50 | host, port = host.split(":", 1) 51 | port = int(port) 52 | 53 | ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) 54 | ai = ai[0] 55 | 56 | s = usocket.socket(ai[0], ai[1], ai[2]) 57 | try: 58 | s.connect(ai[-1]) 59 | if proto == "https:": 60 | s = ussl.wrap_socket(s, server_hostname=host) 61 | s.write(b"%s /%s HTTP/1.0\r\n" % (method, path)) 62 | if not "Host" in headers: 63 | s.write(b"Host: %s\r\n" % host) 64 | # Iterate over keys to avoid tuple alloc 65 | for k in headers: 66 | s.write(k) 67 | s.write(b": ") 68 | s.write(headers[k]) 69 | s.write(b"\r\n") 70 | if json is not None: 71 | assert data is None 72 | import ujson 73 | data = ujson.dumps(json) 74 | s.write(b"Content-Type: application/json\r\n") 75 | if data: 76 | s.write(b"Content-Length: %d\r\n" % len(data)) 77 | s.write(b"\r\n") 78 | if data: 79 | s.write(data) 80 | 81 | l = s.readline() 82 | #print(l) 83 | l = l.split(None, 2) 84 | status = int(l[1]) 85 | reason = "" 86 | if len(l) > 2: 87 | reason = l[2].rstrip() 88 | while True: 89 | l = s.readline() 90 | if not l or l == b"\r\n": 91 | break 92 | #print(l) 93 | if l.startswith(b"Transfer-Encoding:"): 94 | if b"chunked" in l: 95 | raise ValueError("Unsupported " + l) 96 | elif l.startswith(b"Location:") and not 200 <= status <= 299: 97 | raise NotImplementedError("Redirects not yet supported") 98 | except OSError: 99 | s.close() 100 | raise 101 | 102 | resp = Response(s) 103 | resp.status_code = status 104 | resp.reason = reason 105 | return resp 106 | 107 | 108 | def head(url, **kw): 109 | return request("HEAD", url, **kw) 110 | 111 | def get(url, **kw): 112 | return request("GET", url, **kw) 113 | 114 | def post(url, **kw): 115 | return request("POST", url, **kw) 116 | 117 | def put(url, **kw): 118 | return request("PUT", url, **kw) 119 | 120 | def patch(url, **kw): 121 | return request("PATCH", url, **kw) 122 | 123 | def delete(url, **kw): 124 | return request("DELETE", url, **kw) 125 | -------------------------------------------------------------------------------- /Gemini-Development/Gemini.ino: -------------------------------------------------------------------------------- 1 | #include "M5Cardputer.h" 2 | #include "M5GFX.h" 3 | #include "WiFi.h" 4 | #include "HTTPClient.h" 5 | #include "ArduinoJson.h" 6 | #include "SD.h" 7 | #include "FS.h" 8 | #include "SPI.h" 9 | 10 | 11 | 12 | 13 | 14 | //images 15 | #include "remixed.h" 16 | #include "sd_img.h" 17 | #include "wifi_img.h" 18 | #include "generate.h" 19 | #include "error.h" 20 | #include "bg.h" 21 | 22 | // Define maximum lines and line length 23 | constexpr size_t maxLines = 100; 24 | constexpr size_t maxLineLength = 100; 25 | 26 | // Initialize lines array to hold parsed lines of text 27 | char lines[maxLines][maxLineLength]; 28 | size_t numLines = 0; // Number of parsed lines 29 | size_t numDisplayableLines; // Number of lines that can be displayed on the screen 30 | 31 | // Canvas for drawing on the screen 32 | M5Canvas canvas(&M5Cardputer.Display); 33 | 34 | // Global variables 35 | int scrollPosition = 0; 36 | int scrollPositionDOWN = 0; 37 | String userInput = ""; // Initial prompt 38 | String geminiResponse = ""; // Response from Gemini model 39 | int retryAttempts = 5; // Number of retry attempts on request failure 40 | String inputLines[maxLines]; // Array to hold each line of input 41 | int currentLineIndex = 0; // Index of the current line in the array 42 | 43 | 44 | // Line height 45 | int lineHeight; 46 | 47 | // Function to parse text from String into lines 48 | void parseText(const String &text) { 49 | numLines = 0; 50 | 51 | // Get the display width 52 | int displayWidth = M5Cardputer.Display.width(); 53 | 54 | // Split input text into words and parse into lines 55 | String word; 56 | size_t currentLine = 0; 57 | char line[maxLineLength] = ""; 58 | int currentLineWidth = 0; 59 | 60 | // Iterate through each character in the input text 61 | for (size_t i = 0; i < text.length(); i++) { 62 | char ch = text.charAt(i); 63 | 64 | if (ch == ' ' || i == text.length() - 1) { 65 | // If space or end of string, process the current word 66 | if (i == text.length() - 1 && ch != ' ') { 67 | // Append the last character if not a space 68 | word += ch; 69 | } 70 | 71 | // Calculate word width 72 | int wordWidth = canvas.textWidth(word.c_str(), nullptr) + 7; //+6 for font offset 73 | 74 | // Check if adding the word would exceed display width 75 | if (currentLineWidth + wordWidth > displayWidth) { 76 | // Save the line and reset 77 | strncpy(lines[currentLine], line, maxLineLength); 78 | lines[currentLine][maxLineLength - 1] = '\0'; 79 | currentLine++; 80 | line[0] = '\0'; 81 | currentLineWidth = 0; 82 | } 83 | 84 | // Append word and a space to the line 85 | strncat(line, word.c_str(), maxLineLength); 86 | strncat(line, " ", 2); 87 | currentLineWidth += wordWidth; 88 | 89 | // Reset the word 90 | word = ""; 91 | } else { 92 | // Add the current character to the word 93 | word += ch; 94 | } 95 | } 96 | 97 | // Save the last line if not exceeded maxLines 98 | if (currentLine < maxLines) { 99 | strncpy(lines[currentLine], line, maxLineLength); 100 | lines[currentLine][maxLineLength - 1] = '\0'; 101 | currentLine++; 102 | } 103 | 104 | // Update numLines with the number of lines parsed 105 | numLines = currentLine; 106 | } 107 | 108 | void displayText(const String &text, int &scrollPosition, int scrollDirection, bool isSecondInput) { 109 | // Initialize M5Cardputer 110 | M5Cardputer.begin(); 111 | 112 | // Display configuration 113 | M5Cardputer.Display.setRotation(1); 114 | M5Cardputer.Display.setTextColor(GREEN); 115 | M5Cardputer.Display.setTextDatum(TC_DATUM); 116 | M5Cardputer.Display.setTextSize(2); 117 | 118 | // Create a canvas with the width of the display and the height of the text area 119 | int textAreaHeight = (M5Cardputer.Display.height() / 3) + 3; // Use bottom 1/3 of the screen 120 | int textAreaStartY = M5Cardputer.Display.height() - textAreaHeight; 121 | 122 | if (isSecondInput) { 123 | 124 | // Draw a rectangle to mask the bottom 1/3 of the screen 125 | //M5Cardputer.Display.fillRect(0, textAreaStartY, M5Cardputer.Display.width(), textAreaHeight, GREEN); 126 | // Optionally, fill the area with a color (e.g., GREEN) 127 | M5Cardputer.Display.fillRect(0, textAreaStartY, M5Cardputer.Display.width(), textAreaHeight, BLUE); 128 | 129 | // Create a canvas for the second text 130 | canvas.createSprite(M5Cardputer.Display.width(), textAreaHeight); 131 | canvas.setColorDepth(8); 132 | canvas.setTextWrap(false); 133 | canvas.setTextSize(1.5); 134 | canvas.setTextColor(TFT_GREENYELLOW); 135 | //canvas.setFont(&Font2); 136 | 137 | 138 | // Calculate line height 139 | lineHeight = canvas.fontHeight(); 140 | 141 | // Calculate the number of displayable lines for the bottom 1/3 of the screen 142 | numDisplayableLines = textAreaHeight / lineHeight; 143 | 144 | // Parse text into lines 145 | parseText(text); 146 | 147 | // Update scroll position based on scroll direction 148 | if (scrollDirection == -1) { 149 | // Scroll up 150 | if (scrollPositionDOWN > 0) { 151 | scrollPositionDOWN--; 152 | } 153 | } else if (scrollDirection == +1) { 154 | // Scroll down 155 | if (scrollPositionDOWN + numDisplayableLines < numLines) { 156 | scrollPositionDOWN++; 157 | } 158 | } 159 | 160 | // Clear the canvas 161 | canvas.fillScreen(TFT_NAVY); 162 | 163 | 164 | 165 | // Display lines on the canvas 166 | int y = 0; 167 | for (size_t i = scrollPositionDOWN; i < scrollPositionDOWN + numDisplayableLines && i < numLines; i++) { 168 | // Set cursor for each line 169 | canvas.setCursor(0, y); 170 | // Print line from the array 171 | canvas.print(lines[i]); 172 | // Increment y by line height for the next line 173 | y += lineHeight; 174 | } 175 | 176 | // Push the canvas content to the display, offsetting the starting position to the bottom 1/3 of the screen 177 | canvas.pushSprite(0, textAreaStartY); 178 | //M5Cardputer.Display.drawRect(0, textAreaStartY, M5Cardputer.Display.width(), textAreaHeight, BLUE); 179 | } else { 180 | // Display the first text normally, using the whole screen 181 | M5Cardputer.Display.fillScreen(TFT_BLACK); 182 | 183 | // Create a canvas for the first text 184 | canvas.createSprite(M5Cardputer.Display.width(), M5Cardputer.Display.height()); 185 | canvas.setColorDepth(8); 186 | canvas.setTextWrap(false); 187 | canvas.setTextSize(1.5); 188 | 189 | // Calculate line height 190 | lineHeight = canvas.fontHeight(); 191 | 192 | // Calculate the number of displayable lines for the whole screen 193 | numDisplayableLines = M5Cardputer.Display.height() / lineHeight; 194 | 195 | // Parse text into lines 196 | parseText(text); 197 | 198 | // Update scroll position based on scroll direction 199 | if (scrollDirection == -1) { 200 | // Scroll up 201 | if (scrollPosition > 0) { 202 | scrollPosition--; 203 | } 204 | } else if (scrollDirection == +1) { 205 | // Scroll down 206 | if (scrollPosition + numDisplayableLines < numLines) { 207 | scrollPosition++; 208 | } 209 | } 210 | 211 | // Clear the canvas 212 | //canvas.fillScreen(TFT_DARKGREY); 213 | canvas.setTextColor(TFT_LIGHTGREY); 214 | 215 | // Display lines on the canvas 216 | int y = 0; 217 | for (size_t i = scrollPosition; i < scrollPosition + numDisplayableLines && i < numLines; i++) { 218 | // Set cursor for each line 219 | canvas.setCursor(0, y); 220 | // Print line from the array 221 | canvas.print(lines[i]); 222 | // Increment y by line height for the next line 223 | y += lineHeight; 224 | } 225 | 226 | // Push the canvas content to the display 227 | canvas.pushSprite(0, 0); 228 | } 229 | 230 | // Add a delay for smooth scrolling 231 | delay(20); 232 | } 233 | 234 | // Define SD card pins and SPI frequency 235 | #define SD_SCK GPIO_NUM_40 236 | #define SD_MISO GPIO_NUM_39 237 | #define SD_MOSI GPIO_NUM_14 238 | #define SD_SS GPIO_NUM_12 239 | #define SD_SPI_FREQ 1000000 240 | 241 | // Create an instance of the SPIClass for the HSPI 242 | SPIClass hspi = SPIClass(HSPI); 243 | 244 | //To run menu or not 245 | bool Menu = false; 246 | 247 | // Global variables 248 | 249 | //String userInput = ""; // Store user input 250 | int selectedOption = 0; // Index of the currently selected menu option 251 | const int numOptions = 5; // Total number of menu options 252 | 253 | // Menu options 254 | const String menuOptions[numOptions] = { 255 | "1. Notes", 256 | "2. Read from SD Card", 257 | "3. Update Configuration", 258 | "4. Clear SD Card", 259 | "5. Exit Menu" 260 | }; 261 | 262 | // Configuration variables 263 | String ssid; 264 | String password; 265 | String api_key; 266 | 267 | // Function to initialize the SD card 268 | bool initializeSDCard() { 269 | // Begin the SPI interface 270 | hspi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS); 271 | 272 | // Initialize the SD card 273 | if (!SD.begin(SD_SS, hspi, SD_SPI_FREQ)) { 274 | // Clear the canvas screen to display the message clearly 275 | M5Cardputer.Display.pushImage(0, 0, 240, 135, sd_img); 276 | 277 | // Set the cursor position to the top-left corner 278 | M5Cardputer.Display.setCursor(0, 0); 279 | 280 | // Set the text color to white for visibility 281 | M5Cardputer.Display.setTextColor(WHITE); 282 | 283 | // Print the provided debug message 284 | M5Cardputer.Display.println("SD Card Initialization Failed!"); 285 | 286 | // Delay for visibility 287 | delay(800); 288 | 289 | return false; 290 | } else { 291 | // Clear the canvas screen to display the message clearly 292 | M5Cardputer.Display.pushImage(0, 0, 240, 135, sd_img); 293 | 294 | // Set the cursor position to the top-left corner 295 | M5Cardputer.Display.setCursor(0, 0); 296 | 297 | // Set the text color to white for visibility 298 | M5Cardputer.Display.setTextColor(WHITE); 299 | 300 | // Print the provided debug message 301 | M5Cardputer.Display.println("SD Card Initialized Successfully!"); 302 | 303 | // Delay for visibility 304 | delay(800); 305 | 306 | return true; 307 | } 308 | } 309 | // Function to display the saved configuration parameters on the screen 310 | void displayConfig() { 311 | // Clear the display 312 | M5Cardputer.Display.fillScreen(BLACK); 313 | 314 | // Set the cursor position to the top-left corner 315 | M5Cardputer.Display.setCursor(0, 0); 316 | 317 | // Set the text color to white for visibility 318 | M5Cardputer.Display.setTextColor(WHITE); 319 | 320 | // Display the current configuration parameters 321 | M5Cardputer.Display.println("Configuration:"); 322 | M5Cardputer.Display.println("SSID: " + ssid); 323 | M5Cardputer.Display.println("Password: " + password); 324 | M5Cardputer.Display.println("API Key: " + api_key); 325 | 326 | // Add a delay so the user can see the information 327 | delay(5000); 328 | } 329 | 330 | // Function to load configuration parameters from the SD card 331 | void loadConfig() { 332 | // Define the configuration file name 333 | const String configFileName = "/config.txt"; 334 | 335 | // Open the configuration file for reading 336 | File configFile = SD.open(configFileName, FILE_READ); 337 | 338 | // Check if the file is open 339 | if (configFile) { 340 | // Initialize temporary variables to hold the configuration values 341 | String line; 342 | 343 | // Read each line from the configuration file 344 | while (configFile.available()) { 345 | line = configFile.readStringUntil('\n'); 346 | 347 | // Split the line into key-value pairs using the "=" delimiter 348 | int delimiterIndex = line.indexOf('='); 349 | if (delimiterIndex != -1) { 350 | String key = line.substring(0, delimiterIndex); 351 | String value = line.substring(delimiterIndex + 1); 352 | 353 | // Trim whitespace from the key and value 354 | key.trim(); 355 | value.trim(); 356 | 357 | // Update the global variables based on the key 358 | if (key == "SSID") { 359 | ssid = value; 360 | } else if (key == "PASSWORD") { 361 | password = value; 362 | } else if (key == "API_KEY") { 363 | api_key = value; 364 | } 365 | } 366 | } 367 | 368 | // Close the configuration file 369 | configFile.close(); 370 | // Clear the canvas screen to display the message clearly 371 | M5Cardputer.Display.pushImage(0, 0, 240, 135, sd_img); 372 | 373 | // Set the cursor position to the top-left corner 374 | M5Cardputer.Display.setCursor(0, 0); 375 | 376 | // Set the text color to white for visibility 377 | M5Cardputer.Display.setTextColor(WHITE); 378 | 379 | // Print the provided debug message 380 | M5Cardputer.Display.println("Configuration loaded successfully."); 381 | 382 | // Delay for visibility 383 | delay(800); 384 | 385 | } else { 386 | // Clear the canvas screen to display the message clearly 387 | M5Cardputer.Display.pushImage(0, 0, 240, 135, error); 388 | 389 | // Set the cursor position to the top-left corner 390 | M5Cardputer.Display.setCursor(0, 0); 391 | 392 | // Set the text color to white for visibility 393 | M5Cardputer.Display.setTextColor(WHITE); 394 | 395 | // Print the provided debug message 396 | M5Cardputer.Display.println("Failed to open config file for reading."); 397 | 398 | // Delay for visibility 399 | delay(800); 400 | 401 | } 402 | } 403 | 404 | 405 | void handleMenuSelection() { 406 | // Update the M5Cardputer state 407 | M5Cardputer.update(); 408 | M5Cardputer.Display.setTextSize(0.8); 409 | M5Cardputer.Display.setTextFont(&fonts::FreeMono9pt7b); 410 | 411 | // Check if there is a change in the keyboard state 412 | if (M5Cardputer.Keyboard.isChange()) { 413 | // Check if a key is pressed 414 | if (M5Cardputer.Keyboard.isPressed()) { 415 | // Get the keyboard state 416 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 417 | 418 | // Process each key in the status 419 | for (auto key : status.word) { 420 | // Move the selection up or down based on user input 421 | if (status.fn and key == ';') { 422 | // Move selection up 423 | selectedOption--; 424 | if (selectedOption < 0) { 425 | selectedOption = numOptions - 1; 426 | } 427 | } else if (status.fn and key == '.') { 428 | // Move selection down 429 | selectedOption++; 430 | if (selectedOption >= numOptions) { 431 | selectedOption = 0; 432 | } 433 | } 434 | } 435 | 436 | // Check if the Enter key is pressed 437 | if (status.enter) { 438 | M5Cardputer.Display.fillScreen(BLACK); 439 | // Perform the action associated with the selected option 440 | performMenuAction(selectedOption); 441 | } 442 | } 443 | } 444 | } 445 | 446 | 447 | // Function to handle user input 448 | void handleUserInput() { 449 | // Reset user input 450 | userInput = ""; 451 | 452 | // Display the input screen once 453 | M5Cardputer.Display.fillScreen(BLACK); 454 | M5Cardputer.Display.setCursor(0, 0); 455 | M5Cardputer.Display.setTextColor(WHITE); 456 | M5Cardputer.Display.println("Enter your input:"); 457 | M5Cardputer.Display.println(userInput); 458 | 459 | // Continuously process user input until Enter key is pressed 460 | while (true) { 461 | M5Cardputer.update(); 462 | 463 | if (M5Cardputer.Keyboard.isChange()) { 464 | // Check if a key is pressed 465 | if (M5Cardputer.Keyboard.isPressed()) { 466 | // Get the keyboard state 467 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 468 | 469 | // Process each key 470 | bool updateDisplay = false; 471 | for (auto key : status.word) { 472 | // Add the key to userInput 473 | userInput += key; 474 | updateDisplay = true; 475 | } 476 | 477 | // Handle enter key 478 | if (status.enter) { 479 | printDebug("You entered: " + userInput); 480 | return; 481 | } 482 | 483 | // Handle backspace key 484 | if (status.del && userInput.length() > 0) { 485 | userInput.remove(userInput.length() - 1); 486 | updateDisplay = true; 487 | } 488 | 489 | // Update the display only if there is a change in user input 490 | if (updateDisplay) { 491 | M5Cardputer.Display.fillRect(0, 20, M5Cardputer.Display.width(), M5Cardputer.Display.height() - 20, BLACK); 492 | M5Cardputer.Display.setCursor(0, 20); 493 | M5Cardputer.Display.setTextColor(WHITE); 494 | M5Cardputer.Display.println(userInput); 495 | } 496 | } 497 | } 498 | } 499 | } 500 | 501 | // Function to save data to the SD card 502 | void saveToSDCard(const String &data) { 503 | // Define the file name to save the data 504 | const String fileName = "/data.txt"; 505 | 506 | // Open the file in append mode 507 | File file = SD.open(fileName, FILE_APPEND); 508 | 509 | // Check if the file is open 510 | if (file) { 511 | // Write the data to the file 512 | file.println(data); 513 | 514 | // Close the file 515 | file.close(); 516 | 517 | printDebug("Data saved to SD card: " + data); 518 | } else { 519 | printDebug("Failed to open file for writing."); 520 | } 521 | } 522 | 523 | // Function to read data from the SD card 524 | void readFromSDCard() { 525 | // Define the file name to read the data from 526 | const String fileName = "/data.txt"; 527 | 528 | // Open the file in read mode 529 | File file = SD.open(fileName, FILE_READ); 530 | 531 | // Check if the file is open 532 | if (file) { 533 | printDebug("Reading data from SD card:"); 534 | 535 | // Clear the canvas for data display 536 | M5Cardputer.Display.fillScreen(BLACK); 537 | M5Cardputer.Display.setCursor(0, 0); 538 | M5Cardputer.Display.setTextColor(WHITE); 539 | 540 | // Read data line by line 541 | while (file.available()) { 542 | String line = file.readStringUntil('\n'); 543 | M5Cardputer.Display.println(line); 544 | } 545 | 546 | // Push the display to the screen 547 | 548 | // Close the file 549 | file.close(); 550 | } else { 551 | printDebug("Failed to open file for reading."); 552 | } 553 | delay(2000); 554 | } 555 | 556 | // Function to update configuration parameters 557 | void updateConfig() { 558 | // Clear the screen 559 | M5Cardputer.Display.fillScreen(BLACK); 560 | 561 | // Variables for user input 562 | String newSSID; 563 | String newPassword; 564 | String newAPIKey; 565 | 566 | // Prompt the user to update SSID 567 | M5Cardputer.Display.setCursor(0, 0); 568 | M5Cardputer.Display.setTextColor(WHITE); 569 | M5Cardputer.Display.println("Update SSID:"); 570 | 571 | newSSID = handleUserInputAndGetResult(); 572 | 573 | // Prompt the user to update Password 574 | M5Cardputer.Display.fillScreen(BLACK); 575 | M5Cardputer.Display.setCursor(0, 0); 576 | M5Cardputer.Display.println("Update Password:"); 577 | 578 | newPassword = handleUserInputAndGetResult(); 579 | 580 | // Prompt the user to update API Key 581 | M5Cardputer.Display.fillScreen(BLACK); 582 | M5Cardputer.Display.setCursor(0, 0); 583 | M5Cardputer.Display.println("Update API Key:"); 584 | 585 | newAPIKey = handleUserInputAndGetResult(); 586 | 587 | // Update the configuration values 588 | ssid = newSSID; 589 | password = newPassword; 590 | api_key = newAPIKey; 591 | 592 | // Save the updated configuration to the SD card 593 | saveConfig(); 594 | } 595 | 596 | // Function to handle user input and get the result 597 | String handleUserInputAndGetResult() { 598 | String inputResult = ""; 599 | 600 | // Display the input screen once 601 | //M5Cardputer.Display.fillScreen(BLACK); 602 | M5Cardputer.Display.setCursor(0, 20); 603 | M5Cardputer.Display.setTextColor(WHITE); 604 | M5Cardputer.Display.println("Enter your input:"); 605 | M5Cardputer.Display.println(inputResult); 606 | 607 | // Continuously process user input until Enter key is pressed 608 | while (true) { 609 | M5Cardputer.update(); 610 | 611 | if (M5Cardputer.Keyboard.isChange()) { 612 | // Check if a key is pressed 613 | if (M5Cardputer.Keyboard.isPressed()) { 614 | // Get the keyboard state 615 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 616 | 617 | // Process each key 618 | bool updateDisplay = false; 619 | for (auto key : status.word) { 620 | // Add the key to the inputResult 621 | inputResult += key; 622 | updateDisplay = true; 623 | } 624 | 625 | // Handle enter key 626 | if (status.enter) { 627 | return inputResult; 628 | } 629 | 630 | // Handle backspace key 631 | if (status.del && inputResult.length() > 0) { 632 | inputResult.remove(inputResult.length() - 1); 633 | updateDisplay = true; 634 | } 635 | 636 | // Update the display only if there is a change in user input 637 | if (updateDisplay) { 638 | M5Cardputer.Display.fillRect(0, 20, M5Cardputer.Display.width(), M5Cardputer.Display.height() - 20, BLACK); 639 | M5Cardputer.Display.setCursor(0, 20); 640 | M5Cardputer.Display.setTextColor(WHITE); 641 | M5Cardputer.Display.println(inputResult); 642 | } 643 | } 644 | } 645 | } 646 | } 647 | 648 | // Function to clear the SD card by deleting the data file 649 | void clearSDCard() { 650 | // Define the file name to be cleared 651 | const String fileName = "/data.txt"; 652 | 653 | // Delete the data file from the SD card 654 | if (SD.remove(fileName)) { 655 | // Display a success message if the file was deleted 656 | printDebug("SD card cleared: " + fileName + " deleted."); 657 | } else { 658 | // Display an error message if the file could not be deleted 659 | printDebug("Failed to clear SD card: " + fileName + " not found."); 660 | } 661 | } 662 | 663 | // Function to display the menu on the screen 664 | void displayMenu() { 665 | if (Menu == true) { 666 | // Clear the screen 667 | //M5Cardputer.Display.fillScreen(BLACK); 668 | 669 | // Set cursor position and text color 670 | M5Cardputer.Display.setCursor(0, 0); 671 | M5Cardputer.Display.setTextColor(WHITE); 672 | 673 | // Display each menu option 674 | for (int i = 0; i < numOptions; i++) { 675 | if (i == selectedOption) { 676 | // Highlight the currently selected option 677 | M5Cardputer.Display.setTextColor(RED); 678 | } else { 679 | // Display other options in white 680 | M5Cardputer.Display.setTextColor(WHITE); 681 | } 682 | M5Cardputer.Display.println(menuOptions[i]); 683 | } 684 | } 685 | } 686 | 687 | // Function to perform the action associated with the selected menu option 688 | void performMenuAction(int option) { 689 | // After performing the action, display the menu again 690 | displayMenu(); 691 | // Handle the selected option 692 | switch (option) { 693 | case 0: // Option 1: Enter Input 694 | handleUserInput(); 695 | if (userInput.isEmpty()) { 696 | printDebug("No input to save."); 697 | } else { 698 | saveToSDCard(userInput); 699 | userInput = ""; 700 | } 701 | break; 702 | case 1: // Option 2: Read From SD Card 703 | readFromSDCard(); 704 | displayConfig(); 705 | break; 706 | case 2: // Option 3: Update Config 707 | updateConfig(); 708 | break; 709 | case 3: // Option 4: Clear SD Card 710 | clearSDCard(); 711 | break; 712 | case 4: // Option 5: 713 | printDebug("Exiting menu..."); 714 | Menu = false; 715 | M5Cardputer.Display.fillScreen(BLACK); 716 | break; 717 | default: 718 | printDebug("Invalid option selected."); 719 | break; 720 | } 721 | M5Cardputer.Display.fillScreen(BLACK); 722 | } 723 | 724 | // Function to print debug output on the screen 725 | void printDebug(const String &message) { 726 | // Clear the canvas screen to display the message clearly 727 | M5Cardputer.Display.fillScreen(BLACK); 728 | 729 | // Set the cursor position to the top-left corner 730 | M5Cardputer.Display.setCursor(0, 0); 731 | 732 | // Set the text color to white for visibility 733 | M5Cardputer.Display.setTextColor(WHITE); 734 | 735 | // Print the provided debug message 736 | M5Cardputer.Display.println(message); 737 | 738 | // Delay for visibility 739 | delay(800); 740 | } 741 | 742 | // Function to save configuration parameters to the SD card 743 | void saveConfig() { 744 | // Define the configuration file name 745 | const String configFileName = "/config.txt"; 746 | 747 | // Open the configuration file for writing 748 | File configFile = SD.open(configFileName, FILE_WRITE); 749 | 750 | // Check if the file is open 751 | if (configFile) { 752 | // Write the configuration parameters to the file 753 | configFile.println("SSID=" + ssid); 754 | configFile.println("PASSWORD=" + password); 755 | configFile.println("API_KEY=" + api_key); 756 | 757 | // Close the configuration file 758 | configFile.close(); 759 | 760 | printDebug("Configuration updated successfully."); 761 | } else { 762 | printDebug("Failed to open config file for writing."); 763 | } 764 | } 765 | 766 | // Function to connect to Wi-Fi 767 | void connectToWiFi() { 768 | M5Cardputer.Display.pushImage(0, 0, 240, 135, wifi_img); 769 | M5Cardputer.Display.setTextColor(YELLOW); 770 | M5Cardputer.Display.setCursor(0, 0); 771 | M5Cardputer.Display.println("Connecting to Wi-Fi..."); 772 | 773 | WiFi.begin(ssid.c_str(), password.c_str()); 774 | int attempts = 0; 775 | const int maxAttempts = 10; 776 | 777 | while (WiFi.status() != WL_CONNECTED) { 778 | delay(1000); 779 | M5Cardputer.Display.print("."); 780 | attempts++; 781 | 782 | if (attempts >= maxAttempts) { 783 | M5Cardputer.Display.setCursor(0, 0); 784 | M5Cardputer.Display.setTextColor(RED); 785 | M5Cardputer.Display.pushImage(0, 0, 240, 135, error); 786 | M5Cardputer.Display.println("\nFailed to connect to Wi-Fi, switching to menu..."); 787 | delay(2000); 788 | M5Cardputer.Display.fillScreen(BLACK); 789 | Menu = true; 790 | ConfigMenu(); 791 | M5Cardputer.Display.setCursor(0, 0); 792 | M5Cardputer.Display.pushImage(0, 0, 240, 135, bg); 793 | 794 | M5Cardputer.Display.setTextColor(RED); 795 | 796 | M5Cardputer.Display.println("\nRestarting..."); 797 | delay(2000); 798 | ESP.restart(); 799 | } 800 | } 801 | 802 | M5Cardputer.Display.println("\nWi-Fi connected"); 803 | M5Cardputer.Display.println("\nIP: " + WiFi.localIP().toString()); 804 | delay(1000); 805 | M5Cardputer.Display.fillScreen(BLACK); 806 | } 807 | 808 | 809 | void setup() { 810 | // Initialize the M5Cardputer 811 | M5Cardputer.begin(); 812 | M5Cardputer.Display.setSwapBytes(true); 813 | // Set display rotation and text size 814 | M5Cardputer.Display.setRotation(1); 815 | M5Cardputer.Display.pushImage(0, 0, 240, 135, remixed); // Render the image at the origin (0, 0) 816 | delay(800); 817 | 818 | 819 | M5Cardputer.Display.setTextSize(0.9); 820 | M5Cardputer.Display.setTextFont(&fonts::FreeMono9pt7b); 821 | 822 | M5Cardputer.Display.pushImage(0, 0, 240, 135, bg); 823 | M5Cardputer.Display.setCursor(0, 0); 824 | M5Cardputer.Display.setTextColor(ORANGE); 825 | M5Cardputer.Display.println("Keys:"); 826 | M5Cardputer.Display.println("UP = Fn + ;"); 827 | M5Cardputer.Display.println("DOWN = Fn + ."); 828 | M5Cardputer.Display.println("Menu = Opt"); 829 | delay(2000); 830 | 831 | M5Cardputer.Display.pushImage(0, 0, 240, 135, sd_img); 832 | delay(800); 833 | // Initialize the SD card 834 | if (initializeSDCard()) { 835 | // Clear the canvas screen to display the message clearly 836 | M5Cardputer.Display.pushImage(0, 0, 240, 135, sd_img); 837 | 838 | // Set the cursor position to the top-left corner 839 | M5Cardputer.Display.setCursor(0, 0); 840 | 841 | // Set the text color to white for visibility 842 | M5Cardputer.Display.setTextColor(WHITE); 843 | 844 | // Print the provided debug message 845 | M5Cardputer.Display.println("Setup completed."); 846 | 847 | // Delay for visibility 848 | delay(800); 849 | 850 | loadConfig(); 851 | } else { 852 | // Clear the canvas screen to display the message clearly 853 | M5Cardputer.Display.pushImage(0, 0, 240, 135, error); 854 | 855 | // Set the cursor position to the top-left corner 856 | M5Cardputer.Display.setCursor(0, 0); 857 | 858 | // Set the text color to white for visibility 859 | M5Cardputer.Display.setTextColor(WHITE); 860 | 861 | // Print the provided debug message 862 | M5Cardputer.Display.println("Setup failed."); 863 | 864 | // Delay for visibility 865 | delay(800); 866 | 867 | M5Cardputer.Display.fillScreen(BLACK); 868 | Menu = true; 869 | ConfigMenu(); 870 | } 871 | M5Cardputer.Display.pushImage(0, 0, 240, 135, wifi_img); 872 | delay(2000); 873 | connectToWiFi(); 874 | M5Cardputer.Display.fillScreen(BLACK); 875 | displayText("> ", scrollPositionDOWN, 0, true); 876 | 877 | // Display the initial menu 878 | } 879 | 880 | void ConfigMenu() { 881 | while (Menu == true) { 882 | // Handle menu selection 883 | handleMenuSelection(); 884 | 885 | // Display the menu 886 | displayMenu(); 887 | } 888 | } 889 | 890 | 891 | // Function to handle backspace and update user input display 892 | void handleBackspace() { 893 | 894 | // Check if there is text to delete 895 | if (userInput.length() > 0) { // "> " length is 2 896 | parseText(userInput); 897 | int BeforeDelete = numLines; 898 | // Remove the last character from userInput 899 | userInput.remove(userInput.length() - 1); 900 | parseText(userInput); 901 | int AfterDelete = numLines; 902 | if (BeforeDelete == AfterDelete) { 903 | displayText(userInput, scrollPositionDOWN, 0, true); 904 | } else { 905 | displayText(userInput, scrollPositionDOWN, -1, true); 906 | } 907 | } 908 | } 909 | 910 | 911 | 912 | void loop() { 913 | if (Menu == true) { 914 | ConfigMenu(); 915 | } 916 | // Update the M5Cardputer state 917 | M5Cardputer.update(); 918 | 919 | // Handle user input from the keyboard 920 | if (M5Cardputer.Keyboard.isChange()) { 921 | if (M5Cardputer.Keyboard.isPressed()) { 922 | 923 | // Get the keyboard state 924 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 925 | 926 | // Process each key in the status 927 | for (auto key : status.word) { 928 | if (!(status.fn or status.ctrl)) { //somehow status.ctrl is not working in the loop , but is working outside ? 929 | userInput += key; 930 | displayText(userInput, scrollPositionDOWN, +1, true); // Only append key if neither fn nor ctrl is pressed 931 | } 932 | 933 | if (status.fn and key == ';') { 934 | displayText(geminiResponse, scrollPosition, -1, false); 935 | } 936 | 937 | if (status.fn and key == '.') { 938 | 939 | displayText(geminiResponse, scrollPosition, +1, false); 940 | 941 | if (status.ctrl and key == ',' ) { 942 | 943 | displayText(userInput, scrollPositionDOWN, -1, true); 944 | } 945 | 946 | if (status.ctrl and key == '/') { 947 | 948 | displayText(userInput, scrollPositionDOWN, +1, true); 949 | } 950 | } 951 | } 952 | 953 | 954 | 955 | // Handle backspace key 956 | if (status.del) { 957 | handleBackspace(); 958 | } 959 | 960 | if (status.opt) { 961 | Menu = true; 962 | M5Cardputer.Display.fillScreen(BLACK); 963 | } 964 | 965 | // Handle enter key (submit input) 966 | if (status.enter) { 967 | 968 | // Remove the initial prompt from the input 969 | //String inputWithoutPrompt = userInput.substring(2); 970 | String inputWithoutPrompt = userInput; 971 | 972 | // Clear the display to prepare for the response 973 | M5Cardputer.Display.setTextSize(1.2); 974 | M5Cardputer.Display.setTextFont(&fonts::Orbitron_Light_24); 975 | 976 | // Clear the display 977 | M5Cardputer.Display.pushImage(0, 0, 240, 135, generate); 978 | 979 | 980 | // Send a request to the Gemini API with the user input (without prompt) 981 | if (!sendGeminiRequest(inputWithoutPrompt)) { 982 | // If the request fails, retry up to the specified number of times 983 | for (int i = 0; i < retryAttempts; i++) { 984 | if (sendGeminiRequest(inputWithoutPrompt)) { 985 | break; 986 | } 987 | } 988 | } 989 | 990 | // Reset the user input prompt after handling the request 991 | userInput = ""; 992 | scrollPosition = 0; 993 | scrollPositionDOWN = 0; 994 | } 995 | } 996 | } 997 | } 998 | 999 | 1000 | // Function to send a request to the Gemini API 1001 | bool sendGeminiRequest(const String &input) { 1002 | // Construct the Gemini API URL with the API key 1003 | String url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=" + String(api_key); 1004 | 1005 | // Create an HTTP client 1006 | HTTPClient http; 1007 | http.begin(url); 1008 | http.addHeader("Content-Type", "application/json"); 1009 | 1010 | // Create the JSON request body 1011 | DynamicJsonDocument requestBody(1024); 1012 | 1013 | // Add prefix "Keep Responses very short and to the point" to the initial input 1014 | static bool isFirstRequest = true; 1015 | String requestText = input; 1016 | if (isFirstRequest) { 1017 | requestText = "Keep Responses very short and to the point: " + requestText; 1018 | isFirstRequest = false; // After the first request, do not prefix anymore 1019 | } 1020 | 1021 | // Fill the request body with the user's request text 1022 | requestBody["contents"][0]["role"] = "user"; 1023 | requestBody["contents"][0]["parts"][0]["text"] = requestText; 1024 | 1025 | // Convert JSON document to a string 1026 | String requestBodyStr; 1027 | serializeJson(requestBody, requestBodyStr); 1028 | 1029 | // Send HTTP POST request 1030 | int httpResponseCode = http.POST(requestBodyStr); 1031 | if (httpResponseCode == HTTP_CODE_OK) { 1032 | // Get the response 1033 | String response = http.getString(); 1034 | 1035 | // Process the Gemini response 1036 | processGeminiResponse(response); 1037 | 1038 | // End the HTTP connection 1039 | http.end(); 1040 | return true; // Request succeeded 1041 | } else { 1042 | // Display an HTTP error 1043 | 1044 | M5Cardputer.Display.setTextSize(0.9); 1045 | M5Cardputer.Display.setTextFont(&fonts::FreeMono9pt7b); 1046 | M5Cardputer.Display.setCursor(0, 0); 1047 | M5Cardputer.Display.fillScreen(BLACK); 1048 | M5Cardputer.Display.setTextColor(RED); 1049 | M5Cardputer.Display.println("\nHTTP error: " + String(httpResponseCode)); 1050 | M5Cardputer.Display.println("Error: " + http.errorToString(httpResponseCode)); 1051 | displayText("> ", scrollPositionDOWN, 0, true); 1052 | 1053 | 1054 | 1055 | // End the HTTP connection 1056 | http.end(); 1057 | return false; // Request failed 1058 | } 1059 | } 1060 | 1061 | // Function to process the Gemini response 1062 | void processGeminiResponse(const String &response) { 1063 | // Create a JSON document to hold the response 1064 | DynamicJsonDocument responseDoc(2048); 1065 | deserializeJson(responseDoc, response); 1066 | 1067 | // Extract the Gemini API response 1068 | geminiResponse = responseDoc["candidates"][0]["content"]["parts"][0]["text"].as(); 1069 | 1070 | M5Cardputer.Display.fillScreen(BLACK); 1071 | // Display the Gemini API response at the top of the screen 1072 | displayText(geminiResponse, scrollPosition, 0, false); 1073 | } 1074 | -------------------------------------------------------------------------------- /Gemini/Gemini.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* 5 | M5Cardputer Chat with Gemini API 6 | 7 | @date: 2024-05-08 8 | @time: 1:34 pm 9 | @author: @vanshksingh 10 | @github: https://github.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/ 11 | Description: 12 | This code enables an M5Cardputer device to interact with the Gemini API 13 | for generating responses based on user input. The user inputs queries 14 | through the device, and the program sends these queries to the Gemini API 15 | and displays the responses received from the API. The code manages the 16 | display, handles user input and API requests, and includes functionality 17 | for connecting to Wi-Fi and reading configuration data from an SD card. 18 | 19 | Key Components: 20 | - M5Cardputer: An ESP32-based development board that includes a display, keyboard, 21 | and other components. 22 | - WiFi: Used for connecting the device to the internet to communicate with the Gemini API. 23 | - HTTPClient: Used for sending HTTP requests to the Gemini API and receiving responses. 24 | - ArduinoJson: Library for parsing and creating JSON data. 25 | - SD: Library for interacting with an SD card for reading configuration data. 26 | - FS: Filesystem library for managing the SD card. 27 | - SPI: Serial Peripheral Interface library for initializing the SD card interface. 28 | 29 | Key Global Variables: 30 | - userInput: Holds the current user input including the prompt. 31 | - geminiResponse: Holds the response received from the Gemini API. 32 | - retryAttempts: Number of retry attempts allowed for a failed API request. 33 | - scrollingEnabled: Boolean flag indicating whether scrolling is enabled for long responses. 34 | - inputLines: Array holding the history of user inputs for display. 35 | - currentLineIndex: Index indicating the current position in the input history. 36 | - ssid, password, api_key: Wi-Fi credentials and API key loaded from the config file. 37 | 38 | Functions: 39 | - setup(): Initializes the M5Cardputer, connects to Wi-Fi, and loads configuration data from an SD card. 40 | - loadConfig(): Reads and parses the configuration file from the SD card. 41 | - connectToWiFi(): Connects the device to the specified Wi-Fi network. 42 | - displayUserInput(): Displays user input on the screen, handling line wrapping and scrolling. 43 | - handleBackspace(): Handles backspace key input and updates the displayed user input. 44 | - displayGeminiResponse(): Displays the response from the Gemini API on the screen. 45 | - sendGeminiRequest(const String& input): Sends a request to the Gemini API with the user's input and handles the response. 46 | - processGeminiResponse(const String& response): Parses the response from the Gemini API and displays it on the screen. 47 | 48 | In the main loop, the program continuously checks for keyboard input and handles different 49 | key actions such as adding to the user input, backspacing, and submitting the user input 50 | to the Gemini API. The response from the API is processed and displayed to the user. 51 | */ 52 | 53 | 54 | 55 | #include "M5Cardputer.h" 56 | #include "M5GFX.h" 57 | #include "WiFi.h" 58 | #include "HTTPClient.h" 59 | #include "ArduinoJson.h" 60 | #include "SD.h" 61 | #include "FS.h" 62 | #include "SD.h" 63 | #include "SPI.h" 64 | #include 65 | 66 | 67 | #define SD_SCK GPIO_NUM_40 68 | #define SD_MISO GPIO_NUM_39 69 | #define SD_MOSI GPIO_NUM_14 70 | #define SD_SS GPIO_NUM_12 71 | #define SD_SPI_FREQ 1000000 72 | 73 | #define PIN_LED 21 74 | #define NUM_LEDS 1 75 | CRGB leds[NUM_LEDS]; 76 | uint8_t led_ih = 0; 77 | uint8_t led_status = 0; 78 | 79 | 80 | SPIClass hspi = SPIClass(HSPI); 81 | 82 | // Global variables 83 | M5Canvas canvas(&M5Cardputer.Display); 84 | String userInput = "> "; // Initial prompt 85 | String geminiResponse = ""; // Response from Gemini model 86 | int retryAttempts = 5; // Number of retry attempts on request failure 87 | bool scrollingEnabled = false; // Flag for enabling scrolling 88 | const int maxLines = 10; // Maximum number of lines to display 89 | String inputLines[maxLines]; // Array to hold each line of input 90 | int currentLineIndex = 0; // Index of the current line in the array 91 | String ssid, password, api_key; // Variables for SSID, password, and API key 92 | 93 | // Setup function 94 | void setup() { 95 | // Initialize M5Cardputer 96 | M5Cardputer.begin(); 97 | 98 | FastLED.addLeds(leds, NUM_LEDS); 99 | 100 | leds[0] = CRGB::Cyan; 101 | FastLED.show(); 102 | 103 | // Set display rotation and text size 104 | M5Cardputer.Display.setRotation(1); 105 | M5Cardputer.Display.setTextSize(0.1); 106 | M5Cardputer.Display.println("/n "); 107 | M5Cardputer.Display.setTextSize(1.4); 108 | M5Cardputer.Display.setTextFont(&fonts::Orbitron_Light_24); 109 | 110 | // Clear the display 111 | M5Cardputer.Display.fillScreen(BLACK); 112 | M5Cardputer.Display.setTextColor(CYAN); 113 | M5Cardputer.Display.println(" Gemini"); 114 | M5Cardputer.Display.setTextSize(0.7); 115 | M5Cardputer.Display.setTextFont(&fonts::Yellowtail_32); 116 | M5Cardputer.Display.setTextColor(ORANGE); 117 | M5Cardputer.Display.println(" Remixed!"); 118 | M5Cardputer.Display.setTextSize(0.5); 119 | M5Cardputer.Display.setTextFont(&fonts::Yellowtail_32); 120 | M5Cardputer.Display.setTextColor(BLUE); 121 | M5Cardputer.Display.println(" @vanshksingh"); 122 | 123 | delay(2000); 124 | M5Cardputer.Display.setTextSize(0.9); 125 | M5Cardputer.Display.fillScreen(BLACK); 126 | M5Cardputer.Display.setTextFont(&fonts::FreeMono9pt7b); 127 | 128 | hspi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS); 129 | 130 | // Initialize the SD card using the specified chip select pin 131 | if (!SD.begin(SD_SS, hspi, SD_SPI_FREQ)) { 132 | leds[0] = CRGB::Red; 133 | FastLED.show(); 134 | M5Cardputer.Display.setTextColor(RED); 135 | M5Cardputer.Display.setCursor(2, 0); 136 | M5Cardputer.Display.println(" SD Card Init Failed!"); 137 | delay(2000); 138 | ESP.restart(); 139 | } else { 140 | leds[0] = CRGB::Gold; 141 | FastLED.show(); 142 | M5Cardputer.Display.setTextColor(YELLOW); 143 | M5Cardputer.Display.setCursor(2, 0); 144 | M5Cardputer.Display.println(" SD Card Initialized!"); 145 | delay(1000); 146 | 147 | // Load the configuration from the config file on the SD card 148 | if (loadConfig()) { 149 | // Connect to Wi-Fi 150 | connectToWiFi(); 151 | } else { 152 | leds[0] = CRGB::Violet; 153 | FastLED.show(); 154 | M5Cardputer.Display.setTextColor(RED); 155 | M5Cardputer.Display.println("\nFailed to load config file."); 156 | delay(2000); 157 | ESP.restart(); 158 | } 159 | } 160 | 161 | // Display the initial prompt for user input 162 | displayUserInput(); 163 | } 164 | 165 | 166 | // Function to load configuration data from the config file on the SD card 167 | bool loadConfig() { 168 | // Open the configuration file on the SD card 169 | File configFile = SD.open("/config.txt", FILE_READ); 170 | if (!configFile) { 171 | return false; 172 | } 173 | 174 | // Read the configuration file line by line 175 | while (configFile.available()) { 176 | // Read each line from the file 177 | String line = configFile.readStringUntil('\n'); 178 | // Trim the line to remove any surrounding whitespace 179 | line.trim(); 180 | 181 | // Parse the line to extract the parameter and value 182 | int delimiterIndex = line.indexOf('='); 183 | if (delimiterIndex != -1) { 184 | // Extract the parameter and value 185 | String parameter = line.substring(0, delimiterIndex); 186 | String value = line.substring(delimiterIndex + 1); 187 | 188 | // Trim the parameter and value separately 189 | parameter.trim(); 190 | value.trim(); 191 | 192 | // Assign the values to the appropriate variables 193 | if (parameter == "SSID") { 194 | ssid = value; 195 | } else if (parameter == "PASSWORD") { 196 | password = value; 197 | } else if (parameter == "API_KEY") { 198 | api_key = value; 199 | } 200 | } 201 | } 202 | 203 | // Close the configuration file 204 | configFile.close(); 205 | return true; 206 | } 207 | 208 | 209 | 210 | // Function to connect to Wi-Fi 211 | void connectToWiFi() { 212 | leds[0] = CRGB::Orange; 213 | FastLED.show(); 214 | M5Cardputer.Display.fillScreen(BLACK); 215 | M5Cardputer.Display.setTextColor(YELLOW); 216 | M5Cardputer.Display.setCursor(0, 0); 217 | M5Cardputer.Display.println("Connecting to Wi-Fi..."); 218 | 219 | WiFi.begin(ssid.c_str(), password.c_str()); 220 | int attempts = 0; 221 | const int maxAttempts = 10; 222 | 223 | while (WiFi.status() != WL_CONNECTED) { 224 | delay(1000); 225 | M5Cardputer.Display.print("."); 226 | attempts++; 227 | 228 | if (attempts >= maxAttempts) { 229 | 230 | M5Cardputer.Display.setTextColor(RED); 231 | leds[0] = CRGB::Orange; 232 | FastLED.show(); 233 | M5Cardputer.Display.println("\nFailed to connect to Wi-Fi, restarting..."); 234 | delay(2000); 235 | ESP.restart(); 236 | } 237 | } 238 | leds[0] = CRGB::Green; 239 | FastLED.show(); 240 | M5Cardputer.Display.println("\nWi-Fi connected"); 241 | M5Cardputer.Display.println("\nIP: " + WiFi.localIP().toString()); 242 | delay(1000); 243 | M5Cardputer.Display.fillScreen(BLACK); 244 | } 245 | 246 | // Continue with the rest of your program such as displayUserInput(), handleBackspace(), sendGeminiRequest(), processGeminiResponse(), and loop(). 247 | 248 | 249 | // Function to handle backspace and update user input display 250 | void handleBackspace() { 251 | // Check if there is text to delete 252 | if (userInput.length() > 2) { // "> " length is 2 253 | // Remove the last character from userInput 254 | userInput.remove(userInput.length() - 1); 255 | M5Cardputer.Display.fillScreen(BLACK); 256 | 257 | // Display updated user input 258 | displayUserInput(); 259 | } 260 | } 261 | 262 | // Function to handle user input and display by wrapping and pushing lines up 263 | void displayUserInput() { 264 | // Calculate the height of one line of text 265 | int inputHeight = 16; // Adjust if necessary 266 | 267 | // Calculate the bottom line Y position of the display 268 | int bottomLineY = M5Cardputer.Display.height() - inputHeight; 269 | 270 | // Calculate the width of the user's input text 271 | int textWidth = M5Cardputer.Display.textWidth(userInput); 272 | 273 | // Check if the input text width exceeds the display width 274 | if (textWidth > M5Cardputer.Display.width()) { 275 | // If the array is full, push existing lines up 276 | if (currentLineIndex >= maxLines) { 277 | for (int i = 0; i < maxLines - 1; i++) { 278 | inputLines[i] = inputLines[i + 1]; 279 | } 280 | } else { 281 | currentLineIndex++; 282 | } 283 | 284 | // Add the current user input to the last line of the array 285 | inputLines[currentLineIndex] = userInput; 286 | 287 | // Clear the display 288 | M5Cardputer.Display.fillScreen(BLACK); 289 | 290 | // Display all lines of input from the array 291 | for (int i = 0; i <= currentLineIndex; i++) { 292 | // Calculate the Y position for each line of text 293 | int lineY = bottomLineY - (inputHeight * i); 294 | 295 | // Set text color and cursor position 296 | M5Cardputer.Display.setTextColor(GREENYELLOW); 297 | M5Cardputer.Display.setCursor(0, lineY); 298 | 299 | // Print the line of input 300 | M5Cardputer.Display.print(inputLines[i]); 301 | } 302 | 303 | // Reset the user input to an initial prompt for new input 304 | userInput = "> "; 305 | } else { 306 | // If the input text width does not exceed the display width, display it on the bottom line 307 | M5Cardputer.Display.setTextColor(GREENYELLOW); 308 | M5Cardputer.Display.setCursor(0, bottomLineY); 309 | M5Cardputer.Display.print(userInput); 310 | leds[0] = CRGB::SkyBlue; 311 | FastLED.show(); 312 | } 313 | } 314 | 315 | // Function to handle the response from the Gemini API 316 | void displayGeminiResponse() { 317 | // Clear the line where the Gemini response is displayed 318 | M5Cardputer.Display.fillRect(0, 0, M5Cardputer.Display.width(), 16, BLACK); 319 | 320 | // Display the Gemini response in a distinct color 321 | M5Cardputer.Display.setTextColor(WHITE); 322 | M5Cardputer.Display.setCursor(0, 0); 323 | 324 | // Enable scrolling if the response is too long 325 | if (geminiResponse.length() > M5Cardputer.Display.width()) { 326 | scrollingEnabled = true; 327 | canvas.setTextScroll(true); 328 | canvas.createSprite(M5Cardputer.Display.width(), M5Cardputer.Display.height() - 36); 329 | canvas.fillScreen(BLACK); 330 | canvas.setTextColor(WHITE); 331 | canvas.println("Response: " + geminiResponse); 332 | canvas.pushSprite(0, 0); 333 | leds[0] = CRGB::White; 334 | FastLED.show(); 335 | } else { 336 | leds[0] = CRGB::Gray; 337 | FastLED.show(); 338 | M5Cardputer.Display.print("Response: " + geminiResponse); 339 | delay(1000); 340 | } 341 | } 342 | 343 | // Function to handle user input and API response 344 | void loop() { 345 | // Update the M5Cardputer state 346 | M5Cardputer.update(); 347 | 348 | // Handle user input from the keyboard 349 | if (M5Cardputer.Keyboard.isChange()) { 350 | if (M5Cardputer.Keyboard.isPressed()) { 351 | leds[0] = CRGB::SeaGreen; 352 | FastLED.show(); 353 | // Get the keyboard state 354 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 355 | 356 | // Process each key in the status 357 | for (auto key : status.word) { 358 | userInput += key; 359 | } 360 | 361 | // Handle backspace key 362 | if (status.del) { 363 | handleBackspace(); 364 | leds[0] = CRGB::IndianRed; 365 | FastLED.show(); 366 | } 367 | 368 | // Handle enter key (submit input) 369 | if (status.enter) { 370 | leds[0] = CRGB::PaleTurquoise; 371 | FastLED.show(); 372 | // Remove the initial prompt from the input 373 | String inputWithoutPrompt = userInput.substring(2); 374 | 375 | // Clear the display to prepare for the response 376 | M5Cardputer.Display.setTextSize(1.2); 377 | M5Cardputer.Display.setTextFont(&fonts::Orbitron_Light_24); 378 | 379 | // Clear the display 380 | leds[0] = CRGB::DarkBlue; 381 | FastLED.show(); 382 | M5Cardputer.Display.fillScreen(BLACK); 383 | M5Cardputer.Display.setTextColor(BLUE); 384 | M5Cardputer.Display.setCursor(0, 0); 385 | M5Cardputer.Display.println("Generating"); 386 | M5Cardputer.Display.println("Response..."); 387 | M5Cardputer.Display.setTextSize(0.9); 388 | M5Cardputer.Display.setTextFont(&fonts::FreeMono9pt7b); 389 | 390 | // Send a request to the Gemini API with the user input (without prompt) 391 | if (!sendGeminiRequest(inputWithoutPrompt)) { 392 | // If the request fails, retry up to the specified number of times 393 | for (int i = 0; i < retryAttempts; i++) { 394 | if (sendGeminiRequest(inputWithoutPrompt)) { 395 | break; 396 | } 397 | } 398 | } 399 | 400 | // Reset the user input prompt after handling the request 401 | userInput = "> "; 402 | 403 | // Display the user input prompt 404 | displayUserInput(); 405 | } 406 | 407 | // Display the current user input 408 | displayUserInput(); 409 | } 410 | } 411 | } 412 | 413 | // Function to send a request to the Gemini API 414 | bool sendGeminiRequest(const String& input) { 415 | // Construct the Gemini API URL with the API key 416 | String url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=" + String(api_key); 417 | 418 | // Create an HTTP client 419 | HTTPClient http; 420 | http.begin(url); 421 | http.addHeader("Content-Type", "application/json"); 422 | 423 | // Create the JSON request body 424 | DynamicJsonDocument requestBody(1024); 425 | 426 | // Add prefix "Keep Responses very short and to the point" to the initial input 427 | static bool isFirstRequest = true; 428 | String requestText = input; 429 | if (isFirstRequest) { 430 | requestText = "Keep Responses very short and to the point: " + requestText; 431 | isFirstRequest = false; // After the first request, do not prefix anymore 432 | } 433 | 434 | // Fill the request body with the user's request text 435 | requestBody["contents"][0]["role"] = "user"; 436 | requestBody["contents"][0]["parts"][0]["text"] = requestText; 437 | 438 | // Convert JSON document to a string 439 | String requestBodyStr; 440 | serializeJson(requestBody, requestBodyStr); 441 | 442 | // Send HTTP POST request 443 | int httpResponseCode = http.POST(requestBodyStr); 444 | if (httpResponseCode == HTTP_CODE_OK) { 445 | // Get the response 446 | String response = http.getString(); 447 | 448 | // Process the Gemini response 449 | processGeminiResponse(response); 450 | 451 | // End the HTTP connection 452 | http.end(); 453 | return true; // Request succeeded 454 | } else { 455 | // Display an HTTP error 456 | M5Cardputer.Display.setTextColor(RED); 457 | M5Cardputer.Display.print("\nHTTP error: " + String(httpResponseCode)); 458 | 459 | // End the HTTP connection 460 | http.end(); 461 | return false; // Request failed 462 | } 463 | } 464 | 465 | // Function to process the Gemini response 466 | void processGeminiResponse(const String& response) { 467 | // Create a JSON document to hold the response 468 | DynamicJsonDocument responseDoc(2048); 469 | deserializeJson(responseDoc, response); 470 | 471 | // Extract the Gemini API response 472 | geminiResponse = responseDoc["candidates"][0]["content"]["parts"][0]["text"].as(); 473 | 474 | M5Cardputer.Display.fillScreen(BLACK); 475 | // Display the Gemini API response at the top of the screen 476 | displayGeminiResponse(); 477 | } 478 | -------------------------------------------------------------------------------- /Gemini/config.txt: -------------------------------------------------------------------------------- 1 | SSID=your_SSID 2 | PASSWORD=your_pass 3 | API_KEY=your_key 4 | 5 | -------------------------------------------------------------------------------- /Not used/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/225362b9069b986c97db6f61da2a23191e619418/Not used/bg.png -------------------------------------------------------------------------------- /Not used/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/225362b9069b986c97db6f61da2a23191e619418/Not used/error.png -------------------------------------------------------------------------------- /Not used/generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/225362b9069b986c97db6f61da2a23191e619418/Not used/generate.png -------------------------------------------------------------------------------- /Not used/remixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/225362b9069b986c97db6f61da2a23191e619418/Not used/remixed.png -------------------------------------------------------------------------------- /Not used/sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/225362b9069b986c97db6f61da2a23191e619418/Not used/sd.png -------------------------------------------------------------------------------- /Not used/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/225362b9069b986c97db6f61da2a23191e619418/Not used/wifi.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5Cardputer Chat with Gemini API 2 | 3 | 4 | ![IMG_4196](https://github.com/vanshksingh/M5Cardputer-Chat-with-Gemini-API/assets/114809624/6dde1464-cba1-4e41-a0d2-7bf3ffa5e925) 5 | 6 | 7 | 8 | An application that uses M5Cardputer to interact with the Gemini API for generating responses based on user input. This project includes code for managing Wi-Fi connectivity, handling user input, making API requests, and displaying responses on the M5Cardputer's screen. 9 | 10 | ## Features 11 | 12 | - **User Input Handling**: Accepts user input through the M5Cardputer keyboard. 13 | - **API Integration**: Sends queries to the Gemini API and processes the responses. 14 | - **Display Management**: Manages the display of user input and responses. 15 | - **Wi-Fi Connectivity**: Connects to the specified Wi-Fi network for API communication. 16 | - **Configuration**: Reads configuration data such as Wi-Fi credentials and API key from an SD card. 17 | 18 | ## Usage 19 | 1. Insert SD Card 20 | 21 | 2. Follow On-screen instructions. 22 | 23 | 3. Get API key From https://aistudio.google.com/app/apikey 24 | 25 | 4. Type your queries using the M5Cardputer keyboard, and the application will send them to the Gemini API. 26 | 27 | 5. The responses from the Gemini API will be displayed on the M5Cardputer screen. 28 | 29 | 30 | ## In-Development 31 | 32 | 1. Re-integrate Fastled implementation 33 | 2. Clean code 34 | 3. Remove Print Debug function 35 | 4. Save Response to SD card 36 | 5. Voice to text input 37 | 6. Text to voice 38 | 7. Button to switch voice modes 39 | 40 | 41 | 42 | 43 | 44 | ## Create .bin 45 | https://www.reddit.com/r/CardPuter/comments/1aoka55/finally_i_manage_to_compile_m5nemo_from_source/ 46 | 47 | 48 | 49 | ## Contributing 50 | 51 | Contributions are welcome! If you'd like to contribute to this project, please fork the repository and create a pull request. 52 | 53 | ## License 54 | 55 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 56 | 57 | ## Author 58 | 59 | - **@vanshksingh**: [GitHub](https://github.com/vanshksingh) 60 | --------------------------------------------------------------------------------