-
Notifications
You must be signed in to change notification settings - Fork 8
/
nespi.py
283 lines (219 loc) · 11.6 KB
/
nespi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import os
import psutil
import re
import serial
import socket
import subprocess
from subprocess import PIPE, Popen
import threading
import time
from gpiozero import Button, LED
#####################################
# NESPi Cart Reader v0.1 by mike.g #
# www.daftmike.com #
#####################################
#############################################################################################
# Setup the serial port and remove old romdetails.txt
subprocess.call(["sudo", "rm", "/home/pi/NESPi/romdetails.txt"])
ser = serial.Serial("/dev/ttyS0", 9600, timeout=None)
time.sleep(2)
ser.write("ready")
#############################################################################################
# Get the CPU temerature
def get_cpu_temperature():
process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
output, _error = process.communicate()
return float(output[output.index('=') + 1:output.rindex("'")])
#############################################################################################
# Check the CPU temp every 10 seconds and switch the fan on/off if needed
def cpufan():
fanon = 65 # fan turns ON above this temp
fanoff = 55 # fan turns OFF below this temp
threading.Timer(10.0, cpufan).start()
cpu_temp = get_cpu_temperature()
if cpu_temp > fanon:
ser.write("fanon")
if cpu_temp < fanoff:
ser.write("fanoff")
cpufan()
# I want to check the cpu temp at regular intervals and compare it to thresholds
# to decide if we want to turn the fan on or off.
# But I only want to send the fan message when the temperature crosses the setpoints.
# In C on Arduino, I'd detect the 'rising/falling edges' by keeping track of the old value.
# but I'm not sure how to do it in a Python-way :/
#############################################################################################
# Sends 'message' to port 55355 for RetroArch's network commands
def retroarch_command(message):
sock = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM)
sock.sendto(message, ("127.0.0.1", 55355))
#############################################################################################
# Kills the task of 'procnames', also forces Kodi to close if it's running
def killtasks(procnames):
for proc in psutil.process_iter():
if proc.name() in procnames:
pid = str(proc.as_dict(attrs=['pid'])['pid'])
name = proc.as_dict(attrs=['name'])['name']
print "stopping... " + name + " (pid:" + pid + ")"
subprocess.call(["sudo", "kill", "-15", pid])
kodiproc = ["kodi", "kodi.bin"] # kodi needs SIGKILL -9 to close
for proc in psutil.process_iter():
if proc.name() in kodiproc:
pid = str(proc.as_dict(attrs=['pid'])['pid'])
name = proc.as_dict(attrs=['name'])['name']
print "stopping... " + name + " (pid:" + pid + ")"
subprocess.call(["sudo", "kill", "-9", pid])
#############################################################################################
# Safely shuts-down the Raspberry Pi
def shutdown():
print "shutdown...\n"
subprocess.call("sudo shutdown -h now", shell=True)
#############################################################################################
# Returns True if the 'proc_name' process name is currently running
def process_exists(proc_name):
ps = subprocess.Popen("ps ax -o pid= -o args= ", shell=True, stdout=subprocess.PIPE)
ps_pid = ps.pid
output = ps.stdout.read()
ps.stdout.close()
ps.wait()
for line in output.split("\n"):
res = re.findall("(\d+) (.*)", line)
if res:
pid = int(res[0][0])
if proc_name in res[0][1] and pid != os.getpid() and pid != ps_pid:
return True
return False
#############################################################################################
# Check if the console we read from NDEF Record #1 is valid, by checking against a list of supported emulators
def check_console(console):
emulators = ["amiga", "amstradcpc", "apple2", "arcade", "atari800", "atari2600", "atari5200", "atari7800",
"atarilynx", "atarist", "c64", "coco", "dragon32", "dreamcast", "fba", "fds", "gamegear", "gb", "gba",
"gbc", "intellivision", "macintosh", "mame-advmame", "mame-libretro", "mame-mame4all", "mastersystem",
"megadrive", "msx", "n64", "neogeo", "nes", "ngp", "ngpc", "pc", "ports", "psp", "psx", "scummvm",
"sega32x", "segacd", "sg-1000", "snes", "vectrex", "videopac", "wonderswan", "wonderswancolor",
"zmachine", "zxspectrum"]
if console != "":
if console in emulators:
print "NDEF Record \"" + console + "\" is a valid system...\n"
return True
else:
print "Could not find \"" + console + "\" in the supported systems list"
print "Check NDEF Record 1 for a valid system name(all-lowercase)\n"
ser.write("bad") # Tell Arduino there was a cart read error
return False
#############################################################################################
# Return the path of the emulator ready to be used later
def get_emulatorpath(console):
path = "/opt/retropie/supplementary/runcommand/runcommand.sh 0 _SYS_ " + console + " "
return path
#############################################################################################
# Check that the rom is valid by looking for the file, tell the cart slot light green if good, red if bad
def check_rom(console, rom):
# get full rom path and check if it's a file
romfile = "/home/pi/RetroPie/roms/" + console + "/" + rom
if os.path.isfile(romfile):
print "Found \"" + rom + "\"\n"
ser.write("ok") # Tell Arduino the cart read was successful
return True
else:
print "But couldn\'t find \"" + romfile + "\""
print "Check NDEF Record 2 contains a valid filename...\n"
ser.write("bad") # Tell Arduino there was a cart read error
return False
#############################################################################################
# Return the full path of the rom read from NDEF record #2 on the NFC tag
def get_rompath(console, rom):
# escape the spaces and brackets in rom filename
rom = rom.replace(" ", "\ ")
rom = rom.replace("(", "\(")
rom = rom.replace(")", "\)")
rom = rom.replace("'", "\\'")
rompath = "/home/pi/RetroPie/roms/" + console + "/" + rom
return rompath
#############################################################################################
# If the cartridge is valid when the button is switched on then we can launch the rom
def button_on():
print "power button turned ON...\n"
if cartok:
procnames = ["retroarch", "ags", "uae4all2", "uae4arm", "capricerpi", "linapple", "hatari", "stella",
"atari800", "xroar", "vice", "daphne", "reicast", "pifba", "osmose", "gpsp", "jzintv",
"basiliskll", "mame", "advmame", "dgen", "openmsx", "mupen64plus", "gngeo", "dosbox", "ppsspp",
"simcoupe", "scummvm", "snes9x", "pisnes", "frotz", "fbzx", "fuse", "gemrb", "cgenesis", "zdoom",
"eduke32", "lincity", "love", "alephone", "micropolis", "openbor", "openttd", "opentyrian",
"cannonball", "tyrquake", "ioquake3", "residualvm", "xrick", "sdlpop", "uqm", "stratagus",
"wolf4sdl", "solarus", "emulationstation", "emulationstatio"]
killtasks(procnames)
subprocess.call("sudo openvt -c 1 -s -f " + emulatorpath + rompath + "&", shell=True)
subprocess.call("sudo chown pi -R /dev/shm", shell=True) # ES needs permission as 'pi' to access this later
time.sleep(1)
subprocess.call("sudo chown pi -R /home/pi/NESPi/romdetails.txt", shell=True)
else:
print "no valid cartridge inserted...\n"
#############################################################################################
# Close the emulator when the button is pushed again ("off")
def button_off():
print "power button turned OFF...\n"
if process_exists("emulationstation"):
print "\nemulationstation is running...\n"
with open('/home/pi/NESPi/romdetails.txt') as myfile:
romstring=myfile.readline().replace('/home/pi/RetroPie/roms', 'rom')
romstring=romstring.replace('\n', '')
ser.write(romstring)
else:
procnames = ["retroarch", "ags", "uae4all2", "uae4arm", "capricerpi", "linapple", "hatari", "stella",
"atari800", "xroar", "vice", "daphne", "reicast", "pifba", "osmose", "gpsp", "jzintv",
"basiliskll", "mame", "advmame", "dgen", "openmsx", "mupen64plus", "gngeo", "dosbox", "ppsspp",
"simcoupe", "scummvm", "snes9x", "pisnes", "frotz", "fbzx", "fuse", "gemrb", "cgenesis", "zdoom",
"eduke32", "lincity", "love", "alephone", "micropolis", "openbor", "openttd", "opentyrian",
"cannonball", "tyrquake", "ioquake3", "residualvm", "xrick", "sdlpop", "uqm", "stratagus",
"wolf4sdl", "solarus"]
killtasks(procnames)
# I check if ES is running here because if it *is* then any running game was launched from within ES
# and we don't want to quit it when the button is pressed. But if the game was launched from a cart
# then ES will not be running in the background and we *do* want to quit the emulator.
#############################################################################################
# Set BCM 4 HIGH... the arduino reads this to determine if the Raspberry Pi is running
led = LED(4)
led.on()
# If we do a manual shutdown from within ES then our program will be stopped and the pin
# will return to a LOW state, the arduino can read this and cut the power when appropriate
#############################################################################################
# Assign the NES 'power' button to the button functions
onbtn = Button(3)
offbtn = Button(2)
onbtn.when_pressed = button_on
offbtn.when_pressed = button_off
#############################################################################################
# Assume the cart is not valid until we've checked it in the main loop
cartok = False
# Main Loop
while True:
try:
line = ser.readline()
if line != "":
records = line[:-1].split(', ') # incoming data looks like: "$$$, $$$, $$$, \n"
uid = records[0] # 'uid' is read from the NFC tag, also used for shutdown, reset and cart eject
console = records[1] # 'console' is NDEF Record #1
rom = records[2] # 'rom' is NDEF Record #2
except IndexError:
print "NDEF read error...\n"
ser.write("bad") # Tell the Arduino there was a cart read error
#############################################################################################
# Check serial data for a command message in the 1st field
if uid == "shutdown":
print "shutdown command received...\n"
shutdown()
if uid == "cart_eject":
print "cart ejected...\n"
cartok = False
if uid == "reset":
print "reset button pressed...\n"
retroarch_command("RESET")
#############################################################################################
# Check the console and rom data for validity
if console != "":
if check_console(console):
if check_rom(console, rom):
emulatorpath = get_emulatorpath(console)
rompath = get_rompath(console, rom)
cartok = True