├── .gitignore ├── LICENSE.txt ├── README.md ├── esp8266-rc-car └── esp8266-rc-car.ino └── wifi_controller_gui.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Indrek Ots 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Control an RC car with ESP8266 over WiFi 2 | 3 | This repository contains code to control an RC car over WiFi using an ESP8266 board. For more information head over to [this blog post](http://blog.indrek.io/articles/how-to-control-an-rc-car-over-wifi-with-esp8266/) to see how to set up an RC car. 4 | 5 | ## Requirements 6 | 7 | * Python 3 8 | * Arduino IDE 9 | 10 | ## Example usage 11 | 12 | Open `esp8266-rc-car` with your Arduino IDE and upload the sketch to your Arduino board. Make sure to enter your WiFi SSID and password in the header of the file. 13 | 14 | ```cpp 15 | const char* ssid = "SSID"; //Enter your wifi network SSID 16 | const char* password = "PASSWORD"; //Enter your wifi network password 17 | ``` 18 | 19 | Open your terminal and run the python script with 20 | 21 | ```bash 22 | python wifi_controller_gui.py --host= 23 | ``` 24 | 25 | Press W, A, S, D keys to control your RC car. 26 | -------------------------------------------------------------------------------- /esp8266-rc-car/esp8266-rc-car.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const char* ssid = "SSID"; //Enter your wifi network SSID 5 | const char* password = "PASSWORD"; //Enter your wifi network password 6 | 7 | const int FORWARDS = 5; 8 | const int BACKWARDS = 0; 9 | const int RIGHT = 4; 10 | const int LEFT = 13; 11 | 12 | const int SERVER_PORT = 1111; 13 | const int BAUD_RATE = 9600; 14 | 15 | byte incomingByte = 0; 16 | 17 | bool forwardsPressed = false; 18 | bool backwardsPressed = false; 19 | bool rightPressed = false; 20 | bool leftPressed = false; 21 | 22 | const int FORWARDS_PRESSED = 1; 23 | const int FORWARDS_RELEASED = 2; 24 | const int BACKWARDS_PRESSED = 3; 25 | const int BACKWARDS_RELEASED = 4; 26 | const int RIGHT_PRESSED = 5; 27 | const int RIGHT_RELEASED = 6; 28 | const int LEFT_PRESSED = 7; 29 | const int LEFT_RELEASED = 8; 30 | 31 | byte packetBuffer[512]; 32 | 33 | WiFiUDP Udp; 34 | 35 | void initOutputs() { 36 | pinMode(FORWARDS, OUTPUT); 37 | pinMode(BACKWARDS, OUTPUT); 38 | pinMode(RIGHT, OUTPUT); 39 | pinMode(LEFT, OUTPUT); 40 | } 41 | 42 | void connectWifi() { 43 | Serial.println(); 44 | Serial.println(); 45 | Serial.print("Connecting to WIFI network"); 46 | 47 | WiFi.begin(ssid, password); 48 | while (WiFi.status() != WL_CONNECTED) { 49 | delay(500); 50 | Serial.print("."); 51 | } 52 | 53 | Serial.println(""); 54 | Serial.println("WiFi connected"); 55 | Udp.begin(SERVER_PORT); 56 | } 57 | 58 | void moveForwards() { 59 | digitalWrite(BACKWARDS, LOW); 60 | digitalWrite(FORWARDS, HIGH); 61 | } 62 | 63 | void moveBackwards() { 64 | digitalWrite(FORWARDS, LOW); 65 | digitalWrite(BACKWARDS, HIGH); 66 | } 67 | 68 | void turnRight() { 69 | Serial.println("move right pressed"); 70 | digitalWrite(LEFT, LOW); 71 | digitalWrite(RIGHT, HIGH); 72 | } 73 | 74 | void turnLeft() { 75 | digitalWrite(RIGHT, LOW); 76 | digitalWrite(LEFT, HIGH); 77 | } 78 | 79 | void resetSteering() { 80 | digitalWrite(RIGHT, LOW); 81 | digitalWrite(LEFT, LOW); 82 | } 83 | 84 | void resetEngine() { 85 | digitalWrite(FORWARDS, LOW); 86 | digitalWrite(BACKWARDS, LOW); 87 | } 88 | 89 | void detectKeyPresses() { 90 | if (incomingByte == FORWARDS_PRESSED) { 91 | forwardsPressed = true; 92 | } 93 | else if (incomingByte == BACKWARDS_PRESSED) { 94 | backwardsPressed = true; 95 | } 96 | 97 | if (incomingByte == FORWARDS_RELEASED) { 98 | forwardsPressed = false; 99 | } 100 | else if (incomingByte == BACKWARDS_RELEASED) { 101 | backwardsPressed = false; 102 | } 103 | 104 | if (incomingByte == RIGHT_PRESSED) { 105 | rightPressed = true; 106 | } 107 | else if (incomingByte == LEFT_PRESSED) { 108 | leftPressed = true; 109 | } 110 | 111 | if (incomingByte == RIGHT_RELEASED) { 112 | rightPressed = false; 113 | } 114 | else if (incomingByte == LEFT_RELEASED) { 115 | leftPressed = false; 116 | } 117 | } 118 | 119 | void handlePinOutputs() { 120 | if (forwardsPressed == true) { 121 | moveForwards(); 122 | } 123 | else if (backwardsPressed == true) { 124 | moveBackwards(); 125 | } 126 | else { 127 | resetEngine(); 128 | } 129 | 130 | if (rightPressed == true) { 131 | turnRight(); 132 | } 133 | else if (leftPressed == true) { 134 | turnLeft(); 135 | } 136 | else { 137 | resetSteering(); 138 | } 139 | } 140 | 141 | void setup() { 142 | Serial.begin(BAUD_RATE); 143 | delay(10); 144 | 145 | initOutputs(); 146 | connectWifi(); 147 | } 148 | 149 | void loop() { 150 | int noBytes = Udp.parsePacket(); 151 | String received_command = ""; 152 | 153 | if ( noBytes ) { 154 | Serial.print(millis() / 1000); 155 | Serial.print(":Packet of "); 156 | Serial.print(noBytes); 157 | Serial.print(" received from "); 158 | Serial.print(Udp.remoteIP()); 159 | Serial.print(":"); 160 | Serial.println(Udp.remotePort()); 161 | 162 | Udp.read(packetBuffer,noBytes); 163 | Serial.println(); 164 | 165 | Serial.println(packetBuffer[0]); 166 | incomingByte = packetBuffer[0]; 167 | Serial.println(); 168 | } 169 | detectKeyPresses(); 170 | handlePinOutputs(); 171 | } 172 | -------------------------------------------------------------------------------- /wifi_controller_gui.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import socket 3 | import getopt, sys 4 | 5 | class Controller: 6 | DEFAULT_PORT = 1111 7 | 8 | def __init__(self, ip): 9 | self.pressed = {} 10 | self.prevPressed = {} 11 | self._initPresses() 12 | self._create_ui() 13 | self._host = ip 14 | self._port = self.DEFAULT_PORT 15 | 16 | def _netcat(self, content): 17 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 18 | s.sendto(content, (self._host, self._port)) 19 | 20 | def _initPresses(self): 21 | self.pressed["w"] = False 22 | self.pressed["a"] = False 23 | self.pressed["s"] = False 24 | self.pressed["d"] = False 25 | self.prevPressed["w"] = False 26 | self.prevPressed["a"] = False 27 | self.prevPressed["s"] = False 28 | self.prevPressed["d"] = False 29 | 30 | def start(self): 31 | self._check_key_press() 32 | self.root.mainloop() 33 | 34 | def _check_for_press(self, key, command): 35 | if self._is_pressed(key): 36 | self.prevPressed[key] = True 37 | self._netcat(command) 38 | print(key + " pressed") 39 | 40 | def _check_for_release(self, key, command): 41 | if self._is_released(key): 42 | self.prevPressed[key] = False 43 | self._netcat(command) 44 | print(key + " released") 45 | 46 | def _check_key_press(self): 47 | self._check_for_press("w", b"\x01") 48 | self._check_for_release("w", b"\x02") 49 | self._check_for_press("s", b"\x03") 50 | self._check_for_release("s", b"\x04") 51 | self._check_for_press("d", b"\x05") 52 | self._check_for_release("d", b"\x06") 53 | self._check_for_press("a", b"\x07") 54 | self._check_for_release("a", b"\x08") 55 | 56 | self.root.after(20, self._check_key_press) 57 | 58 | def _is_pressed(self, key): 59 | return self.pressed[key] and self.prevPressed[key] == False 60 | 61 | def _is_released(self, key): 62 | return self.pressed[key] == False and self.prevPressed[key] 63 | 64 | def _create_ui(self): 65 | self.root = tk.Tk() 66 | self.root.geometry('400x300') 67 | self._set_bindings() 68 | 69 | def _set_bindings(self): 70 | for char in ["w","s","d", "a"]: 71 | self.root.bind("" % char, self._pressed) 72 | self.root.bind("" % char, self._released) 73 | self.pressed[char] = False 74 | 75 | def _pressed(self, event): 76 | self.pressed[event.char] = True 77 | 78 | def _released(self, event): 79 | self.pressed[event.char] = False 80 | 81 | def main(): 82 | try: 83 | opts, args = getopt.getopt(sys.argv[1:], "h:", ["host="]) 84 | except getopt.GetoptError as err: 85 | print(str(err)) 86 | usage() 87 | sys.exit(2) 88 | host = "" 89 | for o, a in opts: 90 | if o in ("-h", "--host"): 91 | host = a 92 | 93 | if host == "": 94 | print("Did not define host, use -h or --host to pass the host name of the car") 95 | sys.exit(2) 96 | 97 | p = Controller(host) 98 | p.start() 99 | 100 | def usage(): 101 | print("Available options:") 102 | print("-h, --host Define RC car IP address") 103 | 104 | 105 | if __name__ == "__main__": 106 | main() 107 | --------------------------------------------------------------------------------