Merge pull request #654 from MycroftAI/feature/issue-653
The configuration values were poorly documented. See issue #653pull/660/head
commit
0036f404b5
|
@ -14,14 +14,15 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
import json
|
||||
|
||||
import json
|
||||
import inflection
|
||||
import re
|
||||
from genericpath import exists, isfile
|
||||
from os.path import join, dirname, expanduser
|
||||
|
||||
from mycroft.util.log import getLogger
|
||||
from mycroft.util.json_helper import load_commented_json
|
||||
|
||||
__author__ = 'seanfitz, jdorleans'
|
||||
|
||||
|
@ -38,6 +39,25 @@ load_order = [DEFAULT_CONFIG, REMOTE_CONFIG, SYSTEM_CONFIG, USER_CONFIG]
|
|||
class ConfigurationLoader(object):
|
||||
"""
|
||||
A utility for loading Mycroft configuration files.
|
||||
|
||||
Mycroft configuration comes from four potential locations:
|
||||
* Defaults found in 'mycroft.conf' in the code
|
||||
* Remote settings (coming from home.mycroft.ai)
|
||||
* System settings (typically found at /etc/mycroft/mycroft.conf
|
||||
* User settings (typically found at /home/<user>/.mycroft/mycroft.conf
|
||||
These get loaded in that order on top of each other. So a value specified
|
||||
in the Default would be overridden by a value with the same name found
|
||||
in the Remote. And a value in the Remote would be overridden by a value
|
||||
set in the User settings. Not all values exist at all levels.
|
||||
|
||||
See comments in the 'mycroft.conf' for more information about specific
|
||||
settings and where they reside.
|
||||
|
||||
Note:
|
||||
Values are overridden by name. This includes all data under that name,
|
||||
so you if a value contains a complex structure, you cannot specify
|
||||
only a single component of that structure -- you have to override the
|
||||
entire structure.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
|
@ -81,9 +101,8 @@ class ConfigurationLoader(object):
|
|||
def __load(config, location):
|
||||
if exists(location) and isfile(location):
|
||||
try:
|
||||
with open(location) as f:
|
||||
config.update(json.load(f))
|
||||
LOG.debug("Configuration '%s' loaded" % location)
|
||||
config.update(load_commented_json(location))
|
||||
LOG.debug("Configuration '%s' loaded" % location)
|
||||
except Exception, e:
|
||||
LOG.error("Error loading configuration '%s'" % location)
|
||||
LOG.error(repr(e))
|
||||
|
@ -129,6 +148,8 @@ class RemoteConfiguration(object):
|
|||
def __load(config, setting):
|
||||
for k, v in setting.iteritems():
|
||||
if k not in RemoteConfiguration.IGNORED_SETTINGS:
|
||||
# Translate the CamelCase values stored remotely into the
|
||||
# Python-style names used within mycroft-core.
|
||||
key = inflection.underscore(re.sub(r"Setting(s)?", "", k))
|
||||
if isinstance(v, dict):
|
||||
config[key] = config.get(key, {})
|
||||
|
@ -150,14 +171,28 @@ class RemoteConfiguration(object):
|
|||
|
||||
class ConfigurationManager(object):
|
||||
"""
|
||||
Static management utility for calling up cached configuration.
|
||||
Static management utility for accessing the cached configuration.
|
||||
This configuration is periodically updated from the remote server
|
||||
to keep in sync.
|
||||
"""
|
||||
|
||||
__config = None
|
||||
__listener = None
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
The cached configuration.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary representing the Mycroft configuration
|
||||
"""
|
||||
return ConfigurationManager.get()
|
||||
|
||||
@staticmethod
|
||||
def init(ws):
|
||||
ConfigurationManager.__listener = ConfigurationListener(ws)
|
||||
# Start listening for configuration update events on the messagebus
|
||||
ConfigurationManager.__listener = _ConfigurationListener(ws)
|
||||
|
||||
@staticmethod
|
||||
def load_defaults():
|
||||
|
@ -186,7 +221,8 @@ class ConfigurationManager(object):
|
|||
"""
|
||||
Get cached configuration.
|
||||
|
||||
:return: A dictionary representing Mycroft configuration.
|
||||
Returns:
|
||||
dict: A dictionary representing the Mycroft configuration
|
||||
"""
|
||||
if not ConfigurationManager.__config:
|
||||
ConfigurationManager.load_defaults()
|
||||
|
@ -214,14 +250,21 @@ class ConfigurationManager(object):
|
|||
"""
|
||||
ConfigurationManager.update(config)
|
||||
location = SYSTEM_CONFIG if is_system else USER_CONFIG
|
||||
with open(location, 'rw') as f:
|
||||
config = json.load(f).update(config)
|
||||
loc_config = load_commented_json(location)
|
||||
with open(location, 'w') as f:
|
||||
config = loc_config.update(config)
|
||||
json.dump(config, f)
|
||||
|
||||
|
||||
class ConfigurationListener(object):
|
||||
class _ConfigurationListener(object):
|
||||
""" Utility to synchronize remote configuration changes locally
|
||||
|
||||
This listens to the messagebus for 'configuration.updated', and
|
||||
refreshes the cached configuration when this is encountered.
|
||||
"""
|
||||
|
||||
def __init__(self, ws):
|
||||
super(ConfigurationListener, self).__init__()
|
||||
super(_ConfigurationListener, self).__init__()
|
||||
ws.on("configuration.updated", self.updated)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -1,15 +1,56 @@
|
|||
{
|
||||
// Definition and documentation of all variables used by mycroft-core.
|
||||
//
|
||||
// Settings seen here are considered DEFAULT. Settings can also be
|
||||
// overridden at the REMOTE level (set by the user via
|
||||
// https://home.mycroft.ai), at the SYSTEM level (typically in the file
|
||||
// '/etc/mycroft/mycroft.conf'), or at the USER level (typically in the
|
||||
// file '~/.mycroft/mycroft.conf').
|
||||
//
|
||||
// The Override: comment indicates at what level (if any) this is
|
||||
// overridden by the system to a value besides the default shown here.
|
||||
|
||||
// Language used for speech-to-text and text-to-speech.
|
||||
// Code is a BCP-47 identifier (https://tools.ietf.org/html/bcp47), lowercased
|
||||
// TODO: save unmodified, lowercase upon demand
|
||||
// Override: none
|
||||
"lang": "en-us",
|
||||
|
||||
// Measurement units, either 'metric' or 'english'
|
||||
// Override: REMOTE
|
||||
"system_unit": "metric",
|
||||
|
||||
// Time format, either 'half' (e.g. "11:37 pm") or 'full' (e.g. "23:37")
|
||||
// Override: REMOTE
|
||||
"time_format": "half",
|
||||
|
||||
// Date format, either 'MDY' (e.g. "11-29-1978") or 'DMY' (e.g. "29-11-1978")
|
||||
// Override: REMOTE
|
||||
"date_format": "MDY",
|
||||
|
||||
// Play a beep when system begins to listen?
|
||||
// Override: none
|
||||
"confirm_listening": true,
|
||||
|
||||
// File locations of sounds to play for system events
|
||||
// Override: none
|
||||
"sounds": {
|
||||
"start_listening": "snd/start_listening.wav",
|
||||
"end_listening": "snd/end_listening.wav"
|
||||
},
|
||||
|
||||
// Mechanism used to play WAV audio files
|
||||
// Override: SYSTEM
|
||||
"play_wav_cmdline": "aplay %1",
|
||||
|
||||
// Mechanism used to play MP3 audio files
|
||||
// Override: SYSTEM
|
||||
"play_mp3_cmdline": "mpg123 %1",
|
||||
|
||||
// Location where the system resides
|
||||
// NOTE: Although this is set here, an Enclosure can override the value.
|
||||
// For example a mycroft-core running in a car could use the GPS.
|
||||
// Override: REMOTE
|
||||
"location": {
|
||||
"city": {
|
||||
"code": "Lawrence",
|
||||
|
@ -34,22 +75,36 @@
|
|||
"offset": -21600000
|
||||
}
|
||||
},
|
||||
|
||||
// General skill values
|
||||
// Override: none
|
||||
"skills": {
|
||||
// Directory to look for user skills
|
||||
"directory": "~/.mycroft/skills",
|
||||
// TODO: Old unused kludge, remove from code
|
||||
"stop_threshold": 2.0
|
||||
},
|
||||
|
||||
// Address of the REMOTE server
|
||||
// Override: none
|
||||
"server": {
|
||||
"url": "https://api.mycroft.ai",
|
||||
"version": "v1",
|
||||
"update": true,
|
||||
"metrics": false
|
||||
},
|
||||
|
||||
// The mycroft-core messagebus' websocket
|
||||
// Override: none
|
||||
"websocket": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8181,
|
||||
"route": "/core",
|
||||
"ssl": false
|
||||
},
|
||||
|
||||
// Settings used by the wake-up-word listener
|
||||
// Override: REMOTE
|
||||
"listener": {
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
|
@ -59,22 +114,42 @@
|
|||
"multiplier": 1.0,
|
||||
"energy_ratio": 1.5
|
||||
},
|
||||
|
||||
// Mark 1 enclosure settings
|
||||
// Override: SYSTEM (e.g. Picroft)
|
||||
"enclosure": {
|
||||
// Platform name (e.g. 'Picroft', 'Mark_1'
|
||||
// Override: SYSTEM
|
||||
# "platform": "picroft",
|
||||
|
||||
// COMM params to the Arduino/faceplate
|
||||
"port": "/dev/ttyAMA0",
|
||||
"rate": 9600,
|
||||
"timeout": 5.0,
|
||||
|
||||
// ??
|
||||
"update": true,
|
||||
|
||||
// Run a self test at bootup?
|
||||
"test": false
|
||||
},
|
||||
"log_level": "DEBUG",
|
||||
"ignore_logs": ["enclosure.mouth.viseme"],
|
||||
|
||||
|
||||
"session": {
|
||||
"ttl": 180
|
||||
},
|
||||
|
||||
// Speech to Text parameters
|
||||
// Override: REMOTE
|
||||
"stt": {
|
||||
// Engine. Options: "mycroft", "google", "wit", "ibm"
|
||||
"module": "mycroft"
|
||||
},
|
||||
|
||||
// Text to Speech parameters
|
||||
// Override: REMOTE
|
||||
"tts": {
|
||||
// Engine. Options: "mimic", "google", "marytts", "fatts", "espeak", "spdsay"
|
||||
"module": "mimic",
|
||||
"mimic": {
|
||||
"voice": "ap"
|
||||
|
@ -84,6 +159,12 @@
|
|||
"voice": "m1"
|
||||
}
|
||||
},
|
||||
|
||||
// =================================================================
|
||||
// All of the follow are specific to particular skills and will soon
|
||||
// be removed from this file.
|
||||
// =================================================================
|
||||
|
||||
"wifi": {
|
||||
"setup": false
|
||||
},
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# Copyright (c) 2017 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def load_commented_json(filename):
|
||||
""" Loads an JSON file, ignoring comments
|
||||
|
||||
Supports a trivial extension to the JSON file format. Allow comments
|
||||
to be embedded within the JSON, requiring that a comment be on an
|
||||
independent line starting with '//' or '#'.
|
||||
|
||||
NOTE: A file created with these style comments will break strict JSON
|
||||
parsers. This is similar to but lighter-weight than "human json"
|
||||
proposed at https://hjson.org
|
||||
|
||||
Args:
|
||||
filename (str): path to the commented JSON file
|
||||
|
||||
Returns:
|
||||
obj: decoded Python object
|
||||
"""
|
||||
with open(filename) as f:
|
||||
contents = f.read()
|
||||
|
||||
return json.loads(uncomment_json(contents))
|
||||
|
||||
|
||||
def uncomment_json(commented_json_str):
|
||||
""" Removes comments from a JSON string.
|
||||
|
||||
Supporting a trivial extension to the JSON format. Allow comments
|
||||
to be embedded within the JSON, requiring that a comment be on an
|
||||
independent line starting with '//' or '#'.
|
||||
|
||||
Example...
|
||||
{
|
||||
// comment
|
||||
'name' : 'value'
|
||||
}
|
||||
|
||||
Args:
|
||||
commented_json_str (str): a JSON string
|
||||
|
||||
Returns:
|
||||
str: uncommented, legal JSON
|
||||
"""
|
||||
lines = commented_json_str.splitlines()
|
||||
# remove all comment lines, starting with // or #
|
||||
nocomment = []
|
||||
for line in lines:
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("//") or stripped.startswith("#"):
|
||||
continue
|
||||
nocomment.append(line)
|
||||
|
||||
return " ".join(nocomment)
|
|
@ -0,0 +1,168 @@
|
|||
// Leading comment
|
||||
|
||||
{
|
||||
// C-style comment
|
||||
"lang": "en-us",
|
||||
|
||||
// Comment spaced out
|
||||
"system_unit": "metric",
|
||||
|
||||
// Comment tabbed out
|
||||
|
||||
"time_format": "half",
|
||||
|
||||
// comment and // another inside
|
||||
"date_format": "MDY",
|
||||
|
||||
// Comment with " inside it
|
||||
"confirm_listening": true,
|
||||
|
||||
# Python-style comment
|
||||
"sounds": {
|
||||
# Commment with no space
|
||||
"start_listening": "snd/start_listening.wav",
|
||||
# Comment inside with a tab
|
||||
# and multiple lines
|
||||
"end_listening": "snd/end_listening.wav"
|
||||
// and a final thought
|
||||
},
|
||||
"play_wav_cmdline": "aplay %1",
|
||||
"play_mp3_cmdline": "mpg123 %1",
|
||||
"location": {
|
||||
"city": {
|
||||
"code": "Lawrence",
|
||||
"name": "Lawrence",
|
||||
"state": {
|
||||
"code": "KS",
|
||||
"name": "Kansas",
|
||||
"country": {
|
||||
"code": "US",
|
||||
"name": "United States"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coordinate": {
|
||||
"latitude": 38.971669,
|
||||
"longitude": -95.23525
|
||||
},
|
||||
"timezone": {
|
||||
"code": "America/Chicago",
|
||||
"name": "Central Standard Time",
|
||||
"dstOffset": 3600000,
|
||||
"offset": -21600000
|
||||
}
|
||||
},
|
||||
"skills": {
|
||||
"directory": "~/.mycroft/skills",
|
||||
"stop_threshold": 2.0
|
||||
},
|
||||
"server": {
|
||||
"url": "https://api.mycroft.ai",
|
||||
"version": "v1",
|
||||
"update": true,
|
||||
"metrics": false
|
||||
},
|
||||
"websocket": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8181,
|
||||
"route": "/core",
|
||||
"ssl": false
|
||||
},
|
||||
"listener": {
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
"wake_word": "hey mycroft",
|
||||
"phonemes": "HH EY . M AY K R AO F T",
|
||||
"threshold": 1e-90,
|
||||
"multiplier": 1.0,
|
||||
"energy_ratio": 1.5
|
||||
},
|
||||
"enclosure": {
|
||||
"port": "/dev/ttyAMA0",
|
||||
"rate": 9600,
|
||||
"timeout": 5.0,
|
||||
"update": true,
|
||||
"test": false
|
||||
},
|
||||
"log_level": "DEBUG",
|
||||
"ignore_logs": ["enclosure.mouth.viseme"],
|
||||
"session": {
|
||||
"ttl": 180
|
||||
},
|
||||
"stt": {
|
||||
"module": "mycroft"
|
||||
},
|
||||
"tts": {
|
||||
"module": "mimic",
|
||||
"mimic": {
|
||||
"voice": "ap"
|
||||
},
|
||||
"espeak": {
|
||||
"lang": "english-us",
|
||||
"voice": "m1"
|
||||
}
|
||||
},
|
||||
|
||||
// Mixture
|
||||
# of types
|
||||
// of comments
|
||||
|
||||
|
||||
"wifi": {
|
||||
"setup": false
|
||||
},
|
||||
"ConfigurationSkill": {
|
||||
"max_delay": 60
|
||||
},
|
||||
"WikipediaSkill": {
|
||||
"max_results": 5,
|
||||
"max_phrases": 2
|
||||
},
|
||||
"WolframAlphaSkill": {
|
||||
"api_key": "",
|
||||
"proxy": true
|
||||
},
|
||||
"WeatherSkill": {
|
||||
"api_key": "",
|
||||
"proxy": true,
|
||||
"temperature": "fahrenheit"
|
||||
},
|
||||
"NPRNewsSkill": {
|
||||
"url_rss": "http://www.npr.org/rss/podcast.php?id=500005"
|
||||
},
|
||||
"AlarmSkill": {
|
||||
"filename": "alarm.mp3",
|
||||
"max_delay": 600,
|
||||
"repeat_time": 20,
|
||||
"extended_delay": 60
|
||||
},
|
||||
"ReminderSkill": {
|
||||
"max_delay": 600,
|
||||
"repeat_time": 60,
|
||||
"extended_delay": 60
|
||||
},
|
||||
"VolumeSkill": {
|
||||
"default_level": 6,
|
||||
"min_volume": 0,
|
||||
"max_volume": 100
|
||||
},
|
||||
"AudioRecordSkill": {
|
||||
"filename": "/tmp/mycroft-recording.wav",
|
||||
"free_disk": 100,
|
||||
"max_time": 600,
|
||||
"notify_delay": 5,
|
||||
"rate": 16000,
|
||||
"channels": 1
|
||||
},
|
||||
"SkillInstallerSkill": {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Trailing comments
|
||||
# These go on
|
||||
# and on
|
||||
// and on
|
|
@ -0,0 +1,135 @@
|
|||
{
|
||||
"lang": "en-us",
|
||||
"system_unit": "metric",
|
||||
"time_format": "half",
|
||||
"date_format": "MDY",
|
||||
"confirm_listening": true,
|
||||
"sounds": {
|
||||
"start_listening": "snd/start_listening.wav",
|
||||
"end_listening": "snd/end_listening.wav"
|
||||
},
|
||||
"play_wav_cmdline": "aplay %1",
|
||||
"play_mp3_cmdline": "mpg123 %1",
|
||||
"location": {
|
||||
"city": {
|
||||
"code": "Lawrence",
|
||||
"name": "Lawrence",
|
||||
"state": {
|
||||
"code": "KS",
|
||||
"name": "Kansas",
|
||||
"country": {
|
||||
"code": "US",
|
||||
"name": "United States"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coordinate": {
|
||||
"latitude": 38.971669,
|
||||
"longitude": -95.23525
|
||||
},
|
||||
"timezone": {
|
||||
"code": "America/Chicago",
|
||||
"name": "Central Standard Time",
|
||||
"dstOffset": 3600000,
|
||||
"offset": -21600000
|
||||
}
|
||||
},
|
||||
"skills": {
|
||||
"directory": "~/.mycroft/skills",
|
||||
"stop_threshold": 2.0
|
||||
},
|
||||
"server": {
|
||||
"url": "https://api.mycroft.ai",
|
||||
"version": "v1",
|
||||
"update": true,
|
||||
"metrics": false
|
||||
},
|
||||
"websocket": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8181,
|
||||
"route": "/core",
|
||||
"ssl": false
|
||||
},
|
||||
"listener": {
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
"wake_word": "hey mycroft",
|
||||
"phonemes": "HH EY . M AY K R AO F T",
|
||||
"threshold": 1e-90,
|
||||
"multiplier": 1.0,
|
||||
"energy_ratio": 1.5
|
||||
},
|
||||
"enclosure": {
|
||||
"port": "/dev/ttyAMA0",
|
||||
"rate": 9600,
|
||||
"timeout": 5.0,
|
||||
"update": true,
|
||||
"test": false
|
||||
},
|
||||
"log_level": "DEBUG",
|
||||
"ignore_logs": ["enclosure.mouth.viseme"],
|
||||
"session": {
|
||||
"ttl": 180
|
||||
},
|
||||
"stt": {
|
||||
"module": "mycroft"
|
||||
},
|
||||
"tts": {
|
||||
"module": "mimic",
|
||||
"mimic": {
|
||||
"voice": "ap"
|
||||
},
|
||||
"espeak": {
|
||||
"lang": "english-us",
|
||||
"voice": "m1"
|
||||
}
|
||||
},
|
||||
"wifi": {
|
||||
"setup": false
|
||||
},
|
||||
"ConfigurationSkill": {
|
||||
"max_delay": 60
|
||||
},
|
||||
"WikipediaSkill": {
|
||||
"max_results": 5,
|
||||
"max_phrases": 2
|
||||
},
|
||||
"WolframAlphaSkill": {
|
||||
"api_key": "",
|
||||
"proxy": true
|
||||
},
|
||||
"WeatherSkill": {
|
||||
"api_key": "",
|
||||
"proxy": true,
|
||||
"temperature": "fahrenheit"
|
||||
},
|
||||
"NPRNewsSkill": {
|
||||
"url_rss": "http://www.npr.org/rss/podcast.php?id=500005"
|
||||
},
|
||||
"AlarmSkill": {
|
||||
"filename": "alarm.mp3",
|
||||
"max_delay": 600,
|
||||
"repeat_time": 20,
|
||||
"extended_delay": 60
|
||||
},
|
||||
"ReminderSkill": {
|
||||
"max_delay": 600,
|
||||
"repeat_time": 60,
|
||||
"extended_delay": 60
|
||||
},
|
||||
"VolumeSkill": {
|
||||
"default_level": 6,
|
||||
"min_volume": 0,
|
||||
"max_volume": 100
|
||||
},
|
||||
"AudioRecordSkill": {
|
||||
"filename": "/tmp/mycroft-recording.wav",
|
||||
"free_disk": 100,
|
||||
"max_time": 600,
|
||||
"notify_delay": 5,
|
||||
"rate": 16000,
|
||||
"channels": 1
|
||||
},
|
||||
"SkillInstallerSkill": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import unittest
|
||||
import json
|
||||
from mycroft.util.json_helper import load_commented_json, uncomment_json
|
||||
|
||||
|
||||
class TestFileLoad(unittest.TestCase):
|
||||
|
||||
def test_load(self):
|
||||
# Load normal JSON file
|
||||
with open('plain.json', 'rw') as f:
|
||||
data_from_plain = json.load(f)
|
||||
|
||||
# Load commented JSON file
|
||||
data_from_commented = load_commented_json('commented.json')
|
||||
|
||||
# Should be the same...
|
||||
self.assertEqual(data_from_commented, data_from_plain)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue