盲人看电视问题引发的思考:广电的卫星锅经常断信号,维护不方便(锅普遍放在房顶,卡易锁住,更新升级)。对于盲人来讲,处理这些问题,往往难度较大。爬楼顶危险性高,解锁等服务无人愿意提供,请人维修又比较费事。盲人操作电脑似乎也不太现实,为了解决这个问题,计划用废弃电脑连接电视机,将电脑变成简单的电视盒子,具备语音提示功能,使用电视遥控器即可进行节目观看。
总体思路:
将部分电视剧,电影,网页链接等资源放入电脑中指定的文件夹。利用软件配合遥控器的方式,实现普通电视类似功能。当点击遥控器时,提供节目选择窗口和对应的语音提示,程序根据遥控信号打开文件或关闭文件。
作为一个小白,使用python来实现代码部分感觉更简单,所以也就这么干了,总体感觉还好。
提示:盲人使用可删除代码中图像界面部分代码,正常人使用可删除语音提示部分代码。
注:我使用的是python3.7,IDE是:pycharm。

基本组成

软件部分:
1 主程序模块TVC_main.py:负责初始化和命令处理;
2 语音模块voice_BAT.py:负责语音合成,语音播放;
3 图像模块TVC_Window.py:负责图像显示;
4 遥控模块SerialThread.py:负责接收遥控器信号;
5 文件模块open_file_dir.py:负责文件或文件夹的具体操作;

总共包含下面7个py文件,一个图标和一个代码生成的MP3文件。

硬件部分:
电视机,电脑主机,VGA或HDMI数据线,arduino 板,1838红外接收头等。
硬件连接:电视机和电脑主机用数据线直接连,arduino板直接插入到主机USB口即可。arduino和1838的连接网上教程很多,不再赘述。

1 主程序TVC_main.py

主程序模块TVC_main.py主要就是初始化和实现命令处理循环,没有什么好解释的,其实就是一个while循环而已,看代码更容易懂,直接上代码:

import os, time, win32gui
from Command_Class import RemoteCmd
import win32api, win32con
from voice_BAT import convert_mp3, play_mp3, save_mp3
from SerialThread import get_order
from open_file_dir import play, back_dir
from pykeyboard.windows import PyKeyboard
from TVC_Window import TVCWindowThread, get_TVCHWD# 播放MP3,如果没有就先创建,然后播放;注意区分play_voice() 和 play_mp3()函数
# 参数:1-voice文件路径;2-要搜索的文件名;3-voice文件夹的文件名列表
def play_voice(path_voice, filename, list_voice):if filename not in list_voice:# 首先,提醒第一个文件名为语音。该函数负责语音的寻找或生成工作。convert_mp3(filename)# 将生成的MP3文件保存到voice文件中save_mp3(path_voice, filename)print("语音合成结束!")# 播放语音,参数是文件路径和文件名,但不包含后缀名play_mp3(path_voice + filename)'''作用:实现对一个文件夹内所有的文件的读取,当用户选择某个文件时,语音提示文件名,如用户选择打开,则打开该文件。同时具有图形界面显示,辅助选择。盲人可去掉图形界面,正常人则可去掉语音部分。问题:语音部分暂时依靠网络环境,速度可能较慢,无网络可能不能正常运行。
'''##############################################
# 【1】 判断两个文件路径是否正确
##############################################
# 根据自己的路径情况修改path和path_voice;
# flag:屏幕刷新标志;index:当前文件名在list中的索引;TVCHWD: 图形界面句柄;PlayHanle:播放器的句柄
lists_dict = {"path": "C:\\movie\\", "strlists": " ", "flag": 0, "index": 0, "TVCHWD": 0, "PlayHanle": 0}
last_path = []  # 路径名栈path = lists_dict["path"]
path_voice = "C:\\voice\\"
list_voice = []if os.path.exists(path) & os.path.exists(path_voice):print("资源 和 voice 文件夹文件夹存在!")
else:print("资源 或voice文件夹不存在,请确保文件夹的名字和路径正确!")win32api.MessageBox(0, "资源 或voice文件夹不存在,请联系工作人员!", "提示信息:", win32con.MB_OK)exit(1)##################################################################
# 【2】 读取路径中的所有文件名到lists;加载对应的voice文件夹lists_voice
##################################################################
# 加载 资源 文件夹到list###################
if os.path.isdir(path):lists_dict["strlists"] = os.listdir(path)print(lists_dict["strlists"])# 加载voice文件夹到list_voice###################
if os.path.isdir(path_voice):list_voice = os.listdir(path_voice)print(list_voice)
# 去掉后缀名 mp3
for index in range(len(list_voice)):temp = list_voice[index]# 先按照 '.'切割成多个字符串,然后取其中的第一个字符串list_voice[index] = temp.split('.')[0]
print(list_voice)##################################################################
# 【3】 开启图形界面线程;开启后手动对lists_dict["TVCHWD"赋值
##################################################################
TVC = TVCWindowThread(lists_dict)
TVC.start()
lists_dict["TVCHWD"] = get_TVCHWD()
win32gui.SetActiveWindow(lists_dict["TVCHWD"])
##################################################################
# 【4】 语音提示用户第一个文件名,并等待用户动作,即是打开还是选择下一个,或者返回总目录
##################################################################
'''作用:提示当前位置文件名称;执行时先在voice文件夹搜索有没有对应的MP3,如果有直接播放如果没有就需要调用 百度AI 来进行识别,识别的语音保存到voice文件夹,然后在播放。
'''
# 播放第一个语音############################
# 播放第一个文件的mp3文件,资料lists 是包含文件名后缀的,音频部分的list_voice是不包含后缀名的,注意区别!
play_voice(path_voice, lists_dict["strlists"][0].split('.')[0], list_voice)#####################################################################################
# 【5】 等待用户输入,然后执行相应的动作
# ###################################################################################
# order 表示从串口接收到的遥控器的 指令编码
order = 0
cmd = RemoteCmd()  # 常量对象,要用到的常量宏,遥控器不同修改该类即可
order_list = [0, 0]  # 利用引用变量list来传递命令值,必须初始化为[0,0]
get_order(order_list)  # 启动红外命令线程,循环读取命令,并修改order_list[1]的值while True:# 每次循环先读取串口传递的命令,若没有新命令等待0.1秒,循环读该命令。# 若有命令则取出命令,并将命令参数strlist[1]恢复原始值order = order_list[1]if order == 0:time.sleep(0.1)continueelse:order_list[1] = 0print("当前索引:", lists_dict["index"])if order == cmd.DOWN:if lists_dict["index"] >= (len(lists_dict["strlists"]) - 1):print("当前索引:", lists_dict["index"])play_mp3(path_voice + "请向上选择")continuelists_dict["index"] = lists_dict["index"] + 1# 设置窗口加框标志flag,使得窗口重绘lists_dict["flag"] = 2# 播放下一个文件的语音提示temp_lists = lists_dict["strlists"]temp_index = lists_dict["index"]# 播放语音play_voice(path_voice, temp_lists[temp_index].split('.')[0], list_voice)elif order == cmd.UP:  # 命令2:向上选择文件# 如果是最后一个文件了,播放语音提醒反向选择if lists_dict["index"] == 0:play_mp3(path_voice + "请向下选择")continuelists_dict["index"] = lists_dict["index"] - 1# 设置窗口加框标志flag,使得窗口重绘lists_dict["flag"] = 2# 语音播放temp_lists = lists_dict["strlists"]temp_index = lists_dict["index"]play_voice(path_voice, temp_lists[temp_index].split('.')[0], list_voice)elif order == cmd.PLAY:  # 命令3:播放文件temp_lists = lists_dict["strlists"]temp_index = lists_dict["index"]play(lists_dict["path"], temp_lists[temp_index], lists_dict, last_path)elif order == cmd.RETURN:back_dir(lists_dict, last_path)temp_lists = lists_dict["strlists"]temp_index = lists_dict["index"]play_voice(path_voice, temp_lists[temp_index].split('.')[0], list_voice)elif order == cmd.POWEROFF:print("power off")os.system("shutdown -s -t 0")elif order == cmd.VOLP:print("order:", order)k1 = PyKeyboard()k1.tap_key(win32con.VK_VOLUME_UP)elif order == cmd.VOLD:print("order:", order)k1 = PyKeyboard()k1.tap_key(win32con.VK_VOLUME_DOWN)

2 语音模块voice_BAT.py

思路:主要是利用百度在线语音合成技术进行文本转语音,已经转好的语音会保存在电脑一个固定的voice文件夹,每次播放语音时先在该文件夹搜索,如果没有则调用百度进行转换。缺点是依靠网络,效率较低,优点是实现起来极其简单。有空闲时间的可以做自己的语音转换模块。
至于百度语音转换,只需要我们进行简单的注册即可,网站提供基本代码,基本不用改动,免费调用次数远远可以满足普通人的需求,当然也可以试试其他家的语音服务。
模块功能:主要实现语音合成,语音播放,语音保存三个部分。

# coding=utf-8
import sys
import json
import pygame
from shutil import copyfile
'''作用:借助百度AI,在线对汉字进行语音合成
'''
IS_PY3 = sys.version_info.major == 3
if IS_PY3:from urllib.request import urlopenfrom urllib.request import Requestfrom urllib.error import URLErrorfrom urllib.parse import urlencodefrom urllib.parse import quote_plus
else:print("此处为加载python2 对应的包")# 删除了部分代码# 必须修改成自己申请到的密匙
API_KEY = 'xxxxxxxxx'
SECRET_KEY = 'xxxxxxxxxxxxx'# 发音人选择, 基础音库:0为度小美,1为度小宇,3为度逍遥,4为度丫丫,
# 精品音库:5为度小娇,103为度米朵,106为度博文,110为度小童,111为度小萌,默认为度小美
PER = 1
# 语速,取值0-15,默认为5中语速
SPD = 3
# 音调,取值0-15,默认为5中语调
PIT = 5
# 音量,取值0-9,默认为5中音量
VOL = 8
# 下载的文件格式, 3:mp3(default) 4: pcm-16k 5: pcm-8k 6. wav
AUE = 3FORMATS = {3: "mp3", 4: "pcm", 5: "pcm", 6: "wav"}
FORMAT = FORMATS[AUE]CUID = "123456PYTHON"TTS_URL = 'http://tsn.baidu.com/text2audio'class DemoError(Exception):pass"""  TOKEN start """
TOKEN_URL = 'http://openapi.baidu.com/oauth/2.0/token'
SCOPE = 'audio_tts_post'  # 有此scope表示有tts能力,没有请在网页里勾选def fetch_token():print("fetch token begin")params = {'grant_type': 'client_credentials','client_id': API_KEY,'client_secret': SECRET_KEY}post_data = urlencode(params)if (IS_PY3):post_data = post_data.encode('utf-8')req = Request(TOKEN_URL, post_data)try:f = urlopen(req, timeout=5)result_str = f.read()except URLError as err:print('token http response http code : ' + str(err.code))result_str = err.read()if (IS_PY3):result_str = result_str.decode()print(result_str)result = json.loads(result_str)print(result)if ('access_token' in result.keys() and 'scope' in result.keys()):if not SCOPE in result['scope'].split(' '):raise DemoError('scope is not correct')print('SUCCESS WITH TOKEN: %s ; EXPIRES IN SECONDS: %s' % (result['access_token'], result['expires_in']))return result['access_token']else:raise DemoError('MAYBE API_KEY or SECRET_KEY not correct: access_token or scope not found in token response')"""  TOKEN end """# 参数:TEXT为要转换成语音的字符串
def convert_mp3(TEXT):token = fetch_token()tex = quote_plus(TEXT)  # 此处TEXT需要两次urlencodeprint(tex)params = {'tok': token, 'tex': tex, 'per': PER, 'spd': SPD, 'pit': PIT, 'vol': VOL, 'aue': AUE, 'cuid': CUID,'lan': 'zh', 'ctp': 1}  # lan ctp 固定参数data = urlencode(params)print('test on Web Browser' + TTS_URL + '?' + data)req = Request(TTS_URL, data.encode('utf-8'))has_error = Falsetry:f = urlopen(req)result_str = f.read()headers = dict((name.lower(), value) for name, value in f.headers.items())has_error = ('content-type' not in headers.keys() or headers['content-type'].find('audio/') < 0)except  URLError as err:print('asr http response http code : ' + str(err.code))result_str = err.read()has_error = Truesave_file = "error.txt" if has_error else 'result.' + FORMATwith open(save_file, 'wb') as of:of.write(result_str)if has_error:if (IS_PY3):result_str = str(result_str, 'utf-8')print("tts api  error:" + result_str)print("result saved as :" + save_file)# 播放语音mp3文件,路径path 不需要后缀名
def play_mp3(path):pygame.mixer.init()pygame.mixer.music.load(path + ".mp3")pygame.mixer.music.play()while pygame.mixer.music.get_busy() == True:pass# 将获得的MP3文件保存到指定的voice目录中
def save_mp3(path_voice,name):src = sys.path[0] + "\\result.mp3"copyfile(src, path_voice + name + ".mp3")if __name__ == '__main__':token = fetch_token()TEXT = '中国风'tex = quote_plus(TEXT)  # 此处TEXT需要两次urlencodeprint(tex)params = {'tok': token, 'tex': tex, 'per': PER, 'spd': SPD, 'pit': PIT, 'vol': VOL, 'aue': AUE, 'cuid': CUID,'lan': 'zh', 'ctp': 1}  # lan ctp 固定参数data = urlencode(params)print('test on Web Browser' + TTS_URL + '?' + data)req = Request(TTS_URL, data.encode('utf-8'))has_error = Falsetry:f = urlopen(req)result_str = f.read()headers = dict((name.lower(), value) for name, value in f.headers.items())has_error = ('content-type' not in headers.keys() or headers['content-type'].find('audio/') < 0)except  URLError as err:print('asr http response http code : ' + str(err.code))result_str = err.read()has_error = Truesave_file = "error.txt" if has_error else 'result.' + FORMATwith open(save_file, 'wb') as of:of.write(result_str)if has_error:if (IS_PY3):result_str = str(result_str, 'utf-8')print("tts api  error:" + result_str)print("result saved as :" + save_file)

3 图像模块TVC_Window.py

这一部分折腾了一下,pyQT,qt,VC++,tkinter都试了一下,最后还是决定使用tkinter的canvas来实现。主要原因是作为小白的我很难驾驭看起来很简单的控件,entry,listbox,listview之类。图像界面搞好了,通信又是问题,作为小白,不堪其扰,只好决定自己绘制界面,通信问题也就容易解决了。
思路:将图像界面封装为一个类,调用时创建类对象即可。为了保证图像的自动刷新,在类中开辟 了一个新的线程,该线程循环读取系统全局变量lists_dict字典中的flag标志,根据这个标志的改变情况自动刷新图像界面。关于lists_dict这个字典,主程序中注释的很清楚,不再赘述。
具体代码:

from tkinter import *
import time,threading
from EmunWindow import get_TVCHWD'''说明:绘制图形窗口,实现路径和列表的显示。特点:引入多线程,可动态传递数据和窗口重绘描述:取消了键盘响应事件,参数传递和图像刷新由多线程来处理。数据传递依靠一个主线程的字典lists_dict即可。
'''
# Tk 是tkinter中定义的主窗口类
class TVC_Window(Tk):def __init__(self,lists_dict):super().__init__()      # 父类初始化# self.pack() # 若继承 tk.Frame ,此句必须有!# 主窗口配置信息self.title('TVC')self.iconbitmap('wen.ico')# 主窗口的大小,和位置,左上角(100,100)self.geometry('400x500+100+100')self.resizable(width=False, height=False)# 程序参数/数据self.TVCHWD = lists_dict["TVCHWD"]self.lists = lists_dict["strlists"]self.path = lists_dict["path"]self.listsize = len(self.lists)  # 字符列表的成员数量self.index = lists_dict["index"]                  # 当前矩形框位置文本的索引号self.frame = 0                  # 一页只能显示15行,所以要分成页,这表示页的索引self.offset = 65                # 文字绘制起始偏移self.offse_rect = 52            # 矩形框的Y 方向起始偏移self.Left_txt = 15              # 文字位置的起始 X 偏移self.offset_txt = 28            # 文字的行间距离self.listmax = 15               # 文本区最大能显示的行数self.cfont = ('微软雅黑', 14)self.rectID = 0                 # 绘制的矩形框的IDself.path_rectID = 0            # 路径矩形框的IDself.list_rectID = 0            # 文字列表框的IDself.path_txtID = 0             # 路径文字的ID# 创建画布(填充窗口,所以不用定义尺寸)self.cv = Canvas(self, background='AliceBlue')# 组件扩展并x,y方向填充满窗口rootself.cv.pack(fill=BOTH, expand=YES)# 配置程序界面self.setupUI()# 开启数据接收线程,用到了传数据的字典做参数new_thread = myThread(self,lists_dict)# 开启线程new_thread.start()# 创建界面组件def setupUI(self):# 绘制路径方框,大小位置,边框,填充,边框宽度;颜色参考tkinter的颜色定义self.path_rectID = self.cv.create_rectangle(10, 10, 390, 40, outline='Gray', fill="White", width=2)print("路径矩形框的ID:", self.path_rectID)# 绘制文件名方框self.list_rectID = self.cv.create_rectangle(10, 50, 390, 490, outline='Gray', fill="White", width=2)print("文本框的ID:", self.list_rectID)# 输入路径文本self.path_txtID = self.cv.create_text(self.Left_txt, 25, text=self.path, font=self.cfont, fill='gray',anchor=W, justify=LEFT)print("路径ID:", self.path_txtID)# 输入文件名列表if self.listsize <= self.listmax:for i in range(self.listsize):print(i, self.lists[i])temp = self.lists[i]position = self.offset + i * self.offset_txt  # 文字上下间隔 28 像素self.cv.create_text(self.Left_txt, position, text=temp, font=self.cfont, fill='black', anchor=W,justify=LEFT)else:for i in range(self.listmax):temp = self.lists[i]position = self.offset + i * self.offset_txt  # 文字上下间隔 28 像素self.cv.create_text(self.Left_txt, position, text=temp, font=self.cfont, fill='black', anchor=W,justify=LEFT)# 给第一个文件名加矩形框self.rectID = self.cv.create_rectangle(12, 52, 388, 80, outline='blue', fill="", width=4)# 画布得到焦点,从而可以响应按键事件self.cv.focus_set()# 重绘窗口:指负责窗口重绘(路径和文件列表),不负责矩形框def draw_window(self):  # 负责窗口重绘# 首先清除所有绘制的文字和矩形框,保留基本框和路径框tempID = self.cv.find_all()print("draw_window画布的所有组件ID:",tempID)for i in range(len(tempID)):if i <= 1:continueprint("待删除的ID index:",i)print("待删除的组员ID:",tempID[i])self.cv.delete(tempID[i])# 输入路径文本self.path_txtID = self.cv.create_text(self.Left_txt, 25, text=self.path, font=self.cfont, fill='gray',anchor=W, justify=LEFT)# 如果是最后一帧就要计算具体有多少项内容,如果不是最后一帧都是绘制15个数据# 1:15属于第一页,16:30属于第二页,15-0.5=14.5,14.5//15=0;下面使用的是整除符号://# 判断当前帧是否是最后一帧if self.frame == ((self.listsize -0.5) // self.listmax):# 如果是最后一页,判断这一页有多少元素,逐个加载重绘#for i in range(((self.listsize - 1) % self.listmax) + 1):temp = self.lists[i + self.frame * self.listmax]position = self.offset + i * self.offset_txt  # 文字上下间隔 28 像素self.cv.create_text(self.Left_txt, position, text=temp, font=self.cfont, fill='black', anchor=W, justify=LEFT)else:for i in range(15):  # i = 0:14temp = self.lists[i + self.frame * self.listmax]position = self.offset + i * self.offset_txt  # 文字上下间隔 28 像素self.cv.create_text(self.Left_txt, position, text=temp, font=self.cfont, fill='black', anchor=W, justify=LEFT)# 重绘矩形框:根据self.index索引重绘矩形框,若翻页则重绘窗口然后再加框def draw_rect(self):  # 负责文字加框;先检测帧和窗口重绘,然后再加框# 先删除旧的矩形框if self.rectID != 0:self.cv.delete(self.rectID)# 判断索引是否在当前帧内,如果不在就加载下一页frame_temp = self.index // self.listmax  # 第16个索引是15,正好分在第1帧if frame_temp != self.frame:self.frame = frame_tempself.draw_window()y1 = self.offse_rect + (self.index % self.listmax) * self.offset_txty2 = y1 + self.offset_txtself.rectID = self.cv.create_rectangle(12, y1, 388, y2, outline='blue', fill="", width=4)else:y1 = self.offse_rect + (self.index % self.listmax) * self.offset_txty2 = y1 + self.offset_txtself.rectID = self.cv.create_rectangle(12, y1, 388, y2, outline='blue', fill="", width=4)# 图形窗口的刷新线程,继承父类threading.Thread
# 构造函数参数:canvas对象
class myThread(threading.Thread):def __init__(self, TVCobject, lists_dict):# 首先调用父类的初始化函数完成初始化threading.Thread.__init__(self)# 填写自定义的初始化部分self.TVCobject = TVCobjectself.ld = lists_dictdef run(self):  # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数print("myThread进程每隔0.2秒,扫描一次flag!")while True:if self.ld["flag"] == 1:# 刷新图形窗口基本参数self.TVCobject.lists = self.ld["strlists"]self.TVCobject.path = self.ld["path"]self.TVCobject.listsize = len(self.TVCobject.lists)self.TVCobject.index = 0self.TVCobject.frame = 0self.TVCobject.rectID = 0# 刷新图形界面:路径、文件列表和方框print("myThread刷新后的path_txtID:",self.TVCobject.path_txtID)self.TVCobject.draw_window()self.TVCobject.draw_rect()print("myThread修改字典flag为0")self.ld["flag"] = 0elif self.ld["flag"] == 2:# 更新矩形框的索引值,然后重绘矩形框即可self.TVCobject.index = self.ld["index"]self.TVCobject.draw_rect()print("myThread重绘矩形框!")self.ld["flag"] = 0else:time.sleep(0.1)continue# 图形窗口主线程
class TVCWindowThread(threading.Thread):def __init__(self, lists_dict):# 首先调用父类的初始化函数完成初始化threading.Thread.__init__(self)self.ld = lists_dictdef run(self):# 填写自定义的初始化部分root = TVC_Window(self.ld)root.mainloop()if __name__ == "__main__":strlists = ["你好", "他很好", "都不是很好", "他很好", "都不是很好", "他很好"]path = "C:\\VOICE\\"# path: 路径# strlists: 文件名列表,# flag: 刷新标志(默认是0,为1表示需要刷新窗口,是2表示刷新矩形框位置)# index: 矩形框文字在list中的索引(初始值0)# TVCHWD: 图形窗口的句柄# 当flag为 1 时,index 必须为 0;lists_dict = {"path":path, "strlists":strlists, "flag":0, "index":0,"TVCHWD":0}# 先创建图形界面主线程# 主线程创建图形窗口并开启一个窗口刷新线程。图形界面有两个线程:主线程和刷新线程TVC = TVCWindowThread(lists_dict)TVC.start()# 将创建的TVC窗口句柄传递个lists_dict字典lists_dict["TVCHWD"] = get_TVCHWD()print("TVC窗口句柄:",lists_dict["TVCHWD"])time.sleep(5)# 刷新图形窗口信息print("主进程后续代码!")lists_dict["path"] = "d:\\xxx\\"lists_dict["strlists"] = ["jsdlkjf","jsdlkfj","45978","jsdlkjf","jsdlkfj","45978", "jsdlkjf","jsdlkfj","45978","jsdlkfj","45978","jsdlkjf","jsdlkfj","45978", "jsdlkjf","jsdlkfj","45978"]lists_dict["flag"] = 1# 修改矩形框位置# flag =2 表示要刷新矩形框位置;index 具体指定刷新位置,即当前字符串在strlists中的索引值。time.sleep(1)lists_dict["flag"] = 2lists_dict["index"] = 2

图形界面效果:

当我们使用遥控器上下选择时,不但会语音读出文件名,图像上的蓝色选择框也会移动到指定位置。这样盲人和正常人都可以方便的操作。

4 遥控模块SerialThread.py

遥控部分有两部分代码:一个是电脑端接收数据的代码,另一个是arduino接收红外信号的代码。
电脑端代码,其实就是一个while循环读取串口而已。需要注意的是根据具体遥控器键值修改代码中的键值。另外还需要参考Command_Class中定义的命令值。这里我偷懒,可以自己替换一下,代码会更简单。
代码:

import serial, time, threading''' 作用:开辟线程,循环从串口读出数据 注意:不同的遥控器发送的命令字符串可能不同,更换遥控器需要更改下面的参数配置,即if 语句中的 str使用:直接调用get_order()即可,注意参数必须是list类型,注意修改端口号
'''
# python 中的参数是没有引用的,当参数是list或dict时,自动是引用,其他自动是传值
# strlist 必须定义为一个list类型
def receive(strlist):try:portx = "COM6"bps = 9600timex = 5# 打开串口,并得到串口对象ser = serial.Serial(portx,bps,timeout = timex)# 循环取数据while True:if ser.in_waiting:# 读取缓冲区中的数据str = ser.read(ser.in_waiting)# 将命令简化保存,参考类Command_Class中常量定义if str == b'44BB53AC\r\n':strlist[1] = 1elif str == b'44BB4BB4\r\n':strlist[1] = 2#print("当前DOWN被按下:",2)elif str == b'44BB3BC4\r\n':strlist[1] = 3breakelif str == b'44BB738C\r\n':strlist[1] = 5elif str == b'44BBA956\r\n':strlist[1] = 6elif str == b'44BB01FE\r\n':strlist[1] = 7elif str == b'44BB817E\r\n':strlist[1] = 8else:print("收到未定义数据:", str)# 不要让循环太快,很消耗CPUtime.sleep(0.1)ser.close()       #关闭串口except Exception as e:print("---串口红外接收线程异常---:",e)# strlist 必须定义为一个list类型,返回值放在list[1]中,必须初始化该list
def get_order(strlist):# 第一参数是线程入口函数,第二参数是函数的参数,第三个是线程名称thread_order = threading.Thread(target = receive,args=(strlist,), name='receive_thread')thread_order.start()if __name__ == "__main__":strlist = [0,0]get_order(strlist)print(strlist[1])

arduino上的代码具体参考:
http://www.taichi-maker.com/homepage/arduino-tutorial-index/intelligent-index/intelligent-16/
还需要给IDE安装库文件,下载地址:
http://www.taichi-maker.com/homepage/download/#library-download
都很简单,这里不再赘述。

5 文件模块open_file_dir.py

文件模块主要负责文件文件或文件夹的具体操作,包括文件打开,文件关闭,文件夹打开和返回;其实内部只有三个可用函数而已,如下图:


其他看注释,注释里面解释的非常清楚。

6 EmunWindow.py和Command_Class.py

前者是用来枚举窗口的函数定义在里面,后者是保存命令常量的一个类。要注意的是为了偷懒常量封装并没有仔细做,代码量小倒是也无所谓O(∩_∩)O哈哈~。代码一看便知:
EmunWindow.py的代码:

import win32.win32gui as win32gui
from pymouse.windows import PyMouse
import win32.win32clipboard as copy_board
import time'''
作用:返回图形窗口TVC的句柄
'''
hwnd_title = dict()def get_all_hwnd(hwnd, mouse):if win32gui.IsWindow(hwnd) and win32gui.IsWindowEnabled(hwnd) and win32gui.IsWindowVisible(hwnd):# update() 函数把字典dict2的键/值对更新到dict里。hwnd_title.update({hwnd: win32gui.GetWindowText(hwnd)})# 枚举所有符合条件的窗口
def get_TVCHWD():while True:win32gui.EnumWindows(get_all_hwnd, 0)for h, t in hwnd_title.items():print(h,t)if t == "TVC":return helse:time.sleep(1)continue

Command_Class.py代码:

'''# python 没有宏定义和常量定义,用一个类定义成常量
'''class RemoteCmd(object):def __init__(self):self.UP = 1self.DOWN = 2self.POWEROFF = 3self.POWERON = 4self.PLAY = 5self.RETURN = 6self.VOLP = 7self.VOLD = 8if __name__ == "__main__":# 使用前创建RemoteCmd类对象,然后使用其中的常量cmd = RemoteCmd()print("命令代号:",cmd.DOWN)print("命令代号:", cmd.PLAY)

软件的代码部分到此就结束啦,是不是很简单!O(∩_∩)O哈哈~

硬件部分

这个电视连电脑的数据线插拔很简单,arduino部分上面网址中有详细描述就不多说了。要说的就是需要根据自己遥控器上按键的具体码值修改SerialThread.py文件中具体的码值:


下载个串口调试助手,很容易搞清楚遥控器上每个按键的具体键码,这里就不废话啦。

使用

1 使用前必须创建两个文件夹,一个存放资源,一个存放语音MP3文件并将代码路径进行修改。
2 需要有网络环境,否则语音部分会出问题,不能正常提示。
3 文件名中不能使用符号“ . ”,这个是软件判断文件后缀名的标志。
4 文件中定义的按键有限,部分按键并没有实现功能。暂时使用的仅有:上键,下键,播放键,音量+,音量—,返回键。

总结

这里没有讲如何打包发布代码,还有如何开机自启动该程序。另外,代码中虽然实现了遥控关机功能,但是没有实现遥控开机功能。需要的可自行arduino接线到电脑主机,然后添加代码实现。代码的可靠性尚不清楚,个人暂时未发现问题。

python改造电脑解决盲人看电视问题的试验相关推荐

  1. 新版直播星彻底解决农村看电视难题

    两年前推出的直播星在令消费者欢呼雀跃的同时也令广电有线运营商痛苦不已,之后虽然广电总局不停升级密码,据统计市场销售的黑盒子数量依然超过4000万之巨,显示出农村市场对直播星电视机顶盒的巨大需求和对电视 ...

  2. 约四成的平板和智能手机用户看电视时“一心多用”

    调查显示,面对多媒体信息时,美国消费者越来越习惯"一心多用". 在美国,42%的平板电脑用户每天看电视时会使用平板电脑,40%的智能手机用户每天看电视时会使用智能手机,相比而言,电 ...

  3. 在电脑上哪儿看提高收视率_电视上收视率最高的电影的网上搜集完整指南

    在电脑上哪儿看提高收视率 In this article, I will show how to scrape the internet for top-rated films with the Sc ...

  4. 可以在电脑上面看电视了

    新的一年里,买的第一件IT产品就是一个USB2.0的电视盒:天敏视玲珑2代.在淘宝上面买的,价钱还算很便宜,就是不知道质量如何,看看网上的评价,有说好的,也有说坏的,看看其他牌子也差不多,所以不管那么 ...

  5. python如何运行_家长看的懂的Python编程---电脑要如何运行Python?

    本文是<家长看的懂的Python编程>第2节,如果你还没有看过前面的章节,请移步至如下链接! 家长看的懂的Python编程---预备知识 这一章我们要正式踏上Python的学习之旅,今天我 ...

  6. 电脑的任务栏只显示一条杠,没有图标怎么解决,看这里!!!

    电脑的任务栏只显示一条杠,没有图标怎么解决,看这里!!! 问题描述 解决方法 问题描述 打开软件只有一条杠,没有图标,而且点也点不动怎么办 解决方法 鼠标单击任务栏右键 2 找到 工具栏 如图 将 K ...

  7. 打开桌面计算机投屏到扩展屏,电脑投屏到电视显示不完全解决办法

    原标题:电脑投屏到电视显示不完全解决办法 电脑投屏到电视显示不完全解决办法 在我们将电脑无线投屏电视的时候,因为电脑显示比例和电视机显示比例不统一的原因,我们电脑无线投屏电视的时候,往往会出现溢出或者 ...

  8. python查看电脑配置_怎么看电脑配置_怎么查看电脑配置好坏|信息【图文】-太平洋IT百科...

    怎么看电脑配置?怎么查看电脑配置好坏和配置信息?查看电脑配置有两种方法,一种是查看电脑中的设备管理器,但这种方法通常无法完整地查看到电脑的配置信息;另外一种方法是使用软件查看,例如鲁大师.驱动精灵等. ...

  9. 怎么用电脑看电视和点播电视剧电影,电脑上玩手机android游戏

    以前在电脑上看电视,需要在台式机上装块电视卡,还要用闭路电视信号.随着宽带技术.智能电视和网络电视盒子的发展,一批优秀的基于Android的电视APP出现了,而且越来越成熟.那么如何在电脑上看电视和点 ...

最新文章

  1. jQuery Tools:Web开发必备的 jQuery UI 库
  2. 字符串与数组的常用方法
  3. 零基础python必背代码-编程零基础应当如何开始学习 Python?
  4. ElasticSearch5.3插件开发(二)获取集群健康信息
  5. 2017尼毕鲁笔试算法题
  6. Castle学习之一:安装与环境设置
  7. 27. Element nodeType 属性
  8. 堆(基本介绍,代码实现,以及例题)
  9. linux 内核logo 居中,linux logo制作及居中显示
  10. 自然语言处理之机器翻译
  11. 密度泛函理论平面波基组展开
  12. Android 窗口结构(一) 窗口层级构造
  13. [参文]GCN+交通
  14. JAVA I/O之神奇的RandomAccessFile(快速定位文件任意位置,修改或插入)
  15. 基于javaweb+mysql的房屋租赁管理系统(java+SSM+Layui+Maven+Mysql+Jsp)
  16. [附源码]Java计算机毕业设计SSM成都美食交流平台
  17. 从2021年度业绩报告看奇安信的网安“野望”
  18. 博弈游戏·Nim游戏·二
  19. linux防误删工具trash-cli
  20. 阅读笔记:What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision?

热门文章

  1. RTD LCD/OLED显示芯片系列
  2. java 编写cgi_编写CGI小结(Java)
  3. oracle数据量多大,怎么查看oracle数据库数据量大小
  4. C语言多人聊天室,Socket网络编程
  5. 美团面试官:为什么不建议把数据库部署在Docker容器内?
  6. 教你用tcgames电脑玩刺激战场匹配手机的正确姿势:如何降低延迟卡顿
  7. mysql 设置mvcc_mysql的MVCC机制
  8. java爬取网易云歌单_[原创]基于Java网易云音乐评论抓取~【悠着点玩啊~】
  9. 有一种思念,它不是爱情
  10. 30+ 个工作中常用到的前端小知识