mouth.py
Go to the documentation of this file.
1 # Copyright 2017 Mycroft AI Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 import time
16 from PIL import Image
17 
18 
20  """
21  Listens to enclosure commands for Mycroft's Mouth.
22 
23  Performs the associated command on Arduino by writing on the Serial port.
24  """
25 
26  def __init__(self, bus, writer):
27  self.bus = bus
28  self.writer = writer
29  self.is_timer_on = False
30  self.__init_events()
31  self.showing_visemes = False
32 
33  def __init_events(self):
34  self.bus.on('enclosure.mouth.reset', self.reset)
35  self.bus.on('enclosure.mouth.talk', self.talk)
36  self.bus.on('enclosure.mouth.think', self.think)
37  self.bus.on('enclosure.mouth.listen', self.listen)
38  self.bus.on('enclosure.mouth.smile', self.smile)
39  self.bus.on('enclosure.mouth.viseme_list', self.viseme_list)
40  self.bus.on('enclosure.mouth.text', self.text)
41  self.bus.on('enclosure.mouth.display', self.display)
42  self.bus.on('enclosure.mouth.display_image', self.display_image)
43  self.bus.on('enclosure.weather.display', self.display_weather)
44  self.bus.on('mycroft.stop', self.clear_visemes)
45  self.bus.on('enclosure.mouth.events.activate',
46  self._activate_visemes)
47  self.bus.on('enclosure.mouth.events.deactivate',
49 
50  def _activate_visemes(self, event=None):
51  self.bus.on('enclosure.mouth.viseme_list', self.viseme_list)
52 
53  def _deactivate_visemes(self, event=None):
54  self.bus.remove('enclosure.mouth.viseme_list', self.viseme_list)
55 
56  def reset(self, event=None):
57  self.writer.write("mouth.reset")
58 
59  def talk(self, event=None):
60  self.writer.write("mouth.talk")
61 
62  def think(self, event=None):
63  self.writer.write("mouth.think")
64 
65  def listen(self, event=None):
66  self.writer.write("mouth.listen")
67 
68  def smile(self, event=None):
69  self.writer.write("mouth.smile")
70 
71  def viseme_list(self, event=None):
72  if event and event.data:
73  start = event.data['start']
74  visemes = event.data['visemes']
75  self.showing_visemes = True
76  for code, end in visemes:
77  if not self.showing_visemes:
78  break
79  if time.time() < start + end:
80  self.writer.write('mouth.viseme=' + code)
81  time.sleep(start + end - time.time())
82  self.reset()
83 
84  def clear_visemes(self, event=None):
85  self.showing_visemes = False
86 
87  def text(self, event=None):
88  text = ""
89  if event and event.data:
90  text = event.data.get("text", text)
91  self.writer.write("mouth.text=" + text)
92 
93  def __display(self, code, clear_previous, x_offset, y_offset):
94  """ Write the encoded image to enclosure screen.
95 
96  Arguments:
97  code (str): encoded image to display
98  clean_previous (str): if "True" will clear the screen before
99  drawing.
100  x_offset (int): x direction offset
101  y_offset (int): y direction offset
102  """
103  clear_previous = int(str(clear_previous) == "True")
104  clear_previous = "cP=" + str(clear_previous) + ","
105  x_offset = "x=" + str(x_offset) + ","
106  y_offset = "y=" + str(y_offset) + ","
107 
108  message = "mouth.icon=" + x_offset + y_offset + clear_previous + code
109  # Check if message exceeds Arduino's serial buffer input limit 64 bytes
110  if len(message) > 60:
111  message1 = message[:31]
112  message2 = message[31:]
113  message1 += "$"
114  message2 += "$"
115  message2 = "mouth.icon=" + message2
116  self.writer.write(message1)
117  time.sleep(0.25) # writer bugs out if sending messages too rapidly
118  self.writer.write(message2)
119  else:
120  time.sleep(0.1)
121  self.writer.write(message)
122 
123  def display(self, event=None):
124  """ Display a Mark-1 specific code.
125  Arguments:
126  event (Message): messagebus message with data to display
127  """
128  code = ""
129  x_offset = ""
130  y_offset = ""
131  clear_previous = ""
132  if event and event.data:
133  code = event.data.get("img_code", code)
134  x_offset = int(event.data.get("xOffset", x_offset))
135  y_offset = int(event.data.get("yOffset", y_offset))
136  clear_previous = event.data.get("clearPrev", clear_previous)
137  self.__display(code, clear_previous, x_offset, y_offset)
138 
139  def display_image(self, event=None):
140  """ Display an image on the enclosure.
141 
142  The method uses PIL to convert the image supplied into a code
143  suitable for the Mark-1 display.
144 
145  Arguments:
146  event (Message): messagebus message with data to display
147  """
148  if not event:
149  return
150 
151  image_absolute_path = event.data['img_path']
152  refresh = event.data['clearPrev']
153  invert = event.data['invert']
154  x_offset = event.data['xOffset']
155  y_offset = event.data['yOffset']
156  threshold = event.data.get('threshold', 70) # default threshold
157  # to understand how this funtion works you need to understand how the
158  # Mark I arduino proprietary encoding works to display to the faceplate
159  img = Image.open(image_absolute_path).convert("RGBA")
160  img2 = Image.new('RGBA', img.size, (255, 255, 255))
161  width = img.size[0]
162  height = img.size[1]
163 
164  # strips out alpha value and blends it with the RGB values
165  img = Image.alpha_composite(img2, img)
166  img = img.convert("L")
167 
168  # crop image to only allow a max width of 16
169  if width > 32:
170  img = img.crop((0, 0, 32, height))
171  width = img.size[0]
172  height = img.size[1]
173 
174  # crop the image to limit the max height of 8
175  if height > 8:
176  img = img.crop((0, 0, width, 8))
177  width = img.size[0]
178  height = img.size[1]
179 
180  encode = ""
181 
182  # Each char value represents a width number starting with B=1
183  # then increment 1 for the next. ie C=2
184  width_codes = ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
185  'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
186  'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']
187 
188  height_codes = ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
189 
190  encode += width_codes[width - 1]
191  encode += height_codes[height - 1]
192  # Turn the image pixels into binary values 1's and 0's
193  # the Mark I face plate encoding uses binary values to
194  # binary_values returns a list of 1's and 0s'. ie ['1', '1', '0', ...]
195  binary_values = []
196  for i in range(width):
197  for j in range(height):
198  if img.getpixel((i, j)) < threshold:
199  if invert is False:
200  binary_values.append('1')
201  else:
202  binary_values.append('0')
203  else:
204  if invert is False:
205  binary_values.append('0')
206  else:
207  binary_values.append('1')
208 
209  # these values are used to determine how binary values
210  # needs to be grouped together
211  number_of_top_pixel = 0
212  number_of_bottom_pixel = 0
213 
214  if height > 4:
215  number_of_top_pixel = 4
216  number_of_bottom_pixel = height - 4
217  else:
218  number_of_top_pixel = height
219 
220  # this loop will group together the individual binary values
221  # ie. binary_list = ['1111', '001', '0101', '100']
222  binary_list = []
223  binary_code = ''
224  increment = 0
225  alternate = False
226  for val in binary_values:
227  binary_code += val
228  increment += 1
229  if increment == number_of_top_pixel and alternate is False:
230  # binary code is reversed for encoding
231  binary_list.append(binary_code[::-1])
232  increment = 0
233  binary_code = ''
234  alternate = True
235  elif increment == number_of_bottom_pixel and alternate is True:
236  binary_list.append(binary_code[::-1])
237  increment = 0
238  binary_code = ''
239  alternate = False
240 
241  # Code to let the Makrk I arduino know where to place the
242  # pixels on the faceplate
243  pixel_codes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
244  'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
245 
246  for binary_values in binary_list:
247  number = int(binary_values, 2)
248  pixel_code = pixel_codes[number]
249  encode += pixel_code
250 
251  self.__display(encode, refresh, x_offset, y_offset)
252 
253  def display_weather(self, event=None):
254  if event and event.data:
255  # Convert img_code to icon
256  img_code = event.data.get("img_code", None)
257  icon = None
258  if img_code == 0:
259  # sunny
260  icon = "IICEIBMDNLMDIBCEAA"
261  elif img_code == 1:
262  # partly cloudy
263  icon = "IIEEGBGDHLHDHBGEEA"
264  elif img_code == 2:
265  # cloudy
266  icon = "IIIBMDMDODODODMDIB"
267  elif img_code == 3:
268  # light rain
269  icon = "IIMAOJOFPBPJPFOBMA"
270  elif img_code == 4:
271  # raining
272  icon = "IIMIOFOBPFPDPJOFMA"
273  elif img_code == 5:
274  # storming
275  icon = "IIAAIIMEODLBJAAAAA"
276  elif img_code == 6:
277  # snowing
278  icon = "IIJEKCMBPHMBKCJEAA"
279  elif img_code == 7:
280  # wind/mist
281  icon = "IIABIBIBIJIJJGJAGA"
282 
283  temp = event.data.get("temp", None)
284  if icon is not None and temp is not None:
285  icon = "x=2," + icon
286  msg = "weather.display=" + str(temp) + "," + str(icon)
287  self.writer.write(msg)
def __display(self, code, clear_previous, x_offset, y_offset)
Definition: mouth.py:93


mycroft_ros
Author(s):
autogenerated on Mon Apr 26 2021 02:35:40