Change to MicroPython BLE
This commit is contained in:
		
							
								
								
									
										24
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,12 +1,28 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#SERIAL_PORT=/dev/ttyUSB0
 | 
					#SERIAL_PORT=/dev/ttyUSB0
 | 
				
			||||||
SERIAL_PORT=/dev/tty.usbserial-1410
 | 
					SERIAL_PORT=/dev/tty.usbserial-1420
 | 
				
			||||||
#SERIAL_PORT=/dev/tty.SLAB_USBtoUART
 | 
					#SERIAL_PORT=/dev/tty.SLAB_USBtoUART
 | 
				
			||||||
#SERIAL_PORT=/dev/tty.wchusbserial1410
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
all: flash
 | 
					all: flash delay config update reset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					delay:
 | 
				
			||||||
 | 
						sleep 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reset:
 | 
				
			||||||
 | 
						esptool.py -p $(SERIAL_PORT) --after hard_reset read_mac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					update:
 | 
				
			||||||
 | 
						ampy -d 0.5 -p $(SERIAL_PORT) put hal.py
 | 
				
			||||||
 | 
						ampy -d 0.5 -p $(SERIAL_PORT) put main.py
 | 
				
			||||||
 | 
						ampy -d 0.5 -p $(SERIAL_PORT) put boot.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config:
 | 
				
			||||||
 | 
						ampy -d 0.5 -p $(SERIAL_PORT) put config.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
flash:
 | 
					flash:
 | 
				
			||||||
	esptool.py -p $(SERIAL_PORT) -b 460800 erase_flash
 | 
						esptool.py -p $(SERIAL_PORT) -b 460800 erase_flash
 | 
				
			||||||
	esptool.py -p $(SERIAL_PORT) -b 460800 write_flash --flash_mode dio 0x1000 esp32-*.bin
 | 
						esptool.py -p $(SERIAL_PORT) -b 460800 write_flash --flash_mode dio 0x1000 esp32*.bin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					serial:
 | 
				
			||||||
 | 
						picocom --baud 115200 $(SERIAL_PORT)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							@@ -2,12 +2,16 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
The software that is running on the SumoRobots
 | 
					The software that is running on the SumoRobots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<img alt="Code" src="https://www.robokoding.com/assets/img/sumorobot_firmware.png" width="50%">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Instructions
 | 
					# Instructions
 | 
				
			||||||
* Change the SERIAL_PORT in the Makefile
 | 
					* Change the SERIAL_PORT in the Makefile
 | 
				
			||||||
 | 
					* Add your WiFi networks to the config.json file
 | 
				
			||||||
* Install [Python](https://www.python.org/downloads/)
 | 
					* Install [Python](https://www.python.org/downloads/)
 | 
				
			||||||
* Install [esptool](https://github.com/espressif/esptool) (to flash SumoFirmware to the SumoRobot)
 | 
					* Install [esptool](https://github.com/espressif/esptool) (to flash MicroPython to the ESP32)
 | 
				
			||||||
* Download the [SumoFirmware](https://github.com/robokoding/sumorobot-firmware/releases) to this directory
 | 
					* Install [ampy](https://github.com/adafruit/ampy) (for uploading files)
 | 
				
			||||||
* Upload the SumoFirmware to your SumoRobot (open a terminal and type: make all)
 | 
					* Download [the MicroPython binary](http://micropython.org/download#esp32) to this directory
 | 
				
			||||||
 | 
					* Upload the MicroPython binary and the SumoRobot firmware to your ESP32 (open a terminal and type: make all)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Support
 | 
					# Support
 | 
				
			||||||
If you find our work useful, please consider donating : )  
 | 
					If you find our work useful, please consider donating : )  
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								boot.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5
									
								
								boot.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					from utime import sleep_ms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Give time to cancel boot script
 | 
				
			||||||
 | 
					print("Press Ctrl-C to stop boot script...")
 | 
				
			||||||
 | 
					sleep_ms(500)
 | 
				
			||||||
							
								
								
									
										17
									
								
								config.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								config.json
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"status_led_pin": 5,
 | 
				
			||||||
 | 
						"battery_coeff": 2.25,
 | 
				
			||||||
 | 
						"sumorobot_name": "SumoRobot",
 | 
				
			||||||
 | 
						"firmware_timestamp": "2019.11.17 16:23:00",
 | 
				
			||||||
 | 
						"firmware_version": "1.0.0",
 | 
				
			||||||
 | 
						"left_servo_min_tuning": 1,
 | 
				
			||||||
 | 
						"left_servo_max_tuning": 100,
 | 
				
			||||||
 | 
						"right_servo_min_tuning": 1,
 | 
				
			||||||
 | 
						"right_servo_max_tuning": 100,
 | 
				
			||||||
 | 
						"sonar_threshold": 40,
 | 
				
			||||||
 | 
						"boot_code": "code.py",
 | 
				
			||||||
 | 
						"left_line_value": 1000,
 | 
				
			||||||
 | 
						"right_line_value": 1000,
 | 
				
			||||||
 | 
						"left_line_threshold": 1000,
 | 
				
			||||||
 | 
						"right_line_threshold": 1000
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										282
									
								
								hal.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										282
									
								
								hal.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,282 @@
 | 
				
			|||||||
 | 
					from utime import sleep_us, sleep_ms
 | 
				
			||||||
 | 
					from machine import Pin, PWM, ADC, time_pulse_us
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# LEDs
 | 
				
			||||||
 | 
					STATUS = 0
 | 
				
			||||||
 | 
					SONAR = 1
 | 
				
			||||||
 | 
					LEFT_LINE = 2
 | 
				
			||||||
 | 
					RIGHT_LINE = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Directions
 | 
				
			||||||
 | 
					STOP = 0
 | 
				
			||||||
 | 
					LEFT = 1
 | 
				
			||||||
 | 
					RIGHT = 2
 | 
				
			||||||
 | 
					SEARCH = 3
 | 
				
			||||||
 | 
					FORWARD = 4
 | 
				
			||||||
 | 
					BACKWARD = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Sumorobot(object):
 | 
				
			||||||
 | 
					    # Constructor
 | 
				
			||||||
 | 
					    def __init__(self, config = None):
 | 
				
			||||||
 | 
					        # Config file
 | 
				
			||||||
 | 
					        self.config = config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Sonar distance sensor
 | 
				
			||||||
 | 
					        self.echo = Pin(14, Pin.IN)
 | 
				
			||||||
 | 
					        self.trigger = Pin(27, Pin.OUT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Servo PWM-s
 | 
				
			||||||
 | 
					        self.pwm = {
 | 
				
			||||||
 | 
					            LEFT: PWM(Pin(15), freq=50, duty=0),
 | 
				
			||||||
 | 
					            RIGHT: PWM(Pin(4), freq=50, duty=0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # LED sensor feedback
 | 
				
			||||||
 | 
					        self.sensor_feedback = True
 | 
				
			||||||
 | 
					        # Bottom status LED
 | 
				
			||||||
 | 
					        self.status_led = Pin(self.config["status_led_pin"], Pin.OUT)
 | 
				
			||||||
 | 
					        # Bottom status LED is in reverse polarity
 | 
				
			||||||
 | 
					        self.status_led.value(1)
 | 
				
			||||||
 | 
					        # Sensor LEDs
 | 
				
			||||||
 | 
					        self.sonar_led = Pin(16, Pin.OUT)
 | 
				
			||||||
 | 
					        self.left_line_led = Pin(17, Pin.OUT)
 | 
				
			||||||
 | 
					        self.right_line_led = Pin(12, Pin.OUT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Battery level in %
 | 
				
			||||||
 | 
					        self.battery_level = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Battery gauge
 | 
				
			||||||
 | 
					        self.bat_status = 4.3
 | 
				
			||||||
 | 
					        self.move_counter = 0
 | 
				
			||||||
 | 
					        self.adc_battery = ADC(Pin(32))
 | 
				
			||||||
 | 
					        self.bat_charge = Pin(25, Pin.IN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The pullups for the phototransistors
 | 
				
			||||||
 | 
					        Pin(19, Pin.IN, Pin.PULL_UP)
 | 
				
			||||||
 | 
					        Pin(23, Pin.IN, Pin.PULL_UP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The phototransistors
 | 
				
			||||||
 | 
					        self.last_line = LEFT
 | 
				
			||||||
 | 
					        self.adc_line_left = ADC(Pin(34))
 | 
				
			||||||
 | 
					        self.adc_line_right = ADC(Pin(33))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set reference voltage to 3.3V
 | 
				
			||||||
 | 
					        self.adc_battery.atten(ADC.ATTN_11DB)
 | 
				
			||||||
 | 
					        self.adc_line_left.atten(ADC.ATTN_11DB)
 | 
				
			||||||
 | 
					        self.adc_line_right.atten(ADC.ATTN_11DB)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # To smooth out sonar sensor value
 | 
				
			||||||
 | 
					        self.sonar_score = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # For terminating sleep
 | 
				
			||||||
 | 
					        self.terminate = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # For search mode
 | 
				
			||||||
 | 
					        self.search = False
 | 
				
			||||||
 | 
					        self.search_counter = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Memorise previous servo speeds
 | 
				
			||||||
 | 
					        self.prev_speed = {LEFT: 0, RIGHT: 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to set LED states
 | 
				
			||||||
 | 
					    def set_led(self, led, value):
 | 
				
			||||||
 | 
					        # Turn the given LED on or off
 | 
				
			||||||
 | 
					        if led == STATUS:
 | 
				
			||||||
 | 
					            # Status LED is reverse polarity
 | 
				
			||||||
 | 
					            self.status_led.value(0 if value else 1)
 | 
				
			||||||
 | 
					        elif led == SONAR:
 | 
				
			||||||
 | 
					            self.sonar_led.value(value)
 | 
				
			||||||
 | 
					        elif led == LEFT_LINE:
 | 
				
			||||||
 | 
					            self.left_line_led.value(value)
 | 
				
			||||||
 | 
					        elif led == RIGHT_LINE:
 | 
				
			||||||
 | 
					            self.right_line_led.value(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to get battery level in percentage
 | 
				
			||||||
 | 
					    def get_battery_level(self):
 | 
				
			||||||
 | 
					        # When the SumoRobot is not moving
 | 
				
			||||||
 | 
					        if self.prev_speed[LEFT] == 0 and self.prev_speed[RIGHT] == 0:
 | 
				
			||||||
 | 
					            # Calculate battery voltage
 | 
				
			||||||
 | 
					            battery_voltage = round(self.config["battery_coeff"] * (self.adc_battery.read() * 3.3 / 4096), 2)
 | 
				
			||||||
 | 
					            # Map battery voltage to percentage
 | 
				
			||||||
 | 
					            temp_battery_level = 0.0 + ((100.0 - 0.0) / (4.2 - 3.2)) * (battery_voltage - 3.2)
 | 
				
			||||||
 | 
					            # When battery level changed more than 5 percent
 | 
				
			||||||
 | 
					            if abs(self.battery_level - temp_battery_level) > 5:
 | 
				
			||||||
 | 
					                # Update battery level
 | 
				
			||||||
 | 
					                self.battery_level = round(temp_battery_level)
 | 
				
			||||||
 | 
					        # Return the battery level in percentage
 | 
				
			||||||
 | 
					        return min(100, max(0, self.battery_level))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to get distance (cm) from the object in front of the SumoRobot
 | 
				
			||||||
 | 
					    def get_sonar_value(self):
 | 
				
			||||||
 | 
					        # Send a pulse
 | 
				
			||||||
 | 
					        self.trigger.value(0)
 | 
				
			||||||
 | 
					        sleep_us(5)
 | 
				
			||||||
 | 
					        self.trigger.value(1)
 | 
				
			||||||
 | 
					        sleep_us(10)
 | 
				
			||||||
 | 
					        self.trigger.value(0)
 | 
				
			||||||
 | 
					        # Wait for the pulse and calculate the distance
 | 
				
			||||||
 | 
					        return round((time_pulse_us(self.echo, 1, 30000) / 2) / 29.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to get boolean if there is something in front of the SumoRobot
 | 
				
			||||||
 | 
					    def is_sonar(self):
 | 
				
			||||||
 | 
					        # Get the sonar value
 | 
				
			||||||
 | 
					        self.sonar_value = self.get_sonar_value()
 | 
				
			||||||
 | 
					        # When the sonar value is small and the ping actually returned
 | 
				
			||||||
 | 
					        if self.sonar_value < self.config["sonar_threshold"] and self.sonar_value > 0:
 | 
				
			||||||
 | 
					            # When not maximum score
 | 
				
			||||||
 | 
					            if self.sonar_score < 5:
 | 
				
			||||||
 | 
					                # Increase the sonar score
 | 
				
			||||||
 | 
					                self.sonar_score += 1
 | 
				
			||||||
 | 
					        # When no sonar ping was returned
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # When not lowest score
 | 
				
			||||||
 | 
					            if self.sonar_score > 0:
 | 
				
			||||||
 | 
					                # Decrease the sonar score
 | 
				
			||||||
 | 
					                self.sonar_score -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # When the sensor saw something more than 2 times
 | 
				
			||||||
 | 
					        value = True if self.sonar_score > 2 else False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Trigger sonar LED
 | 
				
			||||||
 | 
					        self.set_led(SONAR, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to update the config file
 | 
				
			||||||
 | 
					    def update_config_file(self):
 | 
				
			||||||
 | 
					        # Update the config file
 | 
				
			||||||
 | 
					        with open("config.part", "w") as config_file:
 | 
				
			||||||
 | 
					            config_file.write(ujson.dumps(self.config))
 | 
				
			||||||
 | 
					        os.rename("config.part", "config.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to update line calibration and write it to the config file
 | 
				
			||||||
 | 
					    def calibrate_line_values(self):
 | 
				
			||||||
 | 
					        # Read the line sensor values
 | 
				
			||||||
 | 
					        self.config["left_line_value"] = self.adc_line_left.read()
 | 
				
			||||||
 | 
					        self.config["right_line_value"] = self.adc_line_right.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function to get light inensity from the phototransistors
 | 
				
			||||||
 | 
					    def get_line(self, line):
 | 
				
			||||||
 | 
					        # Check if the direction is valid
 | 
				
			||||||
 | 
					        assert line in (LEFT, RIGHT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Return the given line sensor value
 | 
				
			||||||
 | 
					        if line == LEFT:
 | 
				
			||||||
 | 
					            return self.adc_line_left.read()
 | 
				
			||||||
 | 
					        elif line == RIGHT:
 | 
				
			||||||
 | 
					            return self.adc_line_right.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_line(self, line):
 | 
				
			||||||
 | 
					        # Check if the direction is valid
 | 
				
			||||||
 | 
					        assert line in (LEFT, RIGHT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Define feedback LED
 | 
				
			||||||
 | 
					        led = LEFT_LINE if line == LEFT else RIGHT_LINE
 | 
				
			||||||
 | 
					        # Define config prefix
 | 
				
			||||||
 | 
					        prefix = "left" if line == LEFT else "right"
 | 
				
			||||||
 | 
					        # Check for line
 | 
				
			||||||
 | 
					        value = abs(self.get_line(line) - self.config[prefix + "_line_value"]) > self.config[prefix + "_line_threshold"]
 | 
				
			||||||
 | 
					        # Show LED feedback
 | 
				
			||||||
 | 
					        self.set_led(led, value)
 | 
				
			||||||
 | 
					        # Update last line direction if line was detected
 | 
				
			||||||
 | 
					        self.last_line = value if value else self.last_line
 | 
				
			||||||
 | 
					        # Return the given line sensor value
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_servo(self, servo, speed):
 | 
				
			||||||
 | 
					        # Check if the direction is valid
 | 
				
			||||||
 | 
					        assert servo in (LEFT, RIGHT)
 | 
				
			||||||
 | 
					        # Check if the speed is valid
 | 
				
			||||||
 | 
					        assert speed <= 100 and speed >= -100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # When the speed didn't change
 | 
				
			||||||
 | 
					        if speed == self.prev_speed[servo]:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Save the new speed
 | 
				
			||||||
 | 
					        self.prev_speed[servo] = speed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set the given servo speed
 | 
				
			||||||
 | 
					        if speed == 0:
 | 
				
			||||||
 | 
					            self.pwm[servo].duty(0)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Define config prefix
 | 
				
			||||||
 | 
					            prefix = "left" if servo == LEFT else "right"
 | 
				
			||||||
 | 
					            # -100 ... 100 to min_tuning .. max_tuning
 | 
				
			||||||
 | 
					            min_tuning = self.config[prefix + "_servo_min_tuning"]
 | 
				
			||||||
 | 
					            max_tuning = self.config[prefix + "_servo_max_tuning"]
 | 
				
			||||||
 | 
					            self.pwm[servo].duty(int((speed + 100) / 200 * (max_tuning - min_tuning) + min_tuning))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def move(self, dir):
 | 
				
			||||||
 | 
					        # Check if the direction is valid
 | 
				
			||||||
 | 
					        assert dir in (SEARCH, STOP, RIGHT, LEFT, BACKWARD, FORWARD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Go to the given direction
 | 
				
			||||||
 | 
					        if dir == STOP:
 | 
				
			||||||
 | 
					            self.set_servo(LEFT, 0)
 | 
				
			||||||
 | 
					            self.set_servo(RIGHT, 0)
 | 
				
			||||||
 | 
					        elif dir == LEFT:
 | 
				
			||||||
 | 
					            self.set_servo(LEFT, -100)
 | 
				
			||||||
 | 
					            self.set_servo(RIGHT, -100)
 | 
				
			||||||
 | 
					        elif dir == RIGHT:
 | 
				
			||||||
 | 
					            self.set_servo(LEFT, 100)
 | 
				
			||||||
 | 
					            self.set_servo(RIGHT, 100)
 | 
				
			||||||
 | 
					        elif dir == SEARCH:
 | 
				
			||||||
 | 
					            # Change search mode after X seconds
 | 
				
			||||||
 | 
					            if self.search_counter == 50:
 | 
				
			||||||
 | 
					                self.search = not self.search
 | 
				
			||||||
 | 
					                self.search_counter = 0
 | 
				
			||||||
 | 
					            # When in search mode
 | 
				
			||||||
 | 
					            if self.search:
 | 
				
			||||||
 | 
					                self.move(FORWARD)
 | 
				
			||||||
 | 
					            elif self.last_line == RIGHT:
 | 
				
			||||||
 | 
					                self.move(LEFT)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.move(RIGHT)
 | 
				
			||||||
 | 
					            # Increase search counter
 | 
				
			||||||
 | 
					            self.search_counter += 1
 | 
				
			||||||
 | 
					        elif dir == FORWARD:
 | 
				
			||||||
 | 
					            self.set_servo(LEFT, 100)
 | 
				
			||||||
 | 
					            self.set_servo(RIGHT, -100)
 | 
				
			||||||
 | 
					        elif dir == BACKWARD:
 | 
				
			||||||
 | 
					            self.set_servo(LEFT, -100)
 | 
				
			||||||
 | 
					            self.set_servo(RIGHT, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_sensor_feedback(self):
 | 
				
			||||||
 | 
					        if self.sensor_feedback:
 | 
				
			||||||
 | 
					            # Execute to see LED feedback for sensors
 | 
				
			||||||
 | 
					            self.is_sonar()
 | 
				
			||||||
 | 
					            self.is_line(LEFT)
 | 
				
			||||||
 | 
					            self.is_line(RIGHT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_sensor_scope(self):
 | 
				
			||||||
 | 
					        # TODO: implement sensor value caching
 | 
				
			||||||
 | 
					        return str(self.get_sonar_value()) + ',' + \
 | 
				
			||||||
 | 
					            str(self.get_line(LEFT)) + ',' + \
 | 
				
			||||||
 | 
					            str(self.get_line(RIGHT)) + ',' + \
 | 
				
			||||||
 | 
					            str(self.bat_charge.value()) + ',' + \
 | 
				
			||||||
 | 
					            str(self.get_battery_level())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_configuration_scope(self):
 | 
				
			||||||
 | 
					        return str(self.config["sumorobot_name"]) + ',' + \
 | 
				
			||||||
 | 
					            str(self.config["firmware_version"]) + ',' + \
 | 
				
			||||||
 | 
					            str(self.config["left_line_value"]) + ',' + \
 | 
				
			||||||
 | 
					            str(self.config["right_line_value"]) + ',' + \
 | 
				
			||||||
 | 
					            str(self.config["left_line_threshold"]) + ',' + \
 | 
				
			||||||
 | 
					            str(self.config["right_line_threshold"]) + ',' + \
 | 
				
			||||||
 | 
					            str(self.config["sonar_threshold"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sleep(self, delay):
 | 
				
			||||||
 | 
					        # Check for valid delay
 | 
				
			||||||
 | 
					        assert delay > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Split the delay into 50ms chunks
 | 
				
			||||||
 | 
					        while delay:
 | 
				
			||||||
 | 
					            # Check for forceful termination
 | 
				
			||||||
 | 
					            if self.terminate:
 | 
				
			||||||
 | 
					                # Terminate the delay
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                sleep_ms(50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            delay -= 50
 | 
				
			||||||
							
								
								
									
										173
									
								
								main.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										173
									
								
								main.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,173 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					import ujson
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					import _thread
 | 
				
			||||||
 | 
					import ubluetooth
 | 
				
			||||||
 | 
					from machine import Timer
 | 
				
			||||||
 | 
					from micropython import const
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from hal import *
 | 
				
			||||||
 | 
					# Loading libraries takes ca 400ms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# BLE events
 | 
				
			||||||
 | 
					_IRQ_CENTRAL_CONNECT = const(1)
 | 
				
			||||||
 | 
					_IRQ_CENTRAL_DISCONNECT = const(2)
 | 
				
			||||||
 | 
					_IRQ_GATTS_READ_REQUEST = const(4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Open and parse the config file
 | 
				
			||||||
 | 
					with open("config.json", "r") as config_file:
 | 
				
			||||||
 | 
					    config = ujson.load(config_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Initialize the SumoRobot object
 | 
				
			||||||
 | 
					sumorobot = Sumorobot(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Advertise BLE name (SumoRobot name)
 | 
				
			||||||
 | 
					def advertise_ble_name(name):
 | 
				
			||||||
 | 
					    ble_name = bytes(name, 'ascii')
 | 
				
			||||||
 | 
					    ble_name = bytearray((len(ble_name) + 1, 0x09)) + ble_name
 | 
				
			||||||
 | 
					    ble.gap_advertise(100, bytearray('\x02\x01\x02') + ble_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def update_battery_level(timer):
 | 
				
			||||||
 | 
					    if conn_handle is not None:
 | 
				
			||||||
 | 
					        battery_level = sumorobot.get_battery_level()
 | 
				
			||||||
 | 
					        ble.gatts_notify(conn_handle, battery, bytes([battery_level]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The code processing thread
 | 
				
			||||||
 | 
					def process():
 | 
				
			||||||
 | 
					    global prev_bat_level, python_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        # Leave time to process other code
 | 
				
			||||||
 | 
					        sleep_ms(50)
 | 
				
			||||||
 | 
					        # Execute to see LED feedback for sensors
 | 
				
			||||||
 | 
					        sumorobot.update_sensor_feedback()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # When no code to execute
 | 
				
			||||||
 | 
					        if python_code == b'':
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Try to execute the Python code
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            python_code = compile(python_code, "snippet", "exec")
 | 
				
			||||||
 | 
					            exec(python_code)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            print("main.py: the code sent had errors")
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            print("main.py: finized execution")
 | 
				
			||||||
 | 
					            # Erase the code
 | 
				
			||||||
 | 
					            python_code = b''
 | 
				
			||||||
 | 
					            # Stop the robot
 | 
				
			||||||
 | 
					            sumorobot.move(STOP)
 | 
				
			||||||
 | 
					            # Cancel code termination
 | 
				
			||||||
 | 
					            sumorobot.terminate = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The BLE handler thread
 | 
				
			||||||
 | 
					def ble_handler(event, data):
 | 
				
			||||||
 | 
					    global conn_handle, python_code, temp_python_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if event is _IRQ_CENTRAL_CONNECT:
 | 
				
			||||||
 | 
					        conn_handle, _, _, = data
 | 
				
			||||||
 | 
					        # Turn ON the status LED
 | 
				
			||||||
 | 
					        sumorobot.set_led(STATUS, True)
 | 
				
			||||||
 | 
					        update_battery_level(None)
 | 
				
			||||||
 | 
					    elif event is _IRQ_CENTRAL_DISCONNECT:
 | 
				
			||||||
 | 
					        conn_handle = None
 | 
				
			||||||
 | 
					        # Turn OFF status LED
 | 
				
			||||||
 | 
					        sumorobot.set_led(STATUS, False)
 | 
				
			||||||
 | 
					        # Advertise with name
 | 
				
			||||||
 | 
					        advertise_ble_name(sumorobot.config['sumorobot_name'])
 | 
				
			||||||
 | 
					    elif event is _IRQ_GATTS_READ_REQUEST:
 | 
				
			||||||
 | 
					        # Read the command
 | 
				
			||||||
 | 
					        cmd = ble.gatts_read(rx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if b'<stop>' in cmd:
 | 
				
			||||||
 | 
					            python_code = b''
 | 
				
			||||||
 | 
					            sumorobot.move(STOP)
 | 
				
			||||||
 | 
					            sumorobot.terminate = True
 | 
				
			||||||
 | 
					        elif b'<forward>' in cmd:
 | 
				
			||||||
 | 
					            python_code = b''
 | 
				
			||||||
 | 
					            sumorobot.move(FORWARD)
 | 
				
			||||||
 | 
					        elif b'<backward>' in cmd:
 | 
				
			||||||
 | 
					            python_code = b''
 | 
				
			||||||
 | 
					            sumorobot.move(BACKWARD)
 | 
				
			||||||
 | 
					        elif b'<left>' in cmd:
 | 
				
			||||||
 | 
					            python_code = b''
 | 
				
			||||||
 | 
					            sumorobot.move(LEFT)
 | 
				
			||||||
 | 
					        elif b'<right>' in cmd:
 | 
				
			||||||
 | 
					            python_code = b''
 | 
				
			||||||
 | 
					            sumorobot.move(RIGHT)
 | 
				
			||||||
 | 
					        elif b'<sensors>' in cmd:
 | 
				
			||||||
 | 
					            print(sumorobot.get_sensor_scope())
 | 
				
			||||||
 | 
					            ble.gatts_notify(conn_handle, tx, sumorobot.get_sensor_scope())
 | 
				
			||||||
 | 
					        elif b'<code>' in cmd:
 | 
				
			||||||
 | 
					            temp_python_code = b'\n'
 | 
				
			||||||
 | 
					        elif b'<code/>' in cmd:
 | 
				
			||||||
 | 
					            python_code = temp_python_code
 | 
				
			||||||
 | 
					            temp_python_code = b''
 | 
				
			||||||
 | 
					        elif temp_python_code != b'':
 | 
				
			||||||
 | 
					            temp_python_code += cmd
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            temp_python_code = b''
 | 
				
			||||||
 | 
					            print('main.py: unknown cmd=', cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					conn_handle = None
 | 
				
			||||||
 | 
					temp_python_code = b''
 | 
				
			||||||
 | 
					python_code = b''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# When user code (code.py) exists
 | 
				
			||||||
 | 
					if 'code.py' in os.listdir():
 | 
				
			||||||
 | 
					    print('main.py: trying to load code.py')
 | 
				
			||||||
 | 
					    # Try to load the user code
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        with open("code.py", "r") as code:
 | 
				
			||||||
 | 
					            python_code = compile(code.read(), "snippet", "exec")
 | 
				
			||||||
 | 
					    except:
 | 
				
			||||||
 | 
					        print("main.py: code.py compilation failed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Start BLE
 | 
				
			||||||
 | 
					ble = ubluetooth.BLE()
 | 
				
			||||||
 | 
					ble.active(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Register the BLE hander
 | 
				
			||||||
 | 
					ble.irq(ble_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# BLE info serivce
 | 
				
			||||||
 | 
					INFO_SERVICE_UUID = ubluetooth.UUID(0x180a)
 | 
				
			||||||
 | 
					MODEL_CHARACTERISTIC = (ubluetooth.UUID(0x2a24), ubluetooth.FLAG_READ,)
 | 
				
			||||||
 | 
					FIRMWARE_CHARACTERISTIC = (ubluetooth.UUID(0x2a26), ubluetooth.FLAG_READ,)
 | 
				
			||||||
 | 
					MANUFACTURER_CHARACTERISTIC = (ubluetooth.UUID(0x2a29), ubluetooth.FLAG_READ,)
 | 
				
			||||||
 | 
					INFO_SERVICE = (INFO_SERVICE_UUID, (MODEL_CHARACTERISTIC, FIRMWARE_CHARACTERISTIC, MANUFACTURER_CHARACTERISTIC,),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# BLE battery service
 | 
				
			||||||
 | 
					BATTERY_SERVICE_UUID = ubluetooth.UUID(0x180f)
 | 
				
			||||||
 | 
					BATTERY_CHARACTERISTIC = (ubluetooth.UUID(0x2a19), ubluetooth.FLAG_READ | ubluetooth.FLAG_NOTIFY,)
 | 
				
			||||||
 | 
					BATTERY_SERVICE = (BATTERY_SERVICE_UUID, (BATTERY_CHARACTERISTIC,),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# BLE UART service
 | 
				
			||||||
 | 
					UART_SERVICE_UUID = ubluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
 | 
				
			||||||
 | 
					RX_CHARACTERISTIC = (ubluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), ubluetooth.FLAG_WRITE,)
 | 
				
			||||||
 | 
					TX_CHARACTERISTIC = (ubluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), ubluetooth.FLAG_READ | ubluetooth.FLAG_NOTIFY,)
 | 
				
			||||||
 | 
					UART_SERVICE = (UART_SERVICE_UUID, (TX_CHARACTERISTIC, RX_CHARACTERISTIC,),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Register BLE services
 | 
				
			||||||
 | 
					SERVICES = (INFO_SERVICE, BATTERY_SERVICE, UART_SERVICE,)
 | 
				
			||||||
 | 
					((model, firmware, manufacturer,), (battery,), (tx, rx,),) = ble.gatts_register_services(SERVICES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set BLE info service values
 | 
				
			||||||
 | 
					ble.gatts_write(model, "SumoRobot")
 | 
				
			||||||
 | 
					ble.gatts_write(manufacturer, "RoboKoding LTD")
 | 
				
			||||||
 | 
					ble.gatts_write(firmware, sumorobot.config['firmware_version'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Start BLE advertising with name
 | 
				
			||||||
 | 
					advertise_ble_name(sumorobot.config['sumorobot_name'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Start the code processing thread
 | 
				
			||||||
 | 
					_thread.start_new_thread(process, ())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Start BLE battery percentage update timer
 | 
				
			||||||
 | 
					battery_timer = Timer(Timer.PERIODIC)
 | 
				
			||||||
 | 
					battery_timer.init(period=3000, callback=update_battery_level)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Clean up
 | 
				
			||||||
 | 
					import gc
 | 
				
			||||||
 | 
					gc.collect()
 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
;PlatformIO Project Configuration File
 | 
					 | 
				
			||||||
;
 | 
					 | 
				
			||||||
;   Build options: build flags, source filter
 | 
					 | 
				
			||||||
;   Upload options: custom upload port, speed and extra flags
 | 
					 | 
				
			||||||
;   Library options: dependencies, extra library storages
 | 
					 | 
				
			||||||
;   Advanced options: extra scripting
 | 
					 | 
				
			||||||
;
 | 
					 | 
				
			||||||
; Please visit documentation for the other options and examples
 | 
					 | 
				
			||||||
; https://docs.platformio.org/page/projectconf.html
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[env:lolin32]
 | 
					 | 
				
			||||||
platform = espressif32
 | 
					 | 
				
			||||||
board = lolin32
 | 
					 | 
				
			||||||
framework = arduino
 | 
					 | 
				
			||||||
upload_speed = 460800
 | 
					 | 
				
			||||||
monitor_speed = 115200
 | 
					 | 
				
			||||||
lib_deps = NewPing
 | 
					 | 
				
			||||||
							
								
								
									
										396
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										396
									
								
								src/main.cpp
									
									
									
									
									
								
							@@ -1,396 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
    This the code that runs on the SumoRobots
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
// Include BLE libraries
 | 
					 | 
				
			||||||
#include <BLEDevice.h>
 | 
					 | 
				
			||||||
#include <BLEServer.h>
 | 
					 | 
				
			||||||
#include <BLEUtils.h>
 | 
					 | 
				
			||||||
#include <BLE2902.h>
 | 
					 | 
				
			||||||
#include <BLE2904.h>
 | 
					 | 
				
			||||||
// Include other libraries
 | 
					 | 
				
			||||||
#include <string.h>
 | 
					 | 
				
			||||||
#include <Ticker.h>
 | 
					 | 
				
			||||||
#include <NewPing.h>
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#include <Preferences.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define DEBUG true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define VERSION "0.8.0"
 | 
					 | 
				
			||||||
#define VERSION_TIMESTAMP "2019.08.13 08:00"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// See the following for generating UUIDs:
 | 
					 | 
				
			||||||
// https://www.uuidgenerator.net/
 | 
					 | 
				
			||||||
#define NUS_SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // NUS service UUID
 | 
					 | 
				
			||||||
#define NUS_CHARACTERISTIC_RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
 | 
					 | 
				
			||||||
#define NUS_CHARACTERISTIC_TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Cleate BLE variables
 | 
					 | 
				
			||||||
BLEServer * bleServer = NULL;
 | 
					 | 
				
			||||||
bool deviceConnected = false;
 | 
					 | 
				
			||||||
bool oldDeviceConnected = false;
 | 
					 | 
				
			||||||
BLECharacteristic * nusTxCharacteristic;
 | 
					 | 
				
			||||||
BLECharacteristic * batteryLevelCharacteristic;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Create preferences persistence
 | 
					 | 
				
			||||||
Preferences preferences;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Create timers
 | 
					 | 
				
			||||||
Ticker sonarTimer;
 | 
					 | 
				
			||||||
Ticker batteryTimer;
 | 
					 | 
				
			||||||
Ticker connectionLedTimer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Battery stuff
 | 
					 | 
				
			||||||
float batteryVoltage;
 | 
					 | 
				
			||||||
bool robotMoving = false;
 | 
					 | 
				
			||||||
uint8_t batteryLevel = 0;
 | 
					 | 
				
			||||||
uint8_t tempBatteryLevel = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Sonar stuff
 | 
					 | 
				
			||||||
uint8_t sonarValue;
 | 
					 | 
				
			||||||
NewPing sonar(27, 14, 200);
 | 
					 | 
				
			||||||
uint8_t sonarThreshold = 40;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Line stuff
 | 
					 | 
				
			||||||
uint16_t leftLineValue;
 | 
					 | 
				
			||||||
uint16_t rightLineValue;
 | 
					 | 
				
			||||||
uint16_t leftLineValueField = 0;
 | 
					 | 
				
			||||||
uint16_t rightLineValueField = 0;
 | 
					 | 
				
			||||||
uint16_t leftLineThreshold = 1000;
 | 
					 | 
				
			||||||
uint16_t rightLineThreshold = 1000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Other sensor stuff
 | 
					 | 
				
			||||||
uint8_t sensorValues[6];
 | 
					 | 
				
			||||||
bool ledFeedbackEnabled = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Move command names
 | 
					 | 
				
			||||||
std::string cmdStop("stop");
 | 
					 | 
				
			||||||
std::string cmdLeft("left");
 | 
					 | 
				
			||||||
std::string cmdRight("right");
 | 
					 | 
				
			||||||
std::string cmdForward("forward");
 | 
					 | 
				
			||||||
std::string cmdBackward("backward");
 | 
					 | 
				
			||||||
// Other command names
 | 
					 | 
				
			||||||
std::string cmdLed("led");
 | 
					 | 
				
			||||||
std::string cmdLine("line");
 | 
					 | 
				
			||||||
std::string cmdName("name");
 | 
					 | 
				
			||||||
std::string cmdSonar("sonar");
 | 
					 | 
				
			||||||
std::string cmdServo("servo");
 | 
					 | 
				
			||||||
std::string cmdLedFeedback("ledf");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setLed(char led, char value) {
 | 
					 | 
				
			||||||
  // Convert the value to a HIGH or LOW
 | 
					 | 
				
			||||||
  bool state = value == '1' ? HIGH : LOW; 
 | 
					 | 
				
			||||||
  if (led == 'c') {
 | 
					 | 
				
			||||||
    // Connection status LED is opposite value
 | 
					 | 
				
			||||||
    digitalWrite(5, !state);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else if (led == 's') {
 | 
					 | 
				
			||||||
    digitalWrite(16, state);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else if (led == 'r') {
 | 
					 | 
				
			||||||
    digitalWrite(12, state);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else if (led == 'l') {
 | 
					 | 
				
			||||||
    digitalWrite(17, state);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setServo(char servo, int8_t speed) {
 | 
					 | 
				
			||||||
  Serial.println(speed);
 | 
					 | 
				
			||||||
  if (servo == 'l') {
 | 
					 | 
				
			||||||
    ledcWrite(1, map(speed, -100, 100, 1, 100));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else if (servo == 'r') {
 | 
					 | 
				
			||||||
    ledcWrite(2, map(speed, -100, 100, 1, 30));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void updateSensorFeedback() {
 | 
					 | 
				
			||||||
  if (sonarValue <= sonarThreshold) {
 | 
					 | 
				
			||||||
    digitalWrite(16, HIGH);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    digitalWrite(16, LOW);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (abs(leftLineValue - leftLineValueField) > leftLineThreshold) {
 | 
					 | 
				
			||||||
    digitalWrite(17, HIGH);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    digitalWrite(17, LOW);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (abs(rightLineValue - rightLineValueField) > rightLineThreshold) {
 | 
					 | 
				
			||||||
    digitalWrite(12, HIGH);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    digitalWrite(12, LOW);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void updateSonarValue() {
 | 
					 | 
				
			||||||
  // Update the sensor values
 | 
					 | 
				
			||||||
  sonarValue = sonar.ping_cm();
 | 
					 | 
				
			||||||
  // When we didn't receive a ping back
 | 
					 | 
				
			||||||
  // set to max distance
 | 
					 | 
				
			||||||
  if (sonarValue == 0) sonarValue = 255;
 | 
					 | 
				
			||||||
  leftLineValue = analogRead(34);
 | 
					 | 
				
			||||||
  rightLineValue = analogRead(33);
 | 
					 | 
				
			||||||
  if (ledFeedbackEnabled) updateSensorFeedback();
 | 
					 | 
				
			||||||
  sensorValues[0] = sonarValue;
 | 
					 | 
				
			||||||
  sensorValues[1] = leftLineValue >> 8;
 | 
					 | 
				
			||||||
  sensorValues[2] = leftLineValue;
 | 
					 | 
				
			||||||
  sensorValues[3] = rightLineValue >> 8;
 | 
					 | 
				
			||||||
  sensorValues[4] = rightLineValue;
 | 
					 | 
				
			||||||
  sensorValues[5] = digitalRead(25);
 | 
					 | 
				
			||||||
  // When BLE is connected
 | 
					 | 
				
			||||||
  if (deviceConnected) {
 | 
					 | 
				
			||||||
    // Notify the new sensor values
 | 
					 | 
				
			||||||
    nusTxCharacteristic->setValue(sensorValues, 6);
 | 
					 | 
				
			||||||
    nusTxCharacteristic->notify();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void updateBatteryLevel() {
 | 
					 | 
				
			||||||
  // Don't update battery level when robot is moving
 | 
					 | 
				
			||||||
  // the servo motors lower the battery voltage
 | 
					 | 
				
			||||||
  // TODO: wait still a little more after moving
 | 
					 | 
				
			||||||
  // for the voltage to settle
 | 
					 | 
				
			||||||
  if (robotMoving) {
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Calculate the battery voltage
 | 
					 | 
				
			||||||
  batteryVoltage = 2.12 * (analogRead(32) * 3.3 / 4096);
 | 
					 | 
				
			||||||
  // Calculate battery percentage
 | 
					 | 
				
			||||||
  tempBatteryLevel = 0.0 + ((100.0 - 0.0) / (4.2 - 3.2)) * (batteryVoltage - 3.2);
 | 
					 | 
				
			||||||
  // When battery level changed more than 3%
 | 
					 | 
				
			||||||
  if (abs(batteryLevel - tempBatteryLevel) > 3) {
 | 
					 | 
				
			||||||
    // Update battery level
 | 
					 | 
				
			||||||
    batteryLevel = tempBatteryLevel;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // Notify the new battery level
 | 
					 | 
				
			||||||
  batteryLevelCharacteristic->setValue(&batteryLevel, 1);
 | 
					 | 
				
			||||||
  batteryLevelCharacteristic->notify();
 | 
					 | 
				
			||||||
#if DEBUG
 | 
					 | 
				
			||||||
  Serial.print(batteryVoltage);
 | 
					 | 
				
			||||||
  Serial.print(" : ");
 | 
					 | 
				
			||||||
  Serial.println(batteryLevel);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void blinkConnectionLed() {
 | 
					 | 
				
			||||||
  digitalWrite(5, LOW);
 | 
					 | 
				
			||||||
  delay(20);
 | 
					 | 
				
			||||||
  digitalWrite(5, HIGH);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// BLE connect and disconnect callbacks
 | 
					 | 
				
			||||||
class MyServerCallbacks: public BLEServerCallbacks {
 | 
					 | 
				
			||||||
  void onConnect(BLEServer * pServer) {
 | 
					 | 
				
			||||||
    deviceConnected = true;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void onDisconnect(BLEServer * pServer) {
 | 
					 | 
				
			||||||
    deviceConnected = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// BLE NUS received callback
 | 
					 | 
				
			||||||
class MyCallbacks: public BLECharacteristicCallbacks {
 | 
					 | 
				
			||||||
  void onWrite(BLECharacteristic * nusRxCharacteristic) {
 | 
					 | 
				
			||||||
    // Get the received command over BLE
 | 
					 | 
				
			||||||
    std::string cmd = nusRxCharacteristic->getValue();
 | 
					 | 
				
			||||||
#if DEBUG
 | 
					 | 
				
			||||||
    Serial.println(cmd.c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    if (cmd.length() > 0) {
 | 
					 | 
				
			||||||
      //int speed = atoi(rxValue.c_str());
 | 
					 | 
				
			||||||
      //Serial.println(speed);
 | 
					 | 
				
			||||||
      //ledcWrite(1, speed); // left 1 ... 100
 | 
					 | 
				
			||||||
      //ledcWrite(2, speed); // right 1 ... 30
 | 
					 | 
				
			||||||
      // Specify command
 | 
					 | 
				
			||||||
      if (cmd == cmdForward) {
 | 
					 | 
				
			||||||
        robotMoving = true;
 | 
					 | 
				
			||||||
        ledcWrite(1, 100);
 | 
					 | 
				
			||||||
        ledcWrite(2, 1);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd == cmdBackward) {
 | 
					 | 
				
			||||||
        robotMoving = true;
 | 
					 | 
				
			||||||
        ledcWrite(1, 1);
 | 
					 | 
				
			||||||
        ledcWrite(2, 30);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd == cmdLeft) {
 | 
					 | 
				
			||||||
        robotMoving = true;
 | 
					 | 
				
			||||||
        ledcWrite(1, 1);
 | 
					 | 
				
			||||||
        ledcWrite(2, 1);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd == cmdRight) {
 | 
					 | 
				
			||||||
        robotMoving = true;
 | 
					 | 
				
			||||||
        ledcWrite(1, 100);
 | 
					 | 
				
			||||||
        ledcWrite(2, 30);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd == cmdStop) {
 | 
					 | 
				
			||||||
        robotMoving = false;
 | 
					 | 
				
			||||||
        ledcWrite(1, 0);
 | 
					 | 
				
			||||||
        ledcWrite(2, 0);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd == cmdLedFeedback) {
 | 
					 | 
				
			||||||
        ledFeedbackEnabled = !ledFeedbackEnabled;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd.find(cmdLed) != std::string::npos) {
 | 
					 | 
				
			||||||
        setLed(cmd.at(3), cmd.at(4));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd.find(cmdLine) != std::string::npos) {
 | 
					 | 
				
			||||||
        // Get the threshold value
 | 
					 | 
				
			||||||
        leftLineThreshold = atoi(cmd.substr(4, cmd.length() - 4).c_str());
 | 
					 | 
				
			||||||
        rightLineThreshold = leftLineThreshold;
 | 
					 | 
				
			||||||
        // Remember value on the field (white or black)
 | 
					 | 
				
			||||||
        leftLineValueField = analogRead(34);
 | 
					 | 
				
			||||||
        rightLineValueField = analogRead(33);
 | 
					 | 
				
			||||||
        // Save the threshold value in the persistence
 | 
					 | 
				
			||||||
        preferences.begin("sumorobot", false);
 | 
					 | 
				
			||||||
        preferences.putUInt("line_threshold", leftLineThreshold);
 | 
					 | 
				
			||||||
        preferences.end();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd.find(cmdSonar) != std::string::npos) {
 | 
					 | 
				
			||||||
        sonarThreshold = atoi(cmd.substr(5, cmd.length() - 5).c_str());
 | 
					 | 
				
			||||||
        // Save the threshold value in the persistence
 | 
					 | 
				
			||||||
        preferences.begin("sumorobot", false);
 | 
					 | 
				
			||||||
        preferences.putUInt("sonar_threshold", sonarThreshold);
 | 
					 | 
				
			||||||
        preferences.end();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd.find(cmdServo) != std::string::npos) {
 | 
					 | 
				
			||||||
        setServo(cmd.at(5), atoi(cmd.substr(6, cmd.length() - 6).c_str()));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else if (cmd.find(cmdName) != std::string::npos) {
 | 
					 | 
				
			||||||
        preferences.begin("sumorobot", false);
 | 
					 | 
				
			||||||
        preferences.putString("name", cmd.substr(4, cmd.length() - 4).c_str());
 | 
					 | 
				
			||||||
        preferences.end();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
#if DEBUG
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Start preferences persistence
 | 
					 | 
				
			||||||
  preferences.begin("sumorobot", false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create the BLE device
 | 
					 | 
				
			||||||
  Serial.println(preferences.getString("name", "SumoRobot").c_str());
 | 
					 | 
				
			||||||
  BLEDevice::init(preferences.getString("name", "SumoRobot").c_str());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  preferences.end();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create the BLE server
 | 
					 | 
				
			||||||
  bleServer = BLEDevice::createServer();
 | 
					 | 
				
			||||||
  bleServer->setCallbacks(new MyServerCallbacks());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create device info service and characteristic
 | 
					 | 
				
			||||||
  BLEService * deviceInfoService = bleServer->createService(BLEUUID((uint16_t) 0x180a));
 | 
					 | 
				
			||||||
  BLECharacteristic * modelCharacteristic = deviceInfoService->createCharacteristic(
 | 
					 | 
				
			||||||
    (uint16_t) 0x2A24, BLECharacteristic::PROPERTY_READ);
 | 
					 | 
				
			||||||
  BLECharacteristic * firmwareCharacteristic = deviceInfoService->createCharacteristic(
 | 
					 | 
				
			||||||
    (uint16_t) 0x2A26, BLECharacteristic::PROPERTY_READ);
 | 
					 | 
				
			||||||
  BLECharacteristic * manufacturerCharacteristic = deviceInfoService->createCharacteristic(
 | 
					 | 
				
			||||||
    (uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ);
 | 
					 | 
				
			||||||
  manufacturerCharacteristic->setValue("RoboKoding LTD");
 | 
					 | 
				
			||||||
  modelCharacteristic->setValue("SumoRobot");
 | 
					 | 
				
			||||||
  firmwareCharacteristic->setValue(VERSION);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create battery service
 | 
					 | 
				
			||||||
  BLEService * batteryService = bleServer->createService(BLEUUID((uint16_t) 0x180f));
 | 
					 | 
				
			||||||
  // Mandatory battery level characteristic with notification and presence descriptor
 | 
					 | 
				
			||||||
  BLE2904* batteryLevelDescriptor = new BLE2904();
 | 
					 | 
				
			||||||
	batteryLevelDescriptor->setFormat(BLE2904::FORMAT_UINT8);
 | 
					 | 
				
			||||||
	batteryLevelDescriptor->setNamespace(1);
 | 
					 | 
				
			||||||
	batteryLevelDescriptor->setUnit(0x27ad);
 | 
					 | 
				
			||||||
  // Create battery level characteristics
 | 
					 | 
				
			||||||
  batteryLevelCharacteristic = batteryService->createCharacteristic(
 | 
					 | 
				
			||||||
    (uint16_t) 0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
					 | 
				
			||||||
  batteryLevelCharacteristic->addDescriptor(batteryLevelDescriptor);
 | 
					 | 
				
			||||||
	batteryLevelCharacteristic->addDescriptor(new BLE2902());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create the BLE NUS service
 | 
					 | 
				
			||||||
  BLEService * nusService = bleServer->createService(NUS_SERVICE_UUID);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create a BLE NUS transmit characteristic
 | 
					 | 
				
			||||||
  nusTxCharacteristic = nusService->createCharacteristic(
 | 
					 | 
				
			||||||
    NUS_CHARACTERISTIC_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
 | 
					 | 
				
			||||||
  nusTxCharacteristic->addDescriptor(new BLE2902());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create a BLE NUS receive characteristics
 | 
					 | 
				
			||||||
  BLECharacteristic * nusRxCharacteristic = nusService->createCharacteristic(
 | 
					 | 
				
			||||||
    NUS_CHARACTERISTIC_RX_UUID, BLECharacteristic::PROPERTY_WRITE);
 | 
					 | 
				
			||||||
  nusRxCharacteristic->setCallbacks(new MyCallbacks());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Start the services
 | 
					 | 
				
			||||||
  deviceInfoService->start();
 | 
					 | 
				
			||||||
  batteryService->start();
 | 
					 | 
				
			||||||
  nusService->start();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Start advertising
 | 
					 | 
				
			||||||
  bleServer->getAdvertising()->start();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if DEBUG
 | 
					 | 
				
			||||||
  Serial.println("Waiting a client connection to notify...");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup BLE connection status LED
 | 
					 | 
				
			||||||
  pinMode(5, OUTPUT);
 | 
					 | 
				
			||||||
  connectionLedTimer.attach_ms(2000, blinkConnectionLed);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup the left servo PWM
 | 
					 | 
				
			||||||
  ledcSetup(1, 50, 10);
 | 
					 | 
				
			||||||
  ledcAttachPin(15, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup the right servo PWM
 | 
					 | 
				
			||||||
  ledcSetup(2, 50, 8);
 | 
					 | 
				
			||||||
  ledcAttachPin(4, 2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Phototransistor pull-ups
 | 
					 | 
				
			||||||
  pinMode(19, INPUT_PULLUP);
 | 
					 | 
				
			||||||
  pinMode(23, INPUT_PULLUP);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup battery charge detection pin
 | 
					 | 
				
			||||||
  pinMode(25, INPUT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup sensor feedback LED pins
 | 
					 | 
				
			||||||
  pinMode(16, OUTPUT);
 | 
					 | 
				
			||||||
  pinMode(17, OUTPUT);
 | 
					 | 
				
			||||||
  pinMode(12, OUTPUT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup ADC for reading phototransistors and battery
 | 
					 | 
				
			||||||
  analogSetAttenuation(ADC_11db);
 | 
					 | 
				
			||||||
  adcAttachPin(32);
 | 
					 | 
				
			||||||
  adcAttachPin(33);
 | 
					 | 
				
			||||||
  adcAttachPin(34);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup sonar timer to update it's value
 | 
					 | 
				
			||||||
  sonarTimer.attach_ms(50, updateSonarValue);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Setup battery level timer to update it's value
 | 
					 | 
				
			||||||
  batteryTimer.attach(5, updateBatteryLevel);
 | 
					 | 
				
			||||||
  updateBatteryLevel();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  // When BLE got disconnected
 | 
					 | 
				
			||||||
  if (!deviceConnected && oldDeviceConnected) {
 | 
					 | 
				
			||||||
    delay(500); // Give the bluetooth stack the chance to get things ready
 | 
					 | 
				
			||||||
    bleServer->startAdvertising(); // Restart advertising
 | 
					 | 
				
			||||||
#if DEBUG
 | 
					 | 
				
			||||||
    Serial.println("start advertising");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    oldDeviceConnected = deviceConnected;
 | 
					 | 
				
			||||||
    connectionLedTimer.attach_ms(2000, blinkConnectionLed);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // When BLE got connected
 | 
					 | 
				
			||||||
  if (deviceConnected && !oldDeviceConnected) {
 | 
					 | 
				
			||||||
    oldDeviceConnected = deviceConnected;
 | 
					 | 
				
			||||||
    connectionLedTimer.detach();
 | 
					 | 
				
			||||||
    digitalWrite(5, LOW);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user