本帖最后由 sky007 于 2023-6-8 22:44 编辑  
 
贪食蛇大作战  
      很高兴参加 Beetle ESP32-C3试用活动,这个小项目用到了 DF的C3模组以及自己打了 2812灯板PCB,与 小欧机器人平台(http://www.ovorobot.com)结合,利用 MQTT平台(www.emqx.com),实现了贪食蛇蛇对战的场景。之前,是考虑做激光对战小车的平台,但后来想,还是先做这个2812灯板的小项目来验证一下,一方面可以利用一下之前让ChartGPT写的贪食蛇程序,另一方面,也积累一下MQTT通信的学习。  
      这个项目的总体思路是:两个控制器的C3模运行并订阅MQTT主题,小欧机器人的APP作为裁判员,向MQTT主题发起游戏开启指令,控制器收到指令后分别进行各自的贪食蛇游戏,当控制器吃到果实后,会告诉小欧机器人APP,它会播报实况信息。如果有一方碰到边沿后,游戏失败,控制器会发消息到MQTT主题,小欧机器人APP会宣布比赛结果,播放图片和音效。 
      可以参考以下流程图,进行初步了解。   
 
      如果以上描述还是不够清晰,可以看看B站的这个视频——贪食蛇大作战 
 
(https://www.bilibili.com/video/BV1Ks4y1v71o?buvid=XX25F532A443BEBBE5990166D0CFF4DA69DA3&is_story_h5=false&mid=lJ9bKaN2CiRRy3cz%2BxrEKg%3D%3D&plat_id=240&share_from=ugc&share_medium=android&share_plat=android&share_source=WEIXIN&share_tag=s_i×tamp=1686188472&unique_k=xJq7RHr&up_id=142708284) 
      下面,我来介绍一下,这个项目是如何实现的,我将分Beetle ESP32-C3简介、小欧机器人平台简介、MQTT平台简介,PCB平台简介,代码解析这5个部分来说明,希望可以把我的想法描述清楚。 
 
一、Beetle ESP32-C3简介       啥也不用多说,官方资料都在这里https://wiki.dfrobot.com.cn/_SKU_DFR0868_Beetle_ESP32_C3,特别小巧的一个模组,可以做特别小巧的作品。很不幸,由于操作不慎,被我烧了一块,怪心疼的。 
 
 
 
 
二、小欧机器人平台简介 
      小欧是个很强大的在线编程平台http://www.ovorobot.com/index.html,我的理解,简单点来说,就是把手机作为单片机,利用手机的摄像头、散光灯、扬声器、显示屏、触摸屏、光线传感器、加速度传感器等等作为编程用的传感器,也可以调用网络图片、声音,也可以调用各种API,直接让小欧来使用,他甚至还是一台服务器,在线编程后可以实现各种你想要的功能。关键是开发者叶老师还对所有相关的内容,建立了知识树(小欧知识树:http://atom.ovorobot.com/index.php)可以方便的检索到要学习的内容和示例代码,而且在不断丰富,非常强大,非常方便,有兴趣可以试一下。 
   
 
 
三、MQTT平台简介 
      这个平台https://www.emqx.com/用起来还是很方便的。不用关注服务器端的情况,公共账号也能用。只要在程序里嵌入代码就可以。只要有网络,无论在哪里,都可以方便的控制。 
 
micropython这么设置: 
 
小欧图形化这么设置 
 
 
 
四、PCB平台简介 
      嘉立创EDAhttps://lceda.cn/,每月2张免费包邮优惠券,只要你会画简单的电路图,这个羊毛可以薅。 
 
五、代码解析 
       
      1、控制器A 
			
			
			- '''
 - 实验名称:小欧MQTT贪食蛇大作战
 - 版本:v1.0
 - 日期:2023.6
 - 改编:sky
 - 说明:编程实现MQTT通信,小欧宣布贪食蛇大作战开始后,2个控制器分别开始自己的游戏,吃到果实,小欧就播报,若一方超出边界,即为失败,小欧宣布比赛结果
 - 注意:上传程序时应对应相应的CLIENT_ID,控制器A是sky,控制器B是lucy
 - 小欧程序地址:http://www.ovorobot.com/blockly/blockly.html#-7La73tLfUyNsZpZcgJJYFnRVDNPWIxYzE3K
 - '''
 - import network,time                   #导入网络和时间
 - from simple import MQTTClient         #导入MQTT板块
 - from machine import Pin               #导入引脚
 - import random                         #导入随机数            
 - from neopixel import NeoPixel         #导入2812彩灯
 - import _thread
 - 
 - # 定义输出管脚
 - pin = Pin(3, Pin.OUT)
 - 
 - # 定义灯带参数
 - pixels = NeoPixel(pin, 45)
 - 
 - #定义输入管脚
 - UP= Pin(1, Pin.IN, Pin.PULL_UP)
 - DOWN= Pin(2, Pin.IN, Pin.PULL_UP)
 - LEFT= Pin(21, Pin.IN, Pin.PULL_UP)
 - RIGHT= Pin(20, Pin.IN, Pin.PULL_UP)
 - 
 - # 定义蛇的初始位置、长度、方向和颜色
 - snake_pos = [(2,3), (2,2), (2,1)]           #初始化蛇的位置,头向右【坐标点(y,x),坐标x=0~8,y=0~4】
 - snake_len = 3                               #初始化蛇的长度,3
 - snake_direction = 'right'                   #初始化蛇的方向,向右
 - snake_color = (10,0,0)                      #设定蛇的颜色
 - 
 - # 定义食物的初始位置和颜色
 - food_pos = (random.randint(0, 4), random.randint(0, 8))            #设定果实的位置
 - food_color =  (0,10,0)                                             #设定果实的颜色
 - 
 - # 初始清除灯带
 - pixels.fill((0,0,0))
 - pixels.write() 
 - 
 - #WIFI连接函数
 - def WIFI_Connect():
 - 
 -     WIFI_LED=Pin(10, Pin.OUT) #初始化WIFI指示灯
 - 
 -     wlan = network.WLAN(network.STA_IF) #STA模式
 -     wlan.active(True)                   #激活接口
 -     start_time=time.time()              #记录时间做超时判断
 - 
 -     if not wlan.isconnected():
 -         print('connecting to network...')
 -         wlan.connect('XXXXXX', 'YYYYYY') #输入WIFI账号密码
 - 
 -         while not wlan.isconnected():
 - 
 -             #LED闪烁提示
 -             WIFI_LED.value(1)
 -             time.sleep_ms(300)
 -             WIFI_LED.value(0)
 -             time.sleep_ms(300)
 - 
 -             #超时判断,15秒没连接成功判定为超时
 -             if time.time()-start_time > 15 :
 -                 print('WIFI Connected Timeout!')
 -                 wlan.active(False) #反激活WiFi
 -                 break
 - 
 -     if wlan.isconnected():
 -         #LED点亮
 -         WIFI_LED.value(1)
 - 
 -         #串口打印信息
 -         print('network information:', wlan.ifconfig())
 -         return True
 - 
 -     else:
 -         return False
 -     
 - #MQTT发布数据任务
 - def MQTT_Send(TOPIC2,msg):
 -     client.publish(TOPIC2, CLIENT_ID+msg)
 -     print(CLIENT_ID+msg)
 - 
 -     
 - #接收数据任务
 - def MQTT_Rev(TOPIC1, msg):
 -     global START
 -     print(TOPIC1, msg)
 -     if msg==b'lucylose':                                             #收到胜利的消息                             
 -         win()                                             
 -     if msg==b'start':                                              #收到开始比赛的消息
 -         START=True
 -         
 -             
 - #执行WIFI连接函数并判断是否已经连接成功
 - if WIFI_Connect():
 -     SERVER = 'broker.emqx.io'                     # MQTT服务器地址
 -     PORT = 1883                                          # MQTT服务器端口
 -     CLIENT_ID = 'sky'                                   # 客户端ID
 -     TOPIC1 = 'ovosend'                               # 小欧发送消息TOPIC名称
 -     TOPIC2 = 'ovoreceive'                            # 小欧接收消息TOPIC名称
 -     client = MQTTClient(CLIENT_ID, SERVER, PORT)  # 实例化客户端
 -     client.set_callback(MQTT_Rev)                 #配置回调函数,将反馈
 -     client.connect()                                        #链接网络
 -     client.subscribe(TOPIC1)                          #订阅主题
 -                           
 -  
 -     
 - #定义失败灯效,并发送失败消息
 - def lose():
 -     pixels.fill((10,10,10))
 -     pixels.write()
 -     MQTT_Send(TOPIC2,'lose')
 - 
 - #定义胜利灯效
 - def win():
 -     pixels.fill((10,0,0))
 -     pixels.write()
 - 
 - START=False
 - 
 - #贪食蛇主程序
 - while True:
 -     
 -       client.check_msg()                                               #调用MQTT_Send()函数,接收信息,并返回msg的值
 -       
 -       while START:
 -             # 绘制蛇和食物
 -             pixels.fill((0,0,0))
 -             pixels.write() 
 -             for pos in snake_pos:                                    #snake_pos蛇的位置
 -                 pixels[pos[0]*9+pos[1]] = snake_color
 -             pixels[food_pos[0]*9+food_pos[1]] = food_color           #food_pos食物的位置
 -             pixels.write()
 -             # 检测按键操作
 -             if snake_direction == 'right':
 -                 next_pos = (snake_pos[0][0], snake_pos[0][1]+1)          #最右边的点,y不变,x+1
 -             elif snake_direction == 'left':
 -                 next_pos = (snake_pos[0][0], snake_pos[0][1]-1)          #最右边的点,y不变,x-1
 -             elif snake_direction == 'up':
 -                 next_pos = (snake_pos[0][0]-1, snake_pos[0][1])          #最右边的点,y-1,x不变
 -             elif snake_direction == 'down':
 -                 next_pos = (snake_pos[0][0]+1, snake_pos[0][1])          #最右边的点,y+1,x不变
 -                 
 -             #按键操作后,碰到边沿
 -             if next_pos[0]<0 or next_pos[0]>4 or next_pos[1]<0 or next_pos[1]>8 or next_pos in snake_pos:
 -                 #添加生成音效
 -                 lose()
 -                 START=False
 -                 continue
 -             
 -             #按键操作后,未碰到边沿
 -             snake_pos.insert(0, next_pos)                                            #插入蛇的新坐标next_pos,位置插在index=0的前面
 - 
 -             if next_pos == food_pos:
 -                 MQTT_Send(TOPIC2,'ate')                                           #发送控制器CLIENT_ID吃到果实的消息
 -                 
 -                 snake_len += 1                                                       #吃到了食物,蛇长度加一,重新生成食物                
 -                 food_pos = (random.randint(0, 4), random.randint(0, 8))              #随机生成新的果实    
 -             else:
 -                 snake_pos.pop()                                                      #没有吃到果实,蛇的身体就把最后一个元素删除。刚才在头部增加一个元素,现在减少一个元素,这样就实现了移动的效果。
 -             time.sleep(0.5)
 - 
 -             # 检测按键操作
 -             if LEFT.value()==0:
 -                 snake_direction = 'left'
 -                 time.sleep(0.3)
 -             elif RIGHT.value()==0:
 -                 snake_direction = 'right'
 -                 time.sleep(0.3)
 -             elif UP.value()==0:
 -                 snake_direction = 'up'
 -                 time.sleep(0.3)
 -             elif DOWN.value()==0:
 -                 snake_direction = 'down'
 -                 time.sleep(0.3)
 -             print(snake_direction)
 -     
 
  复制代码
  
 
    2、控制器B 
- '''
 - 实验名称:小欧MQTT贪食蛇大作战
 - 版本:v1.0
 - 日期:2023.6
 - 改编:sky
 - 说明:编程实现MQTT通信,小欧宣布贪食蛇大作战开始后,2个控制器分别开始自己的游戏,吃到果实,小欧就播报,若一方超出边界,即为失败,小欧宣布比赛结果
 - 注意:上传程序时应对应相应的CLIENT_ID,控制器A是sky,控制器B是lucy
 - 小欧程序地址:http://www.ovorobot.com/blockly/blockly.html#-7La73tLfUyNsZpZcgJJYFnRVDNPWIxYzE3K
 - '''
 - import network,time                   #导入网络和时间
 - from simple import MQTTClient         #导入MQTT板块
 - from machine import Pin               #导入引脚
 - import random                         #导入随机数            
 - from neopixel import NeoPixel         #导入2812彩灯
 - import _thread
 - 
 - # 定义输出管脚
 - pin = Pin(3, Pin.OUT)
 - 
 - # 定义灯带参数
 - pixels = NeoPixel(pin, 45)
 - 
 - #定义输入管脚
 - UP= Pin(1, Pin.IN, Pin.PULL_UP)
 - DOWN= Pin(2, Pin.IN, Pin.PULL_UP)
 - LEFT= Pin(21, Pin.IN, Pin.PULL_UP)
 - RIGHT= Pin(20, Pin.IN, Pin.PULL_UP)
 - 
 - # 定义蛇的初始位置、长度、方向和颜色
 - snake_pos = [(2,3), (2,2), (2,1)]           #初始化蛇的位置,头向右【坐标点(y,x),坐标x=0~8,y=0~4】
 - snake_len = 3                               #初始化蛇的长度,3
 - snake_direction = 'right'                   #初始化蛇的方向,向右
 - snake_color = (10,0,0)                      #设定蛇的颜色
 - 
 - # 定义食物的初始位置和颜色
 - food_pos = (random.randint(0, 4), random.randint(0, 8))            #设定果实的位置
 - food_color =  (0,10,0)                                             #设定果实的颜色
 - 
 - # 初始清除灯带
 - pixels.fill((0,0,0))
 - pixels.write() 
 - 
 - #WIFI连接函数
 - def WIFI_Connect():
 - 
 -     WIFI_LED=Pin(10, Pin.OUT) #初始化WIFI指示灯
 - 
 -     wlan = network.WLAN(network.STA_IF) #STA模式
 -     wlan.active(True)                   #激活接口
 -     start_time=time.time()              #记录时间做超时判断
 - 
 -     if not wlan.isconnected():
 -         print('connecting to network...')
 -         wlan.connect('XXXXXX', 'YYYYYY') #输入WIFI账号密码
 - 
 -         while not wlan.isconnected():
 - 
 -             #LED闪烁提示
 -             WIFI_LED.value(1)
 -             time.sleep_ms(300)
 -             WIFI_LED.value(0)
 -             time.sleep_ms(300)
 - 
 -             #超时判断,15秒没连接成功判定为超时
 -             if time.time()-start_time > 15 :
 -                 print('WIFI Connected Timeout!')
 -                 wlan.active(False) #反激活WiFi
 -                 break
 - 
 -     if wlan.isconnected():
 -         #LED点亮
 -         WIFI_LED.value(1)
 - 
 -         #串口打印信息
 -         print('network information:', wlan.ifconfig())
 -         return True
 - 
 -     else:
 -         return False
 -     
 - #MQTT发布数据任务
 - def MQTT_Send(TOPIC2,msg):
 -     client.publish(TOPIC2, CLIENT_ID+msg)
 -     print(CLIENT_ID+msg)
 - 
 -     
 - #接收数据任务
 - def MQTT_Rev(TOPIC1, msg):
 -     global START
 -     print(TOPIC1, msg)
 -     if msg==b'skylose':                                             #收到胜利的消息                             
 -         win()                                             
 -     if msg==b'start':                                              #收到开始比赛的消息
 -         START=True
 -         
 -             
 - #执行WIFI连接函数并判断是否已经连接成功
 - if WIFI_Connect():
 -     # 设置播放音效
 -     SERVER = 'broker.emqx.io'                     # MQTT服务器地址
 -     PORT = 1883                                   # MQTT服务器端口
 -     CLIENT_ID = 'lucy'                             # 客户端ID
 -     TOPIC1 = 'ovosend'                           # 小欧发送消息TOPIC名称
 -     TOPIC2 = 'ovoreceive'                         # 小欧接收消息TOPIC名称
 -     client = MQTTClient(CLIENT_ID, SERVER, PORT)  # 实例化客户端
 -     client.set_callback(MQTT_Rev)                 #配置回调函数,将反馈
 -     client.connect()    
 -     client.subscribe(TOPIC1)                      #订阅主题
 -                           
 -  
 -     
 - #定义失败灯效,并发送失败消息
 - def lose():
 -     pixels.fill((10,10,10))
 -     pixels.write()
 -     MQTT_Send(TOPIC2,'lose')
 - 
 - #定义胜利灯效
 - def win():
 -     pixels.fill((10,0,0))
 -     pixels.write()
 - 
 - START=False
 - 
 - #贪食蛇主程序
 - while True:
 -     
 -       client.check_msg()                                               #调用MQTT_Send()函数,接收信息,并返回msg的值
 -       
 -       while START:
 -             # 绘制蛇和食物
 -             pixels.fill((0,0,0))
 -             pixels.write() 
 -             for pos in snake_pos:                                    #snake_pos蛇的位置
 -                 pixels[pos[0]*9+pos[1]] = snake_color
 -             pixels[food_pos[0]*9+food_pos[1]] = food_color           #food_pos食物的位置
 -             pixels.write()
 -             # 检测按键操作
 -             if snake_direction == 'right':
 -                 next_pos = (snake_pos[0][0], snake_pos[0][1]+1)          #最右边的点,y不变,x+1
 -             elif snake_direction == 'left':
 -                 next_pos = (snake_pos[0][0], snake_pos[0][1]-1)          #最右边的点,y不变,x-1
 -             elif snake_direction == 'up':
 -                 next_pos = (snake_pos[0][0]-1, snake_pos[0][1])          #最右边的点,y-1,x不变
 -             elif snake_direction == 'down':
 -                 next_pos = (snake_pos[0][0]+1, snake_pos[0][1])          #最右边的点,y+1,x不变
 -                 
 -             #按键操作后,碰到边沿
 -             if next_pos[0]<0 or next_pos[0]>4 or next_pos[1]<0 or next_pos[1]>8 or next_pos in snake_pos:
 -                 #添加生成音效
 -                 lose()
 -                 START=False
 -                 continue
 -             
 -             #按键操作后,未碰到边沿
 -             snake_pos.insert(0, next_pos)                                            #插入蛇的新坐标next_pos,位置插在index=0的前面
 - 
 -             if next_pos == food_pos:
 -                 MQTT_Send(TOPIC2,'ate')                                           #发送控制器CLIENT_ID吃到果实的消息
 -                 
 -                 snake_len += 1                                                       #吃到了食物,蛇长度加一,重新生成食物                
 -                 food_pos = (random.randint(0, 4), random.randint(0, 8))              #随机生成新的果实    
 -             else:
 -                 snake_pos.pop()                                                      #没有吃到果实,蛇的身体就把最后一个元素删除。刚才在头部增加一个元素,现在减少一个元素,这样就实现了移动的效果。
 -             time.sleep(0.5)
 - 
 -             # 检测按键操作
 -             if LEFT.value()==0:
 -                 snake_direction = 'left'
 -                 time.sleep(0.3)
 -             elif RIGHT.value()==0:
 -                 snake_direction = 'right'
 -                 time.sleep(0.3)
 -             elif UP.value()==0:
 -                 snake_direction = 'up'
 -                 time.sleep(0.3)
 -             elif DOWN.value()==0:
 -                 snake_direction = 'down'
 -                 time.sleep(0.3)
 -             print(snake_direction)
 -     
 
  复制代码
  
 
      3、小欧机器人 
- http://www.ovorobot.com/blockly/blockly.html#-lWuq8T4AVcrqW50Mtycgp2jFnvrgUEmJk8g
 
  复制代码
  
 
 
      目前,这个项目还没有很完善,PCB还没最终完成,无源蜂鸣器需要加上去,那样联网成功、吃到果实都可以在本地发出提示音,后面再完善。另外,PCB是裸露的,需要打印个外壳,把板子包裹起来,手感会好很多。还有,利用这个控制器,还可以做其他的小项目,比如小欧MIDI,来作为乐器也是不错的,还可以做一个猫抓老鼠的互动游戏,好像也挺好玩…… 
 
      但是,先告一段落,以后再折腾吧,先得把MQTT激光对战小车捣腾起来。 
 
 
 
 
 
 
 |