forked from scottbez1/splitflap
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JSON serial responses and example python code
Make the Arduino only output single-line JSON objects via serial to make it easy for control software to parse. Also added some example python software which randomly picks words to display from a list of 4-letter animal names. Fixes scottbez1#31
- Loading branch information
Showing
5 changed files
with
267 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from __future__ import print_function | ||
|
||
import random | ||
import time | ||
|
||
import serial | ||
import serial.tools.list_ports | ||
|
||
from splitflap import splitflap | ||
|
||
words = [ | ||
'duck', 'goat', 'lion', 'bear', 'mole', 'crab', | ||
'hare', 'toad', 'wolf', 'lynx', 'cats', 'dogs', | ||
'bees', 'mule', 'seal', 'bird', 'frog', 'deer', | ||
'lamb', 'fish', 'hawk', 'kiwi', | ||
] | ||
|
||
|
||
def run(): | ||
port = ask_for_serial_port() | ||
|
||
print('Starting...') | ||
with splitflap(port) as s: | ||
while True: | ||
word = random.choice(words) | ||
print('Going to {}'.format(word)) | ||
status = s.set_text(word) | ||
print_status(status) | ||
time.sleep(3) | ||
|
||
|
||
def ask_for_serial_port(): | ||
print('Available ports:') | ||
ports = sorted( | ||
filter( | ||
lambda p: p.description != 'n/a', | ||
serial.tools.list_ports.comports(), | ||
), | ||
key=lambda p: p.device, | ||
) | ||
for i, port in enumerate(ports): | ||
print('[{: 2}] {} - {}'.format(i, port.device, port.description)) | ||
print() | ||
value = raw_input('Use which port? ') | ||
port_index = int(value) | ||
assert 0 <= port_index < len(ports) | ||
return ports[port_index].device | ||
|
||
|
||
def print_status(status): | ||
for module in status: | ||
state = '' | ||
if module['state'] == 'panic': | ||
state = '!!!!' | ||
elif module['state'] == 'look_for_home': | ||
state = '...' | ||
elif module['state'] == 'sensor_error': | ||
state = '????' | ||
elif module['state'] == 'normal': | ||
state = module['flap'] | ||
print('{:4} {: 4} {: 4}'.format(state, module['count_missed_home'], module['count_unexpected_home'])) | ||
|
||
|
||
if __name__ == '__main__': | ||
run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import json | ||
from contextlib import contextmanager | ||
|
||
import serial | ||
|
||
_ALPHABET = { | ||
' ', | ||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | ||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | ||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', | ||
'.', | ||
',', | ||
'\'', | ||
} | ||
|
||
|
||
class Splitflap(object): | ||
|
||
def __init__(self, serial_instance): | ||
self.serial = serial_instance | ||
|
||
self.has_inited = False | ||
self.num_modules = 0 | ||
self.last_command = None | ||
self.last_status = None | ||
self.exception = None | ||
|
||
def _loop_for_status(self): | ||
while True: | ||
line = self.serial.readline().lstrip('\0').rstrip('\n') | ||
data = json.loads(line) | ||
t = data['type'] | ||
if t == 'init': | ||
if self.has_inited: | ||
raise RuntimeError('Unexpected re-init!') | ||
self.has_inited = True | ||
self.num_modules = data['num_modules'] | ||
elif t == 'move_echo': | ||
if not self.has_inited: | ||
raise RuntimeError('Got move_echo before init!') | ||
if self.last_command is None: | ||
raise RuntimeError('Unexpected move_echo response from controller') | ||
if self.last_command != data['dest']: | ||
raise RuntimeError('Bad response from controller. Expected {!r} but got {!r}'.format( | ||
self.last_command, | ||
data['dest'], | ||
)) | ||
elif t == 'status': | ||
if not self.has_inited: | ||
raise RuntimeError('Got status before init!') | ||
if len(data['modules']) != self.num_modules: | ||
raise RuntimeError('Wrong number of modules in status update. Expected {} but got {}'.format( | ||
self.num_modules, | ||
len(data['modules']), | ||
)) | ||
self.last_status = data['modules'] | ||
return self.last_status | ||
elif t == 'no_op': | ||
pass | ||
else: | ||
raise RuntimeError('Unexpected message: {!r}'.format(data)) | ||
|
||
def is_in_alphabet(self, letter): | ||
return letter in _ALPHABET | ||
|
||
def set_text(self, text): | ||
for letter in text: | ||
assert self.is_in_alphabet(letter), 'Unexpected letter: {!r}. Must be one of {!r}'.format( | ||
letter, | ||
list(_ALPHABET), | ||
) | ||
self.last_command = text | ||
self.serial.write('={}\n'.format(text)) | ||
return self._loop_for_status() | ||
|
||
def recalibrate_all(self): | ||
self.serial.write('@') | ||
return self._loop_for_status() | ||
|
||
def get_status(self): | ||
return self.last_status | ||
|
||
|
||
@contextmanager | ||
def splitflap(serial_port): | ||
with serial.Serial(serial_port, 38400) as ser: | ||
s = Splitflap(ser) | ||
s._loop_for_status() | ||
yield s |