main.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #/
  2. #
  3. # Note: Requires a build of micropython with ESPNOW support, which I got from
  4. # https://github.com/glenn20/micropython-espnow-images
  5. import binascii
  6. import esp
  7. import esp32
  8. import json
  9. import machine
  10. import math
  11. import network
  12. import random
  13. import _thread
  14. import time
  15. import tm1637
  16. from machine import Pin, PWM
  17. COMM_TIMEOUT = 5 # Prune neighbor if not heard from in 10 seconds
  18. MIN_DELAY = 2000 # Minimum time for a random start
  19. MAX_DELAY = 5000 # Maximum time for a random start
  20. PIN_NUMBERS = {
  21. "BUZZER": 18,
  22. "LED": [ 26, 25, 23, 22, 21 ],
  23. "DISPLAY": [ 33, 32 ],
  24. "COASTER": 27,
  25. "READY": 14,
  26. "MODE": 15
  27. }
  28. PINS = {
  29. "BUZZER": Pin(PIN_NUMBERS["BUZZER"], Pin.OUT),
  30. "LED": [
  31. Pin(PIN_NUMBERS['LED'][0], Pin.OUT, value=0),
  32. Pin(PIN_NUMBERS['LED'][1], Pin.OUT, value=0),
  33. Pin(PIN_NUMBERS['LED'][2], Pin.OUT, value=0),
  34. Pin(PIN_NUMBERS['LED'][3], Pin.OUT, value=0),
  35. Pin(PIN_NUMBERS['LED'][4], Pin.OUT, value=0)
  36. ],
  37. "COASTER": Pin(PIN_NUMBERS['COASTER'], Pin.IN, Pin.PULL_UP),
  38. "READY": Pin(PIN_NUMBERS['READY'], Pin.IN, Pin.PULL_UP),
  39. "MODE": Pin(PIN_NUMBERS['MODE'], Pin.IN, Pin.PULL_UP)
  40. }
  41. display = tm1637.TM1637(clk=Pin(PIN_NUMBERS["DISPLAY"][0]), dio=Pin(PIN_NUMBERS["DISPLAY"][1]))
  42. wlan = network.WLAN(network.STA_IF)
  43. BROADCAST = b'\xff\xff\xff\xff\xff\xff'
  44. msgqueue = []
  45. neighbors = {}
  46. neighborlist = []
  47. abort = False
  48. winner = False
  49. songfinished = False
  50. def initialize():
  51. print("Here's what I know about myself:")
  52. print('')
  53. print(f'\tMAC Address: {wlan.config('mac')}')
  54. print('')
  55. print(f'\tFrequency: {machine.freq()}')
  56. print(f'\tFlash Size: {esp.flash_size()}')
  57. print(f'\tTemperature: {esp32.raw_temperature()}')
  58. print('')
  59. print('Current Switch status:')
  60. print(f'\tCoaster: { get_switch("COASTER") }')
  61. print(f'\t Ready: { get_switch("READY") }')
  62. print(f'\t Mode: { get_switch("MODE") }')
  63. print('')
  64. for i in range(5):
  65. print(f'\tTesting LED {i}')
  66. led(i, True)
  67. beep()
  68. time.sleep(0.25)
  69. led(i, False)
  70. def led(pin, value):
  71. PINS["LED"][pin].value(value)
  72. def get_switch(name):
  73. return not PINS[name].value()
  74. def am_ready():
  75. buttons_ready = get_switch("COASTER") and get_switch("READY")
  76. if buttons_ready is False:
  77. return False
  78. for k, n in neighbors.items():
  79. if n['self'] is True:
  80. continue
  81. if n['neighborlist'] != neighborlist:
  82. return False
  83. return True
  84. #######################
  85. # Buzzer Functions
  86. def tone(freq, delay):
  87. beeper = PWM(PINS["BUZZER"], freq=freq, duty=512)
  88. time.sleep_ms(delay)
  89. beeper.deinit()
  90. def buzz():
  91. tone(freq=300, delay=800)
  92. def beep():
  93. tone(freq=2000, delay=200)
  94. def test_buzzer():
  95. for freq in range(0, 5500, 100):
  96. tone(freq=freq, delay=50)
  97. def play_song():
  98. global songfinished
  99. frequencies = {
  100. 'c': 262,
  101. 'd': 294,
  102. 'e': 330,
  103. 'f': 349,
  104. 'g': 392,
  105. 'a': 440,
  106. 'b': 494,
  107. 'C': 523
  108. }
  109. songs = {
  110. 'song1': {
  111. 'tempo': 118,
  112. 'notes': [
  113. ['c', 1],
  114. ['d', 1],
  115. ['f', 1],
  116. ['d', 1],
  117. ['a', 1],
  118. [' ', 1],
  119. ['a', 4],
  120. ['g', 4],
  121. [' ', 2],
  122. ['c', 1],
  123. ['d', 1],
  124. ['f', 1],
  125. ['d', 1],
  126. ['g', 1],
  127. [' ', 1],
  128. ['g', 4],
  129. ['f', 4],
  130. [' ', 2],
  131. ]
  132. },
  133. 'jingle_bells': {
  134. 'tempo': 118,
  135. 'notes': [
  136. ['e', 1],
  137. ['e', 1],
  138. ['e', 2],
  139. ['e', 1],
  140. ['e', 1],
  141. ['e', 2],
  142. ['e', 1],
  143. ['g', 1],
  144. ['c', 1],
  145. ['d', 1],
  146. ['e', 4],
  147. [' ', 1],
  148. ['f', 1],
  149. ['f', 1],
  150. ['f', 1],
  151. ['f', 1],
  152. ['f', 1],
  153. ['e', 1],
  154. ['e', 1],
  155. ['e', 1],
  156. ['e', 1],
  157. ['d', 1],
  158. ['d', 1],
  159. ['e', 1],
  160. ['d', 2],
  161. ['g', 2]
  162. ]
  163. }
  164. }
  165. #s = random.choice(list(songs.keys()))
  166. if random.randint(0, 10) == 10: # one in 10 chance, for now
  167. s = 'jingle_bells'
  168. else:
  169. s = 'song1'
  170. print(f'Randomly chose song { s }')
  171. for note in songs[s]['notes']:
  172. duration = note[1] * songs[s]['tempo']
  173. if note[0] == ' ':
  174. #print(f'Rest for { duration }')
  175. time.sleep_ms(duration)
  176. else:
  177. #print(f'Note { note[0] } at { frequencies[note[0]] } for { duration }')
  178. tone(frequencies[note[0]], duration)
  179. time.sleep_ms(math.floor(songs[s]['tempo'] / 10))
  180. songfinished = True
  181. #int songLength2 = 26;
  182. #char notes2[] = "eeeeeeegcde fffffeeeeddedg";
  183. #int beats2[] = { 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2};
  184. #const int tempo2 = 130;
  185. #######################
  186. # Display Functions
  187. def display_counts():
  188. nr = count_ready()
  189. nc = count_neighbors()
  190. digits = [ 0, 0, 0, 0 ]
  191. if nr > 19 or nc > 19:
  192. display.show('OVER')
  193. return
  194. if nr < 10:
  195. digits[0] = display.encode_digit(nr)
  196. else:
  197. digits[0] = display.encode_digit(0x01)
  198. digits[1] = display.encode_digit(nr % 10)
  199. if nc < 10:
  200. digits[3] = display.encode_digit(nc)
  201. else:
  202. digits[2] = display.encode_digit(0x01)
  203. digits[3] = display.encode_digit(nc % 10)
  204. display.write(digits)
  205. #######################
  206. # ESPNow Functions
  207. def initialize_network():
  208. global wlan
  209. global espnow
  210. global neighbors
  211. neighbors[mac_to_hex(wlan.config('mac'))] = { 'self': True } # we're our own neighbor
  212. neighborlist.append(mac_to_hex(wlan.config('mac')))
  213. print('Activating WLAN')
  214. wlan.active(True)
  215. print('Activating ESPNow')
  216. espnow = esp.espnow.ESPNow()
  217. espnow.init()
  218. espnow.config(on_recv=espnow_recv_handler)
  219. espnow.add_peer(BROADCAST)
  220. def espnow_recv_handler(e):
  221. # We should add this to a processing queue
  222. global msgqueue
  223. while(e.poll()):
  224. data = e.irecv(0)
  225. #neighbor = binascii.hexlify(data[0]).decode('ascii')
  226. msg = data[1].decode()
  227. msgqueue.append({ 'neighbor': data[0], 'msg': msg })
  228. #print(f'Received from { neighbor }: {msg}')
  229. def process_queue():
  230. global msgqueue
  231. global neighbors
  232. global neighborlist
  233. global abort
  234. while(len(msgqueue)>0):
  235. item = msgqueue.pop(0)
  236. mac = mac_to_hex(item['neighbor'])
  237. process_neighbor(mac)
  238. try:
  239. neighbor_json = json.loads(item["msg"])
  240. except:
  241. print(f'ERROR: Could not decode message from { mac_to_hex(item["neighbor"]) }: { item["msg"] }')
  242. msg_type = neighbor_json.get('type', None)
  243. if msg_type is None:
  244. print(f'ERROR: No message type from { mac_to_hex(item["neighbor"]) }: { item["msg"] }')
  245. return False
  246. if msg_type == 'GOGOGO':
  247. print(f"I'm a peon, and master (or somebody--I don't check) said TIME TO GO!")
  248. return True # time to gO!
  249. if msg_type == 'ABORT':
  250. print(f'Neighbor { mac } disqualified.')
  251. abort = True
  252. return False
  253. if msg_type == 'FINISH':
  254. print(f'Neighbor { mac } finished.')
  255. neighbors[mac]['finished'] = True
  256. neighbors[mac]['time'] = neighbor_json['time']
  257. return False
  258. if msg_type == 'beacon':
  259. neighbors[mac]['neighborlist'] = list(neighbor_json['neighborlist'])
  260. neighbors[mac]['mode'] = neighbor_json['mode']
  261. # Only neighbors in the same mode as us can be ready
  262. if neighbors[mac]['mode'] != get_switch('MODE'):
  263. neighbors[mac]['ready'] = False
  264. else:
  265. neighbors[mac]['ready'] = neighbor_json['ready']
  266. doc = json.dumps(neighbor_json)
  267. print(f'Received from { mac_to_hex(item["neighbor"]) } : { doc }')
  268. return False # Not time to go
  269. def process_neighbor(mac):
  270. if not(mac in neighbors.keys()):
  271. print(f'NEW NEIGHBOR: { mac }')
  272. neighbors[mac] = {
  273. 'self': False,
  274. 'lastseen': time.time(),
  275. 'mode': get_switch('MODE'), # Good a guess as any
  276. 'ready': False
  277. }
  278. neighborlist.append(mac)
  279. neighborlist.sort()
  280. neighbors[mac]['lastseen'] = time.time()
  281. def prune_neighbors():
  282. for n in list(neighborlist):
  283. if neighbors[n]['self'] is True:
  284. continue
  285. if time.time() - neighbors[n]['lastseen'] > COMM_TIMEOUT:
  286. print(f'LOST NEIGHBOR: { n }')
  287. neighborlist.remove(n)
  288. neighbors.pop(n)
  289. def mac_to_hex(mac):
  290. return binascii.hexlify(mac).decode('ascii')
  291. def am_master():
  292. # We're the master if we're the first one on the list
  293. return neighbors[neighborlist[0]]['self']
  294. last_beacon = time.time()
  295. def broadcast_beacon():
  296. global last_beacon
  297. if time.time() - last_beacon < 1:
  298. return False
  299. last_beacon = time.time()
  300. json_beacon = {
  301. 'type': 'beacon',
  302. 'neighborlist': neighborlist,
  303. 'mode': get_switch("MODE"),
  304. 'ready': am_ready()
  305. }
  306. beacon = json.dumps(json_beacon, separators=(',', ':'))
  307. #, 'neighbors': neighbors }
  308. if(len(beacon)) > 250:
  309. display.show('MANY')
  310. else:
  311. print(f'Sending JSON Broadcast ({len(beacon)} bytes): {beacon}')
  312. espnow.send(BROADCAST, beacon, False)
  313. return True
  314. def count_neighbors():
  315. return len(neighborlist)
  316. def count_ready():
  317. count = 0
  318. for m, i in neighbors.items():
  319. if i['self'] and am_ready():
  320. count += 1
  321. elif i.get('ready', False):
  322. count += 1
  323. # For testing with only 1:
  324. #if count == 1:
  325. # return 2
  326. return count
  327. ##### Sub-Ready Loops
  328. def call_regularly():
  329. broadcast_beacon()
  330. time_to_go = process_queue()
  331. prune_neighbors()
  332. def get_ready_loop(should_display_counts):
  333. print(f'***** Stage 1: Get Ready Loop')
  334. time_to_go = False
  335. while time_to_go is False:
  336. call_regularly()
  337. if should_display_counts or get_switch('READY'):
  338. # we only display counts on the first run or once ready
  339. # has been pressed. On other runs
  340. # we wnat the time and/or disqualification to show
  341. should_display_counts = True
  342. display_counts()
  343. if(get_switch('COASTER')):
  344. led(4, 1)
  345. else:
  346. led(4, 0)
  347. # Time to go?
  348. if am_master() and count_neighbors() == count_ready():
  349. print(f"I'm the master and it's TIME TO GO!")
  350. espnow.send(BROADCAST, json.dumps({ 'type': 'GOGOGO' }), False)
  351. time_to_go = True
  352. #time.sleep(1.0)
  353. def sleep_with_coaster_checks(ms):
  354. ''' Returns True if there was a disqualifying event '''
  355. start = time.ticks_ms()
  356. while(time.ticks_diff(time.ticks_ms(), start) < ms):
  357. if not get_switch('COASTER'):
  358. return True
  359. return False
  360. def light_countdown():
  361. ''' Countdown the lights, returns True if there was a disqualifying event '''
  362. print(f'Mode is Drag Race. Running lights...')
  363. # Fix for early disqulaification
  364. if not get_switch('COASTER'):
  365. print('DISQUALIFIED')
  366. return True
  367. for i in range(3):
  368. print(f'\t GO LED {i}')
  369. led(i, True)
  370. beep()
  371. if(sleep_with_coaster_checks(500)):
  372. print('DISQUALIFIED')
  373. return True
  374. led(i, False)
  375. call_regularly()
  376. led(3, 1) # Turn on green
  377. led(4, 0) # turn off red
  378. return False
  379. def random_countdown():
  380. delay = random.randint(MIN_DELAY, MAX_DELAY)
  381. print(f'Mode is random delay, waiting { delay } ms')
  382. start_ticks = time.ticks_ms()
  383. led(0, 1)
  384. led(1, 1)
  385. led(2, 1)
  386. led(3, 0)
  387. led(4, 1)
  388. while( time.ticks_diff(time.ticks_ms(), start_ticks) < delay ):
  389. call_regularly()
  390. if not get_switch('COASTER'):
  391. print('DISQUALIFIED')
  392. return True
  393. led(0, 0)
  394. led(1, 0)
  395. led(2, 0)
  396. led(3, 1)
  397. led(4, 0)
  398. return False
  399. def handle_disqualification():
  400. global abort
  401. espnow.send(BROADCAST, json.dumps({ 'type': 'ABORT' }), False)
  402. for i in range(5):
  403. led(4, 1)
  404. display.show('FAUL')
  405. buzz()
  406. call_regularly()
  407. led(4, 0)
  408. display.show(' ')
  409. time.sleep(0.5)
  410. call_regularly()
  411. led(4,1)
  412. abort = True
  413. def is_everybody_finished():
  414. for mac, state in neighbors.items():
  415. if state['self'] is True:
  416. continue
  417. if state['finished'] is not True:
  418. return False
  419. return True
  420. def did_we_win(time_diff):
  421. for mac, state in neighbors.items():
  422. if state['self'] is True:
  423. continue
  424. if state['time'] < time_diff:
  425. # somebody else did better
  426. return False
  427. return True
  428. def time_to_go_loop():
  429. global abort
  430. global winner
  431. global songfinished
  432. display.show(" GO ")
  433. beep(); beep(); beep()
  434. # Reset State of the Game
  435. abort = False
  436. winner = False
  437. for mac, state in neighbors.items():
  438. state['finished'] = False
  439. state['time'] = 0
  440. time.sleep(1.0)
  441. call_regularly()
  442. if(get_switch('MODE')):
  443. disqualified = light_countdown()
  444. else:
  445. disqualified = random_countdown()
  446. if disqualified:
  447. handle_disqualification()
  448. led(0, 1)
  449. led(1, 1)
  450. led(2, 1)
  451. led(3, 0)
  452. led(4, 1)
  453. return
  454. elif abort:
  455. # somebody else disqualified
  456. led(0, 1)
  457. led(1, 0)
  458. led(2, 0)
  459. led(3, 0)
  460. led(4, 1)
  461. beep(); beep(); beep();
  462. return # Soembody disqualified
  463. call_regularly()
  464. # Start go tone:
  465. beeper = PWM(PINS["BUZZER"], freq=2000, duty=512)
  466. start_time = time.ticks_ms()
  467. done = False
  468. coaster_lifted = False
  469. time_diff = 0
  470. seconds = 0
  471. ms = 0
  472. while not done:
  473. call_regularly()
  474. time_diff = time.ticks_diff(time.ticks_ms(), start_time)
  475. if time_diff > 1000:
  476. beeper.deinit()
  477. seconds = int(time_diff / 1000)
  478. ms = int( (time_diff % 1000) / 10 )
  479. #print(f' Time is {seconds}.{ms}')
  480. display.numbers(seconds, ms)
  481. # Can't win until coaster's been lifted
  482. if not get_switch('COASTER'):
  483. coaster_lifted = True
  484. if coaster_lifted and get_switch('COASTER'):
  485. # We've finished, and may or may not have won
  486. done = True
  487. led(3,0) # turn off the green light when finished
  488. espnow.send(BROADCAST, json.dumps({ 'type': 'FINISH', 'time': time_diff }), False)
  489. while not is_everybody_finished():
  490. call_regularly()
  491. # Determine winner
  492. # If winner, blink a few times and play a song
  493. # If loser, play a sadder song
  494. if did_we_win(time_diff):
  495. songfinished = False
  496. songthread = _thread.start_new_thread(play_song, [])
  497. while not songfinished:
  498. # thread will change end condition
  499. display.show(' ')
  500. time.sleep_ms(200)
  501. display.numbers(seconds, ms)
  502. time.sleep_ms(200)
  503. call_regularly()
  504. else:
  505. buzz()
  506. ##########################################################################
  507. ##########################################################################
  508. ##########################################################################
  509. if __name__ == "__main__":
  510. initialize_network()
  511. initialize()
  512. should_display_counts = True
  513. while True:
  514. get_ready_loop(should_display_counts)
  515. time_to_go_loop()
  516. #test_buzzer()
  517. should_display_counts = False
  518. #while True:
  519. # ###################################################################
  520. # # Loop code goes inside the loop here, this is called repeatedly: #
  521. # ###################################################################
  522. # print(i)
  523. # i = 1
  524. # time.sleep(1.0) # Delay for 1 second.