From c2bf6d60e4c6773e2a686fcbba7887df4f3ad8ab Mon Sep 17 00:00:00 2001 From: openhabian configurator Date: Thu, 18 Jul 2019 20:07:57 +0200 Subject: [PATCH] peoplecounter, IR, spacestatus, proxy config, amplifier --- README.txt | 29 ++++++++- items/level2.items | 26 +++++++- items/statuslights.items | 1 + persistence/mapdb.persist | 1 + rules/alllights.rules | 2 + rules/ir.rules | 2 +- rules/level2.rules | 83 ++++++++++++++++++++++++-- rules/statuslights.rules | 15 +++++ services/addons.cfg | 4 +- sitemaps/default.sitemap | 68 +++++++++++---------- syn2cat/.gitignore | 1 + syn2cat/spaceapi-set-status.sh | 24 ++++++++ syn2cat/spaceapi-update-peoplecount.sh | 13 ++++ things/level2.things | 17 ++++-- 14 files changed, 238 insertions(+), 48 deletions(-) create mode 100644 items/statuslights.items create mode 100644 rules/statuslights.rules create mode 100644 syn2cat/.gitignore create mode 100755 syn2cat/spaceapi-set-status.sh create mode 100755 syn2cat/spaceapi-update-peoplecount.sh diff --git a/README.txt b/README.txt index 7b80edd..f848758 100644 --- a/README.txt +++ b/README.txt @@ -1,4 +1,13 @@ +An openHAB implementaiton for the level2 hackerspace + +Dump this into /etc/openhab2 + + + + + Installing proxy for paperUI + sudo bash apt-get update apt-get install nginx @@ -8,13 +17,29 @@ cat >/etc/nginx/sites-available/openhab <<"EOF" server { listen 80; server_name lights.level2.lu; - +# https://community.openhab.org/t/occasional-offline-in-basicui-when-proxying-via-nginx-connection-timed-out/63347 location / { - proxy_pass http://localhost:8080/basicui/; + proxy_pass http://localhost:8080/; + proxy_buffering off; + proxy_request_buffering off; + proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Server $host; + client_body_buffer_size 0; + client_max_body_size 0; + proxy_max_temp_file_size 0; + proxy_read_timeout 18000; + proxy_send_timeout 18000; + + gzip off; + } + location ~ ^/(paperui|habmin) { + # return 301 /basicui/app; + deny all; } } EOF diff --git a/items/level2.items b/items/level2.items index 3bf22ff..3df0a94 100644 --- a/items/level2.items +++ b/items/level2.items @@ -12,14 +12,16 @@ Group area42 "Area42" (main_floor) //------------------------------------------------------------------------------- Number entrance_people_counter "People Counter [%s]" (entrance) +Number entrance_people_counter_max "People Counter Max [%s]" (entrance) Switch entrance_door_status "Door Status" { channel="mqtt:topic:entrance_door_status:status" } Switch entrance_ceiling_lamps "Ceiling Lamps" (entrance) { channel="mqtt:topic:entrance_ceiling_lamps:power" } +Switch entrance_marbleadder_lamps "Marbleadder Lamps" (entrance) { channel="mqtt:topic:mosquitto:entrance_marbleadder_lamps:power" } Switch chill_zone_lamps "Lamps" (chill_zone) { channel="mqtt:topic:chill_zone_lamps:power" } Switch engineering_table_lamps "Table Lamps" (engineering) { channel="mqtt:topic:engineering_table_lamps:power" } Switch engineering_ceiling_lamps "Ceiling Lamps" (engineering) { channel="mqtt:topic:engineering_ceiling_lamps:power" } Switch engineering_status_lamp "Status Lamp" (engineering) { channel="mqtt:topic:engineering_status_lamp:power" } Switch engineering_ceiling_phone_flash "Ceiling Phone Flash" (engineering) { channel="mqtt:topic:engineering_phone_flash:pulse" } -Switch area42_status_lamp "Status Lamp" (area42) { channel="mqtt:topic:area42_status_lamp:power" } +Switch area42_status_lamp "Status Lamp" (area42) { channel="mqtt:topic:mosquitto:area42_status_lamp:power" } Switch lab_soldering_table "Soldering table" (lab) { channel="mqtt:topic:mosquitto:lab_soldering_table:power" } @@ -41,6 +43,26 @@ String chill_zone_chromecast_status Switch chill_zone_beamer "Beamer" (chill_zone) +String chill_zone_beamer_input "beamer input" (chill_zone) +// please help: beamer current state can be optained like this: +//wget -qO - 'http://10.2.113.7/tgi/return.tgi?query=info' |awk -F'[<>]' '//{print substr($3,33,2)}' +// the awk extracts the info. It is in format 'NG' or 'data +//NG = off +// from data, the interesting part is chars 33 and 34 +// vv +//2a3139c616503735303000000048e60102ac0c010076 +//00=no signal +//01=VGA1 +//02=DVI (slideshow) +//03=composite +//04=s-video +//05=component +//09=VGA2 +//11=LAN +//12=USB A (presentation to go) +//13=USB B (WTF?) +//15=hdmi2 (chromecast) + // ---------------------------------------------------------------------------- @@ -63,6 +85,6 @@ Switch chill_zone_screen_button_down Rollershutter chill_projection_screen "Projection Screen" -String IRCode_01 "IR Code" { channel="mqtt:topic:mosquitto:irremote:ir_code" } +String IRCode "IR Code" { channel="mqtt:topic:mosquitto:irremote:ir_code" } String proxyIR diff --git a/items/statuslights.items b/items/statuslights.items new file mode 100644 index 0000000..c3487c5 --- /dev/null +++ b/items/statuslights.items @@ -0,0 +1 @@ +Switch all_status_lights "All Status Lights" (engineering) \ No newline at end of file diff --git a/persistence/mapdb.persist b/persistence/mapdb.persist index 72b5227..5b882ef 100644 --- a/persistence/mapdb.persist +++ b/persistence/mapdb.persist @@ -4,4 +4,5 @@ Strategies { Items { entrance_people_counter: strategy = everyChange + entrance_people_counter_max: strategy = everyChange } diff --git a/rules/alllights.rules b/rules/alllights.rules index 08c095d..759d603 100644 --- a/rules/alllights.rules +++ b/rules/alllights.rules @@ -7,6 +7,7 @@ then //all_engineering_lights.postUpdate("ON") chill_zone_lamps.sendCommand("ON") entrance_ceiling_lamps.sendCommand("ON") + entrance_marbleadder_lamps.sendCommand("ON") end @@ -18,6 +19,7 @@ then engineering_ceiling_lamps.sendCommand("OFF") chill_zone_lamps.sendCommand("OFF") entrance_ceiling_lamps.sendCommand("OFF") + entrance_marbleadder_lamps.sendCommand("OFF") end diff --git a/rules/ir.rules b/rules/ir.rules index 7562e4f..a5beebd 100644 --- a/rules/ir.rules +++ b/rules/ir.rules @@ -2,5 +2,5 @@ rule "proxy IR mqtt" when Item proxyIR received command then - IRCode_01.sendCommand(transform("MAP", "ir.map", receivedCommand.toString)) + IRCode.sendCommand(transform("MAP", "ir.map", receivedCommand.toString)) end \ No newline at end of file diff --git a/rules/level2.rules b/rules/level2.rules index 3d4613a..5193ec0 100644 --- a/rules/level2.rules +++ b/rules/level2.rules @@ -1,35 +1,69 @@ var Timer ChromeCastOffTimer = null +val peoplecountermax = 0 // if openhab is restarted, the max value is lost... do we need persistence, how does that work? +val SpaceStatus = '' + +rule "Startup" +when + System started +then + if (entrance_door_status.state.toString == 'OFF') { + SpaceStatus = 'open' + } else { + SpaceStatus = 'closed' + } +end rule "Door Status" when Item entrance_door_status changed then val state = entrance_door_status.state.toString - + val peoplecountermax // the sensor state is inverted: // OFF means opened // ON means opened if (state == 'OFF') { logInfo('door', 'opened') + SpaceStatus = 'open' entrance_ceiling_lamps.sendCommand("ON") + entrance_marbleadder_lamps.sendCommand("ON") chill_zone_lamps.sendCommand("ON") engineering_table_lamps.sendCommand("ON") engineering_ceiling_lamps.sendCommand("ON") engineering_status_lamp.sendCommand("ON") area42_status_lamp.sendCommand("ON") + executeCommandLine("/etc/openhab2/syn2cat/spaceapi-set-status.sh open") + // needs current time in OpenTime + // sendTweet("It's "+OpenTime+" and we are open \o/ \ncome in and create something awesome =) \nhttps://Level2.lu/openingTimes") + } else { logInfo('door', 'closed') + SpaceStatus = 'closed' // turn the beamer off sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3102fd0660") chill_zone_screen_button_up.sendCommand("ON") entrance_ceiling_lamps.sendCommand("OFF") + entrance_marbleadder_lamps.sendCommand("OFF") chill_zone_lamps.sendCommand("OFF") engineering_table_lamps.sendCommand("OFF") engineering_ceiling_lamps.sendCommand("OFF") engineering_status_lamp.sendCommand("OFF") area42_status_lamp.sendCommand("OFF") lab_soldering_table.sendCommand("OFF") + IRCode.sendCommand(transform("MAP", "ir.map", "amp_off")) + executeCommandLine("/etc/openhab2/syn2cat/spaceapi-set-status.sh closed") + // needs current time in OpenTime + // and number of people in peopleMax="\nThere were up to $peopleMax hackers today.\n" + peoplecountermax=entrance_people_counter_max.state + if(peoplecountermax>8) { + peopleMax="\nThere were up to "+peoplecountermax+" hackers today.\n" + } else { + peopleMax=".".repeat(peoplecountermax) + } + logInfo("door", "twitter message: "+ OpenTime+peopleMax) + // sendTweet("We just closed our doors at "+OpenTime+peopleMax+" See you very soon... \nhttps://Level2.lu/openingTimes") + entrance_people_counter_max.postUpdate(0) } @@ -47,10 +81,26 @@ then val count = java.lang.Integer.parseInt( transform("XPATH", "/table/tr[4]/td[2]/text()", response).trim() ) + if ( SpaceStatus == 'closed' ) { + count=0 + } if ( count != Integer.parseInt(entrance_people_counter.previousState().state.toString)) { entrance_people_counter.postUpdate(count) + // fresh run needs a max init, later we use persistence to init + if (entrance_people_counter_max.state == NULL) {entrance_people_counter_max.postUpdate(count) } + // count is bigger, so we got a new maximum + if(entrance_people_counter_max.state < count) { + entrance_people_counter_max.postUpdate(count) + logInfo("door", "People counter maximum set to "+ count) + } + executeCommandLine("/etc/openhab2/syn2cat/spaceapi-update-peoplecount.sh "+count) } end + // moved this to some external scripts, way easier with debugging and quoting + // /usr/bin/curl --max-time 1 --silent --data key="$spaceapikey" --data-urlencode 'sensors={"sensors":{"people_now_present":[{"value":'"$presency"'}]}}' https://spaceapi.syn2cat.lu/sensor/set + // executeCommandLine("/usr/bin/curl -v --max-time 1 --data key=$(cat /etc/openhab2/spaceapikey.txt) --data-urlencode 'sensors={\"sensors\":{\"people_now_present\":[{\"value\":'+count+'}]}}' https://spaceapi.syn2cat.lu/sensor/set >/tmp/c.log 2>&1") + // needs import java.net.URLEncoder + // sendHttpGetRequest("https://spaceapi.syn2cat.lu/sensor/set?"+URLEncoder::encode('key='+spaceapikey+'&sensors={sensors":{"people_now_present":[{"value":'+count+'}]}}') // ---------------------------------------------------------------------------- @@ -58,7 +108,8 @@ rule "Tweet People Count" when Item entrance_people_counter changed then -// sendTweet('There are currently ' + entrance_people_counter.state.toString + ' hackers present. Just pass by!') + // val people = entrance_people_counter.state.toString + // sendTweet('There are currently ' + people + ' hackers present. Just pass by!') end // ---------------------------------------------------------------------------- @@ -87,15 +138,39 @@ then if (state == 'ON') { // turn the beamer on - beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3101fe0660") + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3101fe0660") + chill_zone_screen_button_down.sendCommand("ON") } else { // turn the beamer off - beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3102fd0660") + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3102fd0660") } val beamerStatus = transform("XPATH", "/return/text()", beamerResponse).replace("\n", '') logInfo("beamer", "Beamer state: {}", beamerStatus) end +rule "Beamer input" +when + Item chill_zone_beamer_input received command +then + val state = chill_zone_beamer_input.state.toString + var beamerResponse + + if (state == 'hdmi1') { + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3109f6071475") + } else if (state == 'hdmi2') { + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3109f6071576") + } else if (state == 'dvi') { + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3109f6070566") + } else if (state == 'vga1') { + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3109f6070162") + } else if (state == 'vga2') { + beamerResponse = sendHttpGetRequest("http://10.2.113.7/tgi/return.tgi?command=2a3109f6070263") + } + else { } + val beamerStatus = transform("XPATH", "/return/text()", beamerResponse).replace("\n", '') + logInfo("beamer", "Beamer state: {}", beamerStatus) +end + // ---------------------------------------------------------------------------- diff --git a/rules/statuslights.rules b/rules/statuslights.rules new file mode 100644 index 0000000..d2422a0 --- /dev/null +++ b/rules/statuslights.rules @@ -0,0 +1,15 @@ +rule "Status Lights ON" +when + Item all_status_lights received update ON +then + engineering_status_lamp.sendCommand("ON") + area42_status_lamp.sendCommand("ON") +end + +rule "Status Lights OFF" +when + Item all_status_lights received update OFF +then + engineering_status_lamp.sendCommand("OFF") + area42_status_lamp.sendCommand("OFF") +end \ No newline at end of file diff --git a/services/addons.cfg b/services/addons.cfg index cec9130..c976f81 100644 --- a/services/addons.cfg +++ b/services/addons.cfg @@ -26,7 +26,7 @@ # Include legacy 1.x bindings. If set to true, it also allows the installation of 1.x bindings for which there is # already a 2.x version available (requires remote repo access, see above). (default is false) # -legacy = true +legacy = false # A comma-separated list of bindings to install (e.g. "binding = sonos,knx,zwave") binding = chromecast,mqtt,http1,exec,systeminfo @@ -38,7 +38,7 @@ ui = basic,habmin,habpanel,paper persistence = mapdb # A comma-separated list of actions to install (e.g. "action = mail,pushover") -action = mail,twitter,mqtt +action = mail,twitter # A comma-separated list of transformation services to install (e.g. "transformation = map,jsonpath") transformation = jsonpath,map,regex,scale,xpath diff --git a/sitemaps/default.sitemap b/sitemaps/default.sitemap index b00971a..0349aae 100644 --- a/sitemaps/default.sitemap +++ b/sitemaps/default.sitemap @@ -5,49 +5,51 @@ sitemap default label="Level2" { Switch item=all_lights label="All Lights" icon="light" } Frame label="Multimedia" { - Switch item=chill_zone_beamer label="Beamer" icon="projector" + Switch item=chill_zone_beamer label="Beamer" icon="projector" mappings=[ON="on",OFF="off"] + Selection item=chill_zone_beamer_input label="input" mappings=['hdmi1'='HDMI','hdmi2'='Chromecast','dvi'='Slideshow','vga1'='VGA'] // mir brauchen en screen rule mat 3 status dei een kann setzen // Switch item=chill_zone_screen_button_up label="Projection Screen (todo)" icon="screen" mappings=[up="up",stop="stop",down="down"] Switch item=chill_projection_screen icon="screen" mappings=[UP="up",STOP="stop",DOWN="down"] + } - Frame label="Amplifier" { - Switch item=proxyIR label="power" mappings=['amp_on'='on','amp_off'='off'] - Switch item=proxyIR label="volume" mappings=['amp_volup'='up','amp_voldown'='down'] - Selection item=proxyIR label="input" mappings=['amp_jack1'='jack1','amp_jack2'='jack2','amp_hdmi'='hdmi','amp_optical'='optical','amp_cd'='cd','amp_tuner'='tuner'] - } - Frame label="Bluray" { - Switch item=proxyIR label="power" mappings=['br_power'='power'] - Switch item=proxyIR label="play" mappings=['br_play'='play','br_pause'='pause'] icon="mediacontrol" - Switch item=proxyIR label="control" mappings=['br_menu'='menu','br_ok'='ok'] - Switch item=proxyIR label="navigate" mappings=['br_left'='left','br_up'='up','br_down'='down','br_right'='right'] icon="movecontrol" - Switch item=proxyIR label="language" mappings=['br_language'='audio','br_subtitle'='subtitles'] - } + Frame label="Amplifier" { + Switch item=proxyIR label="power" mappings=['amp_on'='on','amp_off'='off'] + Switch item=proxyIR label="volume" mappings=['amp_volup'='up','amp_voldown'='down'] + Selection item=proxyIR label="input" mappings=['amp_jack1'='jack1','amp_jack2'='jack2','amp_hdmi'='hdmi','amp_optical'='optical','amp_cd'='cd','amp_tuner'='tuner'] + } + Frame label="Bluray" { + Switch item=proxyIR label="power" mappings=['br_power'='power'] + Switch item=proxyIR label="play" mappings=['br_play'='play','br_pause'='pause'] icon="mediacontrol" + Switch item=proxyIR label="control" mappings=['br_menu'='menu','br_ok'='ok'] + Switch item=proxyIR label="navigate" mappings=['br_left'='left','br_up'='up','br_down'='down','br_right'='right'] icon="movecontrol" + Switch item=proxyIR label="language" mappings=['br_language'='audio','br_subtitle'='subtitles'] } Frame label="Other" { // een knaeppchen deen nemmen aus geht Switch item=lab_soldering_table label="Soldering Table" icon="poweroutlet" mappings=[OFF="off"] Switch item=engineering_ceiling_phone_flash label="Phone flashlight" icon="siren" mappings=[OFF="off"] // wei switchen ech dei 2 status lights?. Dat hei ass fir status un ze maachen falls se net un wiren - Switch item=area42_status_lamp label="Status lights (todo)" mappings=[ON="ON"] + Switch item=all_status_lights label="Status lights" mappings=[ON="on"] } - Frame label="Main Floor" icon="firstfloor" { - Group item=engineering - // Group item=lab - Group item=chill_zone - Group item=entrance +// Frame label="Main Floor" icon="firstfloor" { +// Group item=engineering +// // Group item=lab +// Group item=chill_zone +// Group item=entrance +// +// // Selection item=chill_zone_beamer_input +// // label="Beamer Input" +// // mappings=[ +// // 0="DVI", // Hackerspace video +// // 1="HDMI 1", +// // 2="HDMI 2", // Chromecast +// // 3="VGA 1", +// // 4="VGA 2" +// // ] +// } - // Selection item=chill_zone_beamer_input - // label="Beamer Input" - // mappings=[ - // 0="DVI", // Hackerspace video - // 1="HDMI 1", - // 2="HDMI 2", // Chromecast - // 3="VGA 1", - // 4="VGA 2" - // ] - } - Frame label="Ground Floor" icon="groundfloor" { - Group item=ground_floor - // Image url="https://raw.githubusercontent.com/wiki/openhab/openhab/images/features.png" - } +// Frame label="Ground Floor" icon="groundfloor" { +// Group item=ground_floor +// // Image url="https://raw.githubusercontent.com/wiki/openhab/openhab/images/features.png" +// } } diff --git a/syn2cat/.gitignore b/syn2cat/.gitignore new file mode 100644 index 0000000..ea1e341 --- /dev/null +++ b/syn2cat/.gitignore @@ -0,0 +1 @@ +spaceapikey.txt diff --git a/syn2cat/spaceapi-set-status.sh b/syn2cat/spaceapi-set-status.sh new file mode 100755 index 0000000..080974d --- /dev/null +++ b/syn2cat/spaceapi-set-status.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -x +exec >>/tmp/c.log +exec 2>&1 +if [ "$1" = "" ] || ( [ "$1" != "open" ] && [ "$1" != "closed" ] ) +then + echo "usage: $0 {open|closed}" + exit 1 +fi +spaceapikey="$(cat "$(dirname $0)"/spaceapikey.txt)" +nai=$(date +%s) +case "$1" in + "open" ) + openstate="true" + ;; + "closed" ) + openstate="false" + ;; + * ) echo "error" + exit + ;; +esac +/usr/bin/curl --max-time 1 --silent --data key="$spaceapikey" --data-urlencode sensors='{"state":{"open":'"$openstate"',"lastchange":'"$nai"'}}' https://spaceapi.syn2cat.lu/sensor/set + diff --git a/syn2cat/spaceapi-update-peoplecount.sh b/syn2cat/spaceapi-update-peoplecount.sh new file mode 100755 index 0000000..b575cc6 --- /dev/null +++ b/syn2cat/spaceapi-update-peoplecount.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -x +exec >>/tmp/c.log +exec 2>&1 +if [ "$1" = "" ] +then + echo "usage: $0 number" + exit 1 +fi +spaceapikey="$(cat "$(dirname $0)"/spaceapikey.txt)" +presency="$1" +/usr/bin/curl --max-time 1 --silent --data key="$spaceapikey" --data-urlencode sensors='{"sensors":{"people_now_present":[{"value":'"$presency"'}]}}' https://spaceapi.syn2cat.lu/sensor/set + diff --git a/things/level2.things b/things/level2.things index b84d76d..9b51f0d 100644 --- a/things/level2.things +++ b/things/level2.things @@ -14,13 +14,20 @@ Bridge mqtt:broker:mosquitto "Mosquitto" [ stateTopic="stat/entrance/door_status/POWER" ] } - + Thing topic entrance_ceiling_lamps "Entrance Ceiling Lamps" @ "Entrance" { Channels: Type switch : power "Power" [ commandTopic="cmnd/entrance/ceiling_lamp/Power" ] } + + Thing topic entrance_marbleadder_lamps "Entrance Marbleadder Lamps" @ "Entrance" { + Channels: + Type switch : power "Power" [ + commandTopic="cmnd/entrance/marbleadder_lamp/Power" + ] + } // ---------------------------------------------------------------------------- @@ -45,6 +52,7 @@ Bridge mqtt:broker:mosquitto "Mosquitto" [ commandTopic="cmnd/chill_zone/sonoffs/Power" ] } + Thing exec:command:beamerpower [command="/sbin/apcaccess -u", interval=5, timeout=2, autorun=false] // ---------------------------------------------------------------------------- @@ -90,7 +98,7 @@ Bridge mqtt:broker:mosquitto "Mosquitto" [ Thing topic lab_soldering_table "Soldering table" @ "Lab" { Channels: Type switch : power "Power" [ - commandTopic="cmnd/lab/soldering_table/Power", stateTopic="stat/lab/soldering_table/Power" + commandTopic="cmnd/lab/soldering_table/Power", stateTopic="stat/lab/soldering_table/POWER" ] } @@ -100,7 +108,8 @@ Bridge mqtt:broker:mosquitto "Mosquitto" [ Thing topic irremote "IR remote" @ "Chill Zone" { Type string : ir_code "ircode" - [ commandTopic="cmnd/chill_zone/irremote/IRSEND" + [ + commandTopic="cmnd/chill_zone/irremote/IRSEND" ] } @@ -110,4 +119,4 @@ Bridge mqtt:broker:mosquitto "Mosquitto" [ Thing chromecast:chromecast:b000d809ccb35a409a3dd3d14e0fbf08 [ipAddress="10.2.113.120", port=8009] //Thing exec:command:meaow [command="espeak -s 250 'meow meow meow meow meow meow meow meow' && espeak -s 250 -p 99 'meow meow meow meow meow meow' && espeak -s 250 -p 10 'meow meow meow meow meow meow' && espeak -s 30 -p 1 'meow meow meow meow'", autorun=false] -//Thing exec:command:meaow [command="espeak -s 250 'meow meow meow meow meow meow meow meow'", autorun=false] \ No newline at end of file +//Thing exec:command:meaow [command="espeak -s 250 'meow meow meow meow meow meow meow meow'", autorun=false]