2from typing 
import Optional, Union
 
    3from random 
import choice
 
    5from functools 
import partial
 
   13from xconf.contrib 
import DirectoryConfig
 
   14from purepyindi2 
import device, properties, constants, messages
 
   15from purepyindi2.messages 
import DefNumber, DefSwitch, DefText
 
   16from magaox.indi.device 
import XDevice, BaseConfig
 
   18from .personality 
import Personality, Transition, Operation, SSML, Recording
 
   19from .opentts_bridge 
import speak, ssml_to_wav
 
   30    random_utterance_interval_sec : Union[float, int] = 
xconf.field(default=15 * 60, help=
"Seconds since last (real or random) utterance before a random utterance should play")
 
   31    cache : DirectoryConfig = 
xconf.field(default=DirectoryConfig(path=
"/tmp/audibleAlerts_cache"))
 
 
   34    return '{' in text 
or '}' in text
 
 
   37    config : AudibleAlertsConfig
 
   38    personality : Personality
 
   40    _speech_requests : list[Union[SSML, Recording]]
 
   42    default_voice : str = 
"coqui-tts:en_ljspeech"   
   43    personalities : list[str] = [
'default', 
'lab_mode',]
 
   44    active_personality : str = 
"default" 
   45    api_url : str = 
"http://localhost:5500/" 
   48    per_transition_cooldown_ts : dict[Transition, float]
 
   49    last_utterance_ts : float = 0
 
   50    last_utterance_chosen : Optional[str] = 
None 
   51    observers_device : str = 
'observers' 
   52    last_walkup : Optional[dict[str,str]] = 
None 
   53    last_walkup_ts : float = 0
 
   54    walkup_double_trigger_timeout_sec : float = 30
 
   58            log.warn(f
"Duplicated speech request while already enqueued: {sr}")
 
 
   63        if 'target' in new_message 
and new_message[
'target'] != existing_property[
'current']:
 
   64            self.log.
debug(f
"Setting new speech text: {new_message['target']}")
 
   65            existing_property[
'current'] = new_message[
'target']
 
   66            existing_property[
'target'] = new_message[
'target']
 
   67        self.update_property(existing_property)
 
 
   70        self.log.
debug(f
"{new_message['request']=}")
 
   72            current_text = self.properties[
'speech_text'][
'current']
 
   74                st = 
'<speak>' + current_text + 
'</speak>' 
   76                self.telem(
"speech_request", {
"text": current_text})
 
   77        self.update_property(existing_property)  
 
 
   83        self.update_property(existing_property)  
 
 
   86        existing_property[
'toggle'] = new_message[
'toggle']
 
   89            self.log.info(
"Muted")
 
   91            self.log.info(
"Unmuted")
 
   92        self.update_property(existing_property)
 
   93        self.telem(
"mute_toggle", {
"mute": self.
mutemute})
 
 
   97        for element_name 
in new_message:
 
   98            value = new_message[element_name]
 
  101                    self.log.
debug(f
"Already did {self.last_walkup[which_updated]}")
 
  105                    self.log.info(f
"Queueing walk-up {utterance} for {element_name}")
 
 
  113        if element_name 
not in new_message:
 
  115        value = new_message[element_name]
 
  117            self.log.
debug(f
"Judging reaction for {element_name} change to {repr(value)} using {transition}")
 
  118            self.log.
debug(f
"before check {self.latch_transitions=}")
 
  121            self.log.
debug(f
"{new_message}\n{transition.compare(value)=}, last value was {last_value}, {value != last_value=} {(not transition.compare(last_value))=}")
 
  131                self.log.
debug(f
"after update {self.latch_transitions=}")
 
  132                self.log.
debug(f
"latched {transition=} with {value=}")
 
  134            sec_since_trigger = 
time.time() - last_transition_ts
 
  136            self.log.
debug(f
"Checking for debounce: {sec_since_trigger=} {debounce_expired=}")
 
  138                utterance = choice(utterance_choices)
 
  139                self.log.
debug(f
"Submitting speech request: {utterance}")
 
  142                self.log.
debug(f
"Would have spoken, but it's only been {sec_since_trigger=}")
 
  145                self.log.
debug(f
"un-latch {transition}, so next time we change to a value that compares True we trigger again. ({last_value=} {value=})")
 
  146                self.log.
debug(f
"before {self.latch_transitions=}")
 
  149                self.log.
debug(f
"after {self.latch_transitions=}")
 
  152                self.log.
debug(f
"Got {new_message.device}.{new_message.name} but {transition=} did not match")
 
 
  159        for sub 
in substitutables:
 
  161            value = self.client[indi_id]
 
  164            self.log.
debug(f
"Replacing {repr(sub)} with {value=}")
 
  165            if value 
is not None:
 
  168                    value = 
"{:.1f}".format(value)
 
  169                except (TypeError, ValueError):
 
  172        return SSML(speech_text)
 
 
  177        active_personality = 
None 
  181                active_personality = elem
 
  183        if active_personality 
is not None:
 
  184            self.log.info(f
"Switching to {active_personality=}")
 
  187        self.update_property(prop)
 
 
  193        for elem 
in new_message:
 
  196                self.log.info(f
"Soundboard requested {srq}")
 
  199        self.update_property(prop)
 
 
  202        personality_file = 
os.path.join(HERE, 
"personalities", f
"{personality_name}.xml")
 
  207                log.exception(f
"Tried to remove {cb=} {device_name=} {property_name=}")
 
  212        self.log.info(f
"Loading personality from {personality_file}")
 
  233                    device_name=device_name,
 
  234                    property_name=property_name
 
  237                self.log.
debug(f
"Registered reaction handler on {device_name=} {property_name=} {element_name=} using transition {t}")
 
  239                    self.log.
debug(f
"{reaction.indi_id}: {t}: {utterance}")
 
  243                            self.log.
debug(f
"Caching synthesis to {result}")
 
  245                            self.log.
debug(f
"Cannot pre-cache because there are substitutions to be made")
 
  251        self.telem(
"load_personality", {
'name': personality_name})
 
  252        self.send_all_properties()
 
 
  263            self.log.info(
"Waiting for connection...")
 
  265        self.log.info(
"Connected.")
 
  266        self.log.
debug(f
"Caching synthesis output to {self.config.cache.path}")
 
  307            name=
"reload_personality",
 
  313        self.log.info(
"Set up complete")
 
 
  319                self.log.
debug(f
"Would have said: {repr(speech)}, but muted")
 
  321                self.log.info(f
"Speaking: {repr(speech)}")
 
  323                self.log.
debug(
"Speech complete")
 
  332                self.log.
debug(f
"Would have said: {repr(next_utterance)}, but muted")
 
  334                self.log.info(f
"Randomly spouting off: {repr(next_utterance)}")
 
 
 
load_personality(self, personality_name)
handle_soundboard_switch(self, properties.IndiProperty prop, new_message)
reaction_handler(self, new_message, element_name, transition, utterance_choices)
Optional last_utterance_chosen
handle_speech_request(self, existing_property, new_message)
handle_mute_toggle(self, existing_property, new_message)
AudibleAlertsConfig config
enqueue_speech_request(self, sr)
handle_personality_switch(self, properties.IndiProperty prop, new_message)
handle_speech_text(self, existing_property, new_message)
properties soundboard_sw_prop
per_transition_cooldown_ts
handle_reload_request(self, existing_property, new_message)
dict per_transition_cooldown_ts
walkup_handler(self, new_message)
contains_substitutions(text)