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.hugveyWarnings = {}
self.lightMapFile = os.path.join('state','lightMap.json')
self.lightMap = {}
eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue)) eventLogger.addHandler(logging.handlers.QueueHandler(self.logQueue))
def loadConfig(self, filename): def loadConfig(self, filename):
@ -92,10 +95,10 @@ class CentralCommand(object):
self.config[arg] = getattr(self.args,arg) self.config[arg] = getattr(self.args,arg)
self.hugvey_ids = [i + 1 for i in range(self.config['hugveys'])] self.hugvey_ids = [i + 1 for i in range(self.config['hugveys'])]
self.loadLightMap()
self.loadLanguages() self.loadLanguages()
voice_dir = os.path.join(self.config['web']['files_dir'], 'voices') voice_dir = os.path.join(self.config['web']['files_dir'], 'voices')
self.voiceStorage = VoiceStorage(voice_dir, self.languageConfig) self.voiceStorage = VoiceStorage(voice_dir, self.languageConfig)
varDb = os.path.join( varDb = os.path.join(
@ -134,6 +137,25 @@ class CentralCommand(object):
with open(lang_filename, 'r') as fp: with open(lang_filename, 'r') as fp:
self.languages[lang['code']] = json.load(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): def getHugveyStatus(self, hv_id, isSelected = False):
status = {'id': hv_id} status = {'id': hv_id}
@ -148,7 +170,7 @@ class CentralCommand(object):
#: :type hv: HugveyState #: :type hv: HugveyState
status['status'] = hv.getStatus() status['status'] = hv.getStatus()
status['light_on'] = bool(hv.lightStatus) status['light'] = hv.lightTransitionStatus
status['language'] = hv.language_code status['language'] = hv.language_code
status['light_id'] = hv.lightId status['light_id'] = hv.lightId
status['available'] = hv.isAvailable() status['available'] = hv.isAvailable()
@ -200,6 +222,16 @@ class CentralCommand(object):
return status 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 = []): def getStatusSummary(self, selected_ids = []):
status = { status = {
'uptime': "-" if not self.start_time else (time.time() - self.start_time), 'uptime': "-" if not self.start_time else (time.time() - self.start_time),
@ -373,6 +405,7 @@ class CentralCommand(object):
while self.isRunning.is_set(): while self.isRunning.is_set():
logger.info(f'Instantiate hugvey #{hugvey_id}') logger.info(f'Instantiate hugvey #{hugvey_id}')
h = HugveyState(hugvey_id, self) h = HugveyState(hugvey_id, self)
h.setLightId(self.lightMap[hugvey_id])
# h.config(msg['host'], msg['ip']) # h.config(msg['host'], msg['ip'])
self.hugveys[hugvey_id] = h self.hugveys[hugvey_id] = h
r = h.run() r = h.run()
@ -553,6 +586,7 @@ class HugveyState(object):
self.setStatus(self.STATE_GONE) self.setStatus(self.STATE_GONE)
self.requireRestartAfterStop = None 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): def __del__(self):
self.logger.warn("Destroying hugvey object") self.logger.warn("Destroying hugvey object")
@ -793,14 +827,16 @@ class HugveyState(object):
self.command.commandLight('/hugvey', [self.lightId, self.lightStatus]) self.command.commandLight('/hugvey', [self.lightId, self.lightStatus])
def transitionLight(self, intensity, duration): def transitionLight(self, intensity, duration, isSophie = False):
""" """
Intensity: 0-255 Intensity: 0-255
duration: an integer between 0-92 indicating the lanbox fade times 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.lightTransitionStatus = {'intensity': intensity, 'duration': duration, 'isSophie': isSophie}
self.logger.debug(f"Send /hugvey_fade {self.lightIntensity} {duration}") self.logger.debug(f"Send /hugvey_fade {intensity} {duration} {1 if isSophie else 0}")
self.command.commandLight('/hugvey_fade', [self.lightId, intensity, int(duration)]) self.command.commandLight('/hugvey_fade', [self.lightId, intensity, int(duration), 1 if isSophie else 0])
def setLightId(self, id): def setLightId(self, id):
""" """
@ -890,8 +926,9 @@ class HugveyState(object):
if self.command.config['story']['loop']: if self.command.config['story']['loop']:
if not self.blockRestart: if not self.blockRestart:
self.logger.info("Loop story") if self.notShuttingDown:
self.restart() self.logger.info("Loop story")
self.restart()
else: else:
self.logger.info("Don't loop on manual finish") self.logger.info("Don't loop on manual finish")
@ -914,7 +951,7 @@ class HugveyState(object):
self.streamer.addConsumer(self.player) self.streamer.addConsumer(self.player)
if self.command.config['voice']['record_dir']: 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.recorder = Recorder( self.id,
self.command.config['voice']['src_rate'], self.command.config['voice']['record_dir'], 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) 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) central_command.setLoopTime(seconds)
def msgChangeLightId(self, hv_id, lightId): def msgChangeLightId(self, hv_id, lightId):
if central_command.hugveys[hv_id].eventQueue: central_command.setLightForHugvey(hv_id, lightId)
central_command.hugveys[hv_id].eventQueue.put_nowait({'event': 'change_light', 'light_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): def msgChangeLightStatus(self, hv_id, status):
if central_command.hugveys[hv_id].eventQueue: if central_command.hugveys[hv_id].eventQueue:

View file

@ -1166,14 +1166,19 @@ class Configuration(object):
time_factor = 1 time_factor = 1
light0_intensity = 0 light0_intensity = 0
light0_fade = 30. # fade duration in seconds light0_fade = 30. # fade duration in seconds
light0_isSophie = False
light1_intensity = 150 light1_intensity = 150
light1_fade = 10. light1_fade = 10.
light1_isSophie = False
light2_intensity = 75 light2_intensity = 75
light2_fade = 10. light2_fade = 10.
light2_isSophie = False
light3_intensity = 150 light3_intensity = 150
light3_fade = 10. light3_fade = 10.
light3_isSophie = False
light4_intensity = 150 light4_intensity = 150
light4_fade = 10. light4_fade = 10.
light4_isSophie = False
@classmethod @classmethod
def initFromJson(configClass, data, story): def initFromJson(configClass, data, story):
@ -1187,7 +1192,8 @@ class Configuration(object):
for i in range(5): for i in range(5):
l.append({ l.append({
'intensity': int(c[f"light{i}_intensity"]), '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 return l
@ -1783,7 +1789,7 @@ class Story(object):
# Test stability of Central Command with deliberate crash # Test stability of Central Command with deliberate crash
# if self.timer.getElapsed() > 10: # if self.timer.getElapsed() > 10:
# raise Exception("Test exception") # 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.storeState()
self.timer.setMark('state_save') self.timer.setMark('state_save')
@ -1891,7 +1897,7 @@ class Story(object):
preset = self.configuration.getLightPresets()[presetNr] preset = self.configuration.getLightPresets()[presetNr]
self.currentLightPresetNr = presetNr self.currentLightPresetNr = presetNr
self.hugvey.transitionLight(preset['intensity'], preset['fade']) self.hugvey.transitionLight(preset['intensity'], preset['fade'], preset['isSophie'])
def getCurrentDirections(self): def getCurrentDirections(self):
if self.currentMessage.id not in self.directionsPerMsg: if self.currentMessage.id not in self.directionsPerMsg:
@ -2082,6 +2088,7 @@ class Story(object):
@classmethod @classmethod
def hugveyHasSavedState(cls, hv_id): 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)) return os.path.exists(cls.getStateFilename(hv_id))
@classmethod @classmethod
@ -2095,6 +2102,7 @@ class Story(object):
story.logger = mainLogger.getChild(f"{story.hugvey.id}").getChild("story") 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. # TODO: this is not really working because it is overridden by the set-status later.
# story.hugvey.setLightStatus(story.lightStateSave) # story.hugvey.setLightStatus(story.lightStateSave)
story.logger.critical(f"Light preset {story.currentLightPresetNr}")
if story.currentLightPresetNr is not None: if story.currentLightPresetNr is not None:
story.fadeLightPreset(story.currentLightPresetNr) story.fadeLightPreset(story.currentLightPresetNr)
return story return story

View file

@ -60,6 +60,12 @@ if __name__ == '__main__':
rootLogger.addHandler(socket_handler) rootLogger.addHandler(socket_handler)
logger.info("Start server") logger.info("Start server")
command = CentralCommand(args=args, debug_mode=args.verbose > 0)
command.loadConfig(args.config) try:
command.start() 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; font-weight: normal;
position: absolute; position: absolute;
left: 5px; left: 5px;
top: 5px; } top: 5px;
font-size: 150%; }
#status .hugvey h1 .light_id-hv_id {
font-size: 75%; }
#status .hugvey .status { #status .hugvey .status {
font-style: italic; font-style: italic;
color: gray; 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}); !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) { crel.attrMap['on'] = function(element, value) {
for (var eventName in value) { for (var eventName in value) {
element.addEventListener(eventName, value[eventName]); element.addEventListener(eventName, value[eventName]);

View file

@ -1137,6 +1137,19 @@ class Graph {
'value': this.configuration.hasOwnProperty('light0_fade') ? this.configuration.light0_fade: "" '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('h2', 'Light fade setting #1'),
crel( crel(
'label', 'label',
@ -1169,6 +1182,19 @@ class Graph {
'value': this.configuration.hasOwnProperty('light1_fade') ? this.configuration.light1_fade: "" '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('h2', 'Light fade setting #2'),
crel( crel(
'label', 'label',
@ -1201,6 +1227,19 @@ class Graph {
'value': this.configuration.hasOwnProperty('light2_fade') ? this.configuration.light2_fade: "" '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('h2', 'Light fade setting #3'),
crel( crel(
'label', 'label',
@ -1233,6 +1272,19 @@ class Graph {
'value': this.configuration.hasOwnProperty('light3_fade') ? this.configuration.light3_fade: "" '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('h2', 'Light fade setting #4'),
crel( crel(
'label', 'label',
@ -1264,7 +1316,20 @@ class Graph {
}, },
'value': this.configuration.hasOwnProperty('light4_fade') ? this.configuration.light4_fade: "" '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
}
}
})
)
); );
document.getElementById("interface").appendChild(configEl); document.getElementById("interface").appendChild(configEl);

View file

@ -23,56 +23,55 @@
</div> </div>
<div class='hugvey' v-for="hv in hugveys" <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)" @click="showHugvey(hv)"
> >
<h1> <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> </h1>
<div class='status'>{{ hv.status }}</div> <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'">
<select v-model="hv.language" @change="change_lang(hv, hv.language)" v-on:click.stop> <div v-if="hv.status != 'gone'">
<option v-for="lang in languages" :title="lang.file" <select v-model="hv.language" @change="change_lang(hv, hv.language)" v-on:click.stop>
:value="lang.code"> <option v-for="lang in languages" :title="lang.file"
<span :class="['flag-icon', lang.code]"></span> :value="lang.code">
{{lang.code}} <span :class="['flag-icon', lang.code]"></span>
</option> {{lang.code}}
</select> </option>
{{ hv.language }} </select>
{{ hv.language }}
<!-- <div v-if="hv.awaiting != false"><img class='icon' :src="'/images/icon-finished.svg'" title="Finished"> {{timer(hv, <!-- <div v-if="hv.awaiting != false"><img class='icon' :src="'/images/icon-finished.svg'" title="Finished"> {{timer(hv,
'finished')}}</div> --> 'finished')}}</div> -->
<div class='stats'> <div class='stats'>
<div class='count' v-for="c, key in hv.counts"> <div class='count' v-for="c, key in hv.counts">
<img class='icon' :src="'/images/icon-' + key + '.svg'" :title="key"> <img class='icon' :src="'/images/icon-' + key + '.svg'" :title="key">
{{c}} {{c}}
</div> </div>
<div class='position'> <div class='position'>
<img class='icon' src="/images/icon-position.svg" title="position"> <img class='icon' src="/images/icon-position.svg" title="position">
{{ hv.msg }} {{ hv.msg }}
</div> </div>
<div class='timer' v-if="hv.duration"> <div class='timer' v-if="hv.duration">
<img class='icon' src="/images/icon-clock.svg" title="timer"> <img class='icon' src="/images/icon-clock.svg" title="timer">
{{ timer(hv, 'duration') }} {{ timer(hv, 'duration') }}
</div>
</div> </div>
<div class='btn' v-if="hv.status == 'blocked'" @click.stop="unblock(hv)">Unblock</div>
<div class='btn' v-if="hv.status == 'available'" @click.stop="block(hv)">Block</div>
<div class='btn' v-if="hv.status == 'available' || hv.status =='blocked'" @click.stop="restart(hv)">Start</div>
<div class='btn' v-if="hv.status == 'running'" @click.stop="finish(hv)">Finish</div> <!-- to available state -->
<div class='btn' v-if="hv.status == 'running'" @click.stop="pause(hv)">Pause</div>
<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> </div>
<div class='btn' v-if="hv.status == 'blocked'" @click.stop="unblock(hv)">Unblock</div>
<div class='btn' v-if="hv.status == 'available'" @click.stop="block(hv)">Block</div>
<div class='btn' v-if="hv.status == 'available' || hv.status =='blocked'" @click.stop="restart(hv)">Start</div>
<div class='btn' v-if="hv.status == 'running'" @click.stop="finish(hv)">Finish</div> <!-- to available state -->
<div class='btn' v-if="hv.status == 'running'" @click.stop="pause(hv)">Pause</div>
<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 class='light'> <div class='light'>
Light: Light:
<input type="number" step="1" :value="hv.light_id" @change="change_light" :data-hvid="hv.id" v-on:click.stop> <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> </div>
</div> </div>

View file

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