isSophie checkbox for light, and lightmap which is stored acros runs

This commit is contained in:
Ruben van de Ven 2019-11-28 16:21:00 +01:00
parent f232d89166
commit e3481a58ad
9 changed files with 189 additions and 60 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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
View file

@ -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]);

View file

@ -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
}
}
})
)
);

View file

@ -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>

View file

@ -172,6 +172,10 @@ img.icon{
left: 5px;
top: 5px;
font-size:150%;
.light_id-hv_id{
font-size:75%;
}
}
.status{