isSophie checkbox for light, and lightmap which is stored acros runs
This commit is contained in:
parent
f232d89166
commit
e3481a58ad
9 changed files with 189 additions and 60 deletions
|
@ -77,6 +77,9 @@ class CentralCommand(object):
|
|||
|
||||
self.hugveyWarnings = {}
|
||||
|
||||
self.lightMapFile = os.path.join('state','lightMap.json')
|
||||
self.lightMap = {}
|
||||
|
||||
eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue))
|
||||
|
||||
def loadConfig(self, filename):
|
||||
|
@ -92,10 +95,10 @@ class CentralCommand(object):
|
|||
self.config[arg] = getattr(self.args,arg)
|
||||
|
||||
self.hugvey_ids = [i + 1 for i in range(self.config['hugveys'])]
|
||||
self.loadLightMap()
|
||||
|
||||
self.loadLanguages()
|
||||
|
||||
|
||||
voice_dir = os.path.join(self.config['web']['files_dir'], 'voices')
|
||||
self.voiceStorage = VoiceStorage(voice_dir, self.languageConfig)
|
||||
varDb = os.path.join(
|
||||
|
@ -134,6 +137,25 @@ class CentralCommand(object):
|
|||
with open(lang_filename, 'r') as fp:
|
||||
self.languages[lang['code']] = json.load(fp)
|
||||
|
||||
def loadLightMap(self):
|
||||
if os.path.exists(self.lightMapFile):
|
||||
with open(self.lightMapFile) as fp:
|
||||
lightMap = json.load(fp)
|
||||
#json only has string keys, we want integers (not using pickle for human readability)
|
||||
self.lightMap = {int(k): v for k,v in lightMap.items()}
|
||||
logger.info(f"Loaded light mapping from {self.lightMapFile}")
|
||||
for hv_id in self.hugvey_ids:
|
||||
if hv_id not in self.lightMap:
|
||||
print(hv_id, self.lightMap)
|
||||
raise Exception("Invalid light map, not all hugveys are included. Remove lightMap.json")
|
||||
else:
|
||||
# by default each hv, has the same nr of light
|
||||
self.lightMap = {id: id for id in self.hugvey_ids}
|
||||
|
||||
def saveLightMap(self):
|
||||
with open(self.lightMapFile, 'w') as fp:
|
||||
json.dump(self.lightMap, fp, indent=4, sort_keys=True)
|
||||
logger.info(f"Wrote light mapping to {self.lightMapFile}")
|
||||
|
||||
def getHugveyStatus(self, hv_id, isSelected = False):
|
||||
status = {'id': hv_id}
|
||||
|
@ -148,7 +170,7 @@ class CentralCommand(object):
|
|||
|
||||
#: :type hv: HugveyState
|
||||
status['status'] = hv.getStatus()
|
||||
status['light_on'] = bool(hv.lightStatus)
|
||||
status['light'] = hv.lightTransitionStatus
|
||||
status['language'] = hv.language_code
|
||||
status['light_id'] = hv.lightId
|
||||
status['available'] = hv.isAvailable()
|
||||
|
@ -200,6 +222,16 @@ class CentralCommand(object):
|
|||
|
||||
return status
|
||||
|
||||
def setLightForHugvey(self, hv_id, lightId):
|
||||
if hv_id not in self.lightMap:
|
||||
logger.critical(f"Try to configure light for non-existing Hugvey {hv_id}")
|
||||
return
|
||||
|
||||
logger.info(f"Set light for hugvey: {hv_id} to {lightId}")
|
||||
self.lightMap[hv_id] = lightId
|
||||
self.hugveys[hv_id].setLightId(lightId)
|
||||
self.saveLightMap()
|
||||
|
||||
def getStatusSummary(self, selected_ids = []):
|
||||
status = {
|
||||
'uptime': "-" if not self.start_time else (time.time() - self.start_time),
|
||||
|
@ -373,6 +405,7 @@ class CentralCommand(object):
|
|||
while self.isRunning.is_set():
|
||||
logger.info(f'Instantiate hugvey #{hugvey_id}')
|
||||
h = HugveyState(hugvey_id, self)
|
||||
h.setLightId(self.lightMap[hugvey_id])
|
||||
# h.config(msg['host'], msg['ip'])
|
||||
self.hugveys[hugvey_id] = h
|
||||
r = h.run()
|
||||
|
@ -553,6 +586,7 @@ class HugveyState(object):
|
|||
self.setStatus(self.STATE_GONE)
|
||||
|
||||
self.requireRestartAfterStop = None
|
||||
self.lightTransitionStatus = {'intensity': self.command.config['light']['off_intensity'], 'duration': self.command.config['light']['fade_duration_id'], 'isSophie': False}
|
||||
|
||||
def __del__(self):
|
||||
self.logger.warn("Destroying hugvey object")
|
||||
|
@ -793,14 +827,16 @@ class HugveyState(object):
|
|||
|
||||
self.command.commandLight('/hugvey', [self.lightId, self.lightStatus])
|
||||
|
||||
def transitionLight(self, intensity, duration):
|
||||
def transitionLight(self, intensity, duration, isSophie = False):
|
||||
"""
|
||||
Intensity: 0-255
|
||||
duration: an integer between 0-92 indicating the lanbox fade times
|
||||
The light fade in & out for Sophie (a moment in the story) are an override, so that
|
||||
they come in even though all is out.
|
||||
"""
|
||||
self.lightIntensity = intensity
|
||||
self.logger.debug(f"Send /hugvey_fade {self.lightIntensity} {duration}")
|
||||
self.command.commandLight('/hugvey_fade', [self.lightId, intensity, int(duration)])
|
||||
self.lightTransitionStatus = {'intensity': intensity, 'duration': duration, 'isSophie': isSophie}
|
||||
self.logger.debug(f"Send /hugvey_fade {intensity} {duration} {1 if isSophie else 0}")
|
||||
self.command.commandLight('/hugvey_fade', [self.lightId, intensity, int(duration), 1 if isSophie else 0])
|
||||
|
||||
def setLightId(self, id):
|
||||
"""
|
||||
|
@ -890,6 +926,7 @@ class HugveyState(object):
|
|||
|
||||
if self.command.config['story']['loop']:
|
||||
if not self.blockRestart:
|
||||
if self.notShuttingDown:
|
||||
self.logger.info("Loop story")
|
||||
self.restart()
|
||||
else:
|
||||
|
@ -914,7 +951,7 @@ class HugveyState(object):
|
|||
self.streamer.addConsumer(self.player)
|
||||
|
||||
if self.command.config['voice']['record_dir']:
|
||||
self.logger.warn("Record Audio of conversation")
|
||||
self.logger.info("Record Audio of conversation")
|
||||
self.recorder = Recorder( self.id,
|
||||
self.command.config['voice']['src_rate'], self.command.config['voice']['record_dir'],
|
||||
self.command.config['voice']['record_voice'] if 'record_voice' in self.command.config['voice'] else False)
|
||||
|
|
|
@ -169,8 +169,9 @@ def getWebSocketHandler(central_command):
|
|||
central_command.setLoopTime(seconds)
|
||||
|
||||
def msgChangeLightId(self, hv_id, lightId):
|
||||
if central_command.hugveys[hv_id].eventQueue:
|
||||
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_id': lightId})
|
||||
central_command.setLightForHugvey(hv_id, lightId)
|
||||
# if central_command.hugveys[hv_id].eventQueue:
|
||||
# central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_id': lightId})
|
||||
|
||||
def msgChangeLightStatus(self, hv_id, status):
|
||||
if central_command.hugveys[hv_id].eventQueue:
|
||||
|
|
|
@ -1166,14 +1166,19 @@ class Configuration(object):
|
|||
time_factor = 1
|
||||
light0_intensity = 0
|
||||
light0_fade = 30. # fade duration in seconds
|
||||
light0_isSophie = False
|
||||
light1_intensity = 150
|
||||
light1_fade = 10.
|
||||
light1_isSophie = False
|
||||
light2_intensity = 75
|
||||
light2_fade = 10.
|
||||
light2_isSophie = False
|
||||
light3_intensity = 150
|
||||
light3_fade = 10.
|
||||
light3_isSophie = False
|
||||
light4_intensity = 150
|
||||
light4_fade = 10.
|
||||
light4_isSophie = False
|
||||
|
||||
@classmethod
|
||||
def initFromJson(configClass, data, story):
|
||||
|
@ -1187,7 +1192,8 @@ class Configuration(object):
|
|||
for i in range(5):
|
||||
l.append({
|
||||
'intensity': int(c[f"light{i}_intensity"]),
|
||||
'fade': float(c[f"light{i}_fade"])
|
||||
'fade': float(c[f"light{i}_fade"]),
|
||||
'isSophie': float(c[f"light{i}_isSophie"])
|
||||
})
|
||||
return l
|
||||
|
||||
|
@ -1783,7 +1789,7 @@ class Story(object):
|
|||
# Test stability of Central Command with deliberate crash
|
||||
# if self.timer.getElapsed() > 10:
|
||||
# raise Exception("Test exception")
|
||||
if not self.timer.hasMark('state_save') or self.timer.getElapsed('state_save') > 45:
|
||||
if not self.timer.hasMark('state_save') or self.timer.getElapsed('state_save') > 30:
|
||||
self.storeState()
|
||||
self.timer.setMark('state_save')
|
||||
|
||||
|
@ -1891,7 +1897,7 @@ class Story(object):
|
|||
preset = self.configuration.getLightPresets()[presetNr]
|
||||
self.currentLightPresetNr = presetNr
|
||||
|
||||
self.hugvey.transitionLight(preset['intensity'], preset['fade'])
|
||||
self.hugvey.transitionLight(preset['intensity'], preset['fade'], preset['isSophie'])
|
||||
|
||||
def getCurrentDirections(self):
|
||||
if self.currentMessage.id not in self.directionsPerMsg:
|
||||
|
@ -2082,6 +2088,7 @@ class Story(object):
|
|||
|
||||
@classmethod
|
||||
def hugveyHasSavedState(cls, hv_id):
|
||||
# print(os.path.exists(cls.getStateFilename(hv_id)), cls.getStateFilename(hv_id))
|
||||
return os.path.exists(cls.getStateFilename(hv_id))
|
||||
|
||||
@classmethod
|
||||
|
@ -2095,6 +2102,7 @@ class Story(object):
|
|||
story.logger = mainLogger.getChild(f"{story.hugvey.id}").getChild("story")
|
||||
# TODO: this is not really working because it is overridden by the set-status later.
|
||||
# story.hugvey.setLightStatus(story.lightStateSave)
|
||||
story.logger.critical(f"Light preset {story.currentLightPresetNr}")
|
||||
if story.currentLightPresetNr is not None:
|
||||
story.fadeLightPreset(story.currentLightPresetNr)
|
||||
return story
|
||||
|
|
|
@ -60,6 +60,12 @@ if __name__ == '__main__':
|
|||
rootLogger.addHandler(socket_handler)
|
||||
logger.info("Start server")
|
||||
|
||||
|
||||
try:
|
||||
print('\33]0;Hugvey server\a', end='', flush=True)
|
||||
command = CentralCommand(args=args, debug_mode=args.verbose > 0)
|
||||
command.loadConfig(args.config)
|
||||
command.start()
|
||||
finally:
|
||||
# reset terminal title
|
||||
print('\33]0;\a', end='', flush=True)
|
||||
|
|
|
@ -125,7 +125,10 @@ img.icon {
|
|||
font-weight: normal;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px; }
|
||||
top: 5px;
|
||||
font-size: 150%; }
|
||||
#status .hugvey h1 .light_id-hv_id {
|
||||
font-size: 75%; }
|
||||
#status .hugvey .status {
|
||||
font-style: italic;
|
||||
color: gray;
|
||||
|
|
6
www/js/crel.min.js
vendored
6
www/js/crel.min.js
vendored
|
@ -1,5 +1,11 @@
|
|||
!function(n,e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):n.crel=e()}(this,function(){function n(a){var d,s=arguments,p=s[1],y=2,m=s.length,x=n[o];if(a=n[u](a)?a:c.createElement(a),m>1){if((!e(p,r)||n[f](p)||Array.isArray(p))&&(--y,p=null),m-y==1&&e(s[y],"string"))a.textContent=s[y];else for(;y<m;++y)null!==(d=s[y])&&l(a,d);for(var v in p)if(x[v]){var A=x[v];e(A,t)?A(a,p[v]):a[i](A,p[v])}else e(p[v],t)?a[v]=p[v]:a[i](v,p[v])}return a}var e=function(n,e){return typeof n===e},t="function",r="object",i="setAttribute",o="attrMap",f="isNode",u="isElement",c=document,a=function(n){return n instanceof Node},d=function(n){return n instanceof Element},l=function(e,t){if(Array.isArray(t))return void t.map(function(n){l(e,n)});n[f](t)||(t=c.createTextNode(t)),e.appendChild(t)};return n[o]={},n[u]=d,n[f]=a,e(Proxy,"undefined")||(n.proxy=new Proxy(n,{get:function(e,t){return!(t in n)&&(n[t]=n.bind(null,t)),n[t]}})),n});
|
||||
|
||||
// for input type checkbox, map value to a checkbox
|
||||
crel.attrMap['checked_value'] = function(element, value) {
|
||||
if(value) {
|
||||
element.checked = 'checked';
|
||||
}
|
||||
};
|
||||
crel.attrMap['on'] = function(element, value) {
|
||||
for (var eventName in value) {
|
||||
element.addEventListener(eventName, value[eventName]);
|
||||
|
|
|
@ -1137,6 +1137,19 @@ class Graph {
|
|||
'value': this.configuration.hasOwnProperty('light0_fade') ? this.configuration.light0_fade: ""
|
||||
})
|
||||
),
|
||||
crel(
|
||||
'label',
|
||||
"Is Sophie: ",
|
||||
crel('input', {
|
||||
'type': 'checkbox',
|
||||
'checked_value': this.configuration.hasOwnProperty('light0_isSophie') ? this.configuration.light0_isSophie: false,
|
||||
'on': {
|
||||
'change': function(e){
|
||||
panopticon.graph.configuration['light0_isSophie'] = e.target.checked
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
crel('h2', 'Light fade setting #1'),
|
||||
crel(
|
||||
'label',
|
||||
|
@ -1169,6 +1182,19 @@ class Graph {
|
|||
'value': this.configuration.hasOwnProperty('light1_fade') ? this.configuration.light1_fade: ""
|
||||
})
|
||||
),
|
||||
crel(
|
||||
'label',
|
||||
"Is Sophie: ",
|
||||
crel('input', {
|
||||
'type': 'checkbox',
|
||||
'checked_value': this.configuration.hasOwnProperty('light1_isSophie') ? this.configuration.light1_isSophie: false,
|
||||
'on': {
|
||||
'change': function(e){
|
||||
panopticon.graph.configuration['light1_isSophie'] = e.target.checked
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
crel('h2', 'Light fade setting #2'),
|
||||
crel(
|
||||
'label',
|
||||
|
@ -1201,6 +1227,19 @@ class Graph {
|
|||
'value': this.configuration.hasOwnProperty('light2_fade') ? this.configuration.light2_fade: ""
|
||||
})
|
||||
),
|
||||
crel(
|
||||
'label',
|
||||
"Is Sophie: ",
|
||||
crel('input', {
|
||||
'type': 'checkbox',
|
||||
'checked_value': this.configuration.hasOwnProperty('light2_isSophie') ? this.configuration.light2_isSophie: false,
|
||||
'on': {
|
||||
'change': function(e){
|
||||
panopticon.graph.configuration['light2_isSophie'] = e.target.checked
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
crel('h2', 'Light fade setting #3'),
|
||||
crel(
|
||||
'label',
|
||||
|
@ -1233,6 +1272,19 @@ class Graph {
|
|||
'value': this.configuration.hasOwnProperty('light3_fade') ? this.configuration.light3_fade: ""
|
||||
})
|
||||
),
|
||||
crel(
|
||||
'label',
|
||||
"Is Sophie: ",
|
||||
crel('input', {
|
||||
'type': 'checkbox',
|
||||
'checked_value': this.configuration.hasOwnProperty('light3_isSophie') ? this.configuration.light3_isSophie: false,
|
||||
'on': {
|
||||
'change': function(e){
|
||||
panopticon.graph.configuration['light3_isSophie'] = e.target.checked
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
crel('h2', 'Light fade setting #4'),
|
||||
crel(
|
||||
'label',
|
||||
|
@ -1264,6 +1316,19 @@ class Graph {
|
|||
},
|
||||
'value': this.configuration.hasOwnProperty('light4_fade') ? this.configuration.light4_fade: ""
|
||||
})
|
||||
),
|
||||
crel(
|
||||
'label',
|
||||
"Is Sophie: ",
|
||||
crel('input', {
|
||||
'type': 'checkbox',
|
||||
'checked_value': this.configuration.hasOwnProperty('light4_isSophie') ? this.configuration.light4_isSophie: false,
|
||||
'on': {
|
||||
'change': function(e){
|
||||
panopticon.graph.configuration['light4_isSophie'] = e.target.checked
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -23,14 +23,15 @@
|
|||
|
||||
</div>
|
||||
<div class='hugvey' v-for="hv in hugveys"
|
||||
:class="[{'hugvey--on': hv.status != 'off'},'hugvey--' + hv.status, 'hugvey--light-' + hv.light_on]"
|
||||
:class="[{'hugvey--on': hv.status != 'off'},'hugvey--' + hv.status]"
|
||||
@click="showHugvey(hv)"
|
||||
>
|
||||
<h1>
|
||||
{{ hv.light_id }} <span v-if="hv.id != hv.light_id">(#{{hv.id}})</span>
|
||||
{{ hv.light_id }} <span class='light_id-hv_id' v-if="hv.id != hv.light_id">(#{{hv.id}})</span>
|
||||
</h1>
|
||||
<div class='status'>{{ hv.status }}</div>
|
||||
<div v-if="hv.status != 'off' && hv.status != 'gone' && hv.status != 'loading'">
|
||||
<div v-if="hv.status != 'off' && hv.status != 'loading'">
|
||||
<div v-if="hv.status != 'gone'">
|
||||
<select v-model="hv.language" @change="change_lang(hv, hv.language)" v-on:click.stop>
|
||||
<option v-for="lang in languages" :title="lang.file"
|
||||
:value="lang.code">
|
||||
|
@ -66,13 +67,11 @@
|
|||
<div class='btn' v-if="hv.status == 'paused'" @click.stop="resume(hv)">Resume</div>
|
||||
|
||||
<div class='btn' v-if="(hv.status == 'available' || hv.status == 'blocked') && hv.has_state" @click.stop="resume(hv)">Resume from save</div>
|
||||
<!-- <div class='light'>
|
||||
{{ hv.light }}
|
||||
</div> -->
|
||||
</div>
|
||||
<div class='light'>
|
||||
Light:
|
||||
<input type="number" step="1" :value="hv.light_id" @change="change_light" :data-hvid="hv.id" v-on:click.stop>
|
||||
<input type="checkbox" v-model="hv.light_on" @change="change_light_status" :data-hvid="hv.id" v-on:click.stop>
|
||||
<div>Intensity: {{hv.light['intensity']}}, Fade #{{hv.light['duration']}} <span v-if="hv.light['isSophie']">(sophie)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -172,6 +172,10 @@ img.icon{
|
|||
left: 5px;
|
||||
top: 5px;
|
||||
font-size:150%;
|
||||
|
||||
.light_id-hv_id{
|
||||
font-size:75%;
|
||||
}
|
||||
}
|
||||
|
||||
.status{
|
||||
|
|
Loading…
Reference in a new issue