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
//! # bmOS_client //! //! bmOS_client is an executable in charge of receiving and parsing JSON input from stdin and sending intents with enough confidence (>0.6) to the address and port provided (to bmOS_server) //! //! ## Expected input //! bmOS_client expects JSON strings with the format specified [here](http://voice2json.org/formats.html), and looks for the intent's name and confidence, sending it if the confidence's enough. //! //! ## Warnings //! - bmOS_client is dumb. It will not check whether the intent it's sending is correctly defined in bmOS_server, so sending undefined intents will result in a panic in the target. //! - bmOS_client will panic if the server is unavailable upon sending an intent, or if the input it receives does not conform to the JSON input it expects. There are no attempts on recovering lost connections. //! - bmOS_server needs to be running and listening for new connections before bmOS_client starts. //! //! ## Recommended setup //! Everything is designed around voice2json as the source of intent recognition. The documentation is available [here](http://voice2json.org/#getting-started). The best results have been obtained with the [default profiles](http://voice2json.org/#supported-languages), both in English and Spanish, though one should expect a moderate amount of false positives if the environment is noisy. //! Below is an example of a barebones sentences.ini file (which is the only modification I did on the downloaded profiles): //! ```toml //! [hello] //! hi beemo //! hello beemo //! //! [song] //! play a song beemo //! //! [sad] //! you are ugly beemo //! //! [angry] //! i hate you beemo //! //! [surprise] //! surprise beemo //! //! [chronometer] //! start a chronometer beemo //! give me a chronometer beemo //! //! [5more] //! give it (five | 5) minutes more //! //! [10more] //! give it (ten | 10) minutes more //! //! [20more] //! give it (twenty | 20) minutes more //! //! [5less] //! take (five | 5) minutes less //! //! [10less] //! take (ten | 10) minutes less //! //! [20less] //! take (twenty | 20) minutes less //! //! [done] //! it is done beemo //! i have finished beemo //!``` //! //! Below is an example of a bash script which sets up audio streaming from bmOS_server's host, receives it on the local host running bmOS_client and pipes it through voice2json up to stdout (which should then be piped to bmOS_client) //! ```bash //! ssh pi@[ip_address_here] "rec -c 2 -t wav -" | sox - -d -t raw -r 22.05k -b 8 - gain -5 | sudo ./voice2json.bash --profile /profile/ transcribe-stream --audio-source - | sudo ./voice2json.bash --profile /profile/ recognize-intent //! ``` //! //! Where voice2json.bash contains a script to fire up voice2json's docker container, where en-us_kaldi-zamia is the name of the profile used, and should be changed to whichever is in use: //! ```bash //! docker run -i \ //! --init \ //! -v "[path_to_local_dir]/voice2json/profile:/profile/" \ //! -v "[path_to_local_dir]/voice2json/profile:/root/.local/share/voice2json/en-us_kaldi-zamia/" \ //! -w "$(pwd)" \ //! -e "HOME=${HOME}" \ //! synesthesiam/voice2json "$@" //! ``` use json_minimal::*; /// Returns a tuple consisting of the intent's name and its confidence, /// extracted from the provided json line. /// /// # Panic /// The function will panic if the json string is incorrectly formatted. /// /// # Example input /// Directly from voice2json: /// /// {"text": "hi beemo", "likelihood": 1.0, "transcribe_seconds": 5.955755680000948, "wav_seconds": 7.136, "tokens": ["hi", "beemo"], "timeout": false, "intent": {"name": "hi_bmo", "confidence": 1.0}, "entities": [], "raw_text": "hi beemo", "recognize_seconds": 0.00013091099935991224, "raw_tokens": ["hi", "beemo"], "speech_confidence": null, "wav_name": null, "slots": {}} pub fn parse_intent(line: &[u8]) -> (String, f64) { let json = match Json::parse(line) { Ok(json) => { json }, Err( (position,message) ) => { panic!("Error on {} at position {}!!!",message, position); } }; let search = "intent"; let intent_json = match json.get(search) { Some(a) => a, None => panic!("intent not found in the provided json string"), }; let intent_name = match intent_json.unbox() { Json::OBJECT {name: _, value} => match value.unbox() { Json::JSON(values) => { assert_eq!(values.len(),2); match &values[0] { Json::OBJECT {name: _, value} => { value.unbox() }, json => { panic!("Couldn't parse the intent's name, found {:?}!!!",json); } } }, json => { panic!("Couldn't parse the intent's name, found {:?}!!!",json) } } _ => panic!("Couldn't parse the intent's name, found {:?}!!!",json) }; let intent_confidence = match intent_json.unbox() { Json::OBJECT {name: _, value} => match value.unbox() { Json::JSON(values) => { // We already know its length is 2 match &values[1] { Json::OBJECT { name: _ , value } => { value.unbox() }, json => { panic!("Couldn't parse the intent's confidence, found {:?}!!!",json); } } }, json => { panic!("Couldn't parse the intent's confidence, found {:?}!!!",json) } } _ => panic!("Couldn't parse the intent's confidence, found {:?}!!!",json) }; let intent_name = match intent_name { Json::STRING(val) => val, _ => panic!("The intent's name wasn't a string") }; let intent_confidence = match intent_confidence { Json::NUMBER(val) => val, _ => panic!("The intent's confidence wasn't a number") }; (intent_name.to_string(), *intent_confidence) // return a tuple with the results } #[cfg(test)] mod tests { #[test] fn test_message_passing() { use std::io::prelude::*; use std::net::{TcpListener, TcpStream}; use std::{thread,time}; let thread = thread::spawn(|| { let listener = TcpListener::bind("127.0.0.1:50000").unwrap(); for stream in listener.incoming().take(1) { //let mut buffer = [0; 1024]; let mut cosa : String = "".to_string(); stream.unwrap() //.read(&mut buffer).unwrap(); .read_to_string(&mut cosa).unwrap(); println!("Got: {}", cosa); assert_eq!(cosa, "hi_bmo,2.5".to_string()); } }); thread::sleep(time::Duration::from_secs(3)); let mut stream = TcpStream::connect("127.0.0.1:50000").unwrap(); let (name, conf) = ("hi_bmo", 2.5); stream.write(format!("[{},{}]", name, conf).as_bytes()).unwrap(); println!("wrote to stream"); thread.join().unwrap(); } }