def run(self): atSettings = {} atSettings['fp'] = getAudioBasePath() + self._audioFileName atSettings['vol'] = 100 atSettings['repeat'] = False atSettings['tName'] = "AudioThread_id_" + str(self._id) audioThread = at(**atSettings) #Use Id to mark entry as active Models(Db(dbc.MYSQL_DB).connection()).push( ModelType.FOR_ID_SET_ACTIVE_UPDATE_TS, self._id) #Start audio thread audioThread.start() AudioDbInterface.candidateAudioThreads.append(audioThread) logger("_INFO_", " Starting thread: ", audioThread.getName(), " state=", audioThread.isAlive()) #Wait for completion audioThread.join() #Use Id to mark entry as inactive Models(Db(dbc.MYSQL_DB).connection()).push( ModelType.FOR_ID_UNSET_ACTIVE, self._id) AudioDbInterface.candidateAudioThreads.remove(audioThread) logger("_INFO_", " Thread ending: ", audioThread.getName(), " state=", audioThread.isAlive()) return
def __fetchModel(self): if (self._appSettingsId > 0): self._appSettings = Models(Db(dbc.MYSQL_DB).connection()).fetch( ModelType.APP_SETTINGS_FOR_ID, self._appSettingsId) else: self._appSettings = Models(Db(dbc.MYSQL_DB).connection()).fetch( ModelType.APP_SETTINGS) self._settingsJson = json.loads(self._appSettings[0][dbc.KEY_SETTINGS]) return
def infoPage(): #Grab the id from url parameters id = request.args.get("id") #Fetch model data = Models(mysql.connection).fetch(ModelType.INFO_FOR_ID, id) #If the data returned is not exactly 1, go to home page if(len(data) != 1): return index() ts = str(int(time.time())) jsInclude = '<script src="/static/js/scripts.js?t='+ts+'"></script>' jsInclude += '<script src="/static/js/infoPageScripts.js?t='+ts+'"></script>' jsInclude += '<script src="https://kit.fontawesome.com/7daabcbab0.js" crossorigin="anonymous"></script>' cssInclude = '<link rel="stylesheet" href="static/css/styles.css?t='+ts+'">' templateData = { 'jsInclude' : jsInclude, 'cssInclude' : cssInclude } templateData.update(data[0]) #Pass to template Data return render_template('infoPage.html', **templateData)
def combinatoricData(): ts = str(int(time.time())) response = { "state":"unsuccessful", "ts": ts } location = request.args.get("landscape") r = int(request.args.get("channels")) logger("_INFO_", "location=", location) d = Models(mysql.connection).fetch(ModelType.LOCATION_INFO, location) logger("_INFO_", "d=", d) logger("_INFO_", "d[totalBirdSounds]=", d[0]['totalBirdSounds']) #combinations = nCr n = d[0]['totalBirdSounds'] if(n>0 and r>0): combInt = math.factorial(n)/(math.factorial(n-r) * math.factorial(r)) comb = '{:.0}'.format(math.factorial(n)/(math.factorial(n-r) * math.factorial(r))) logger("_INFO_", "Combinations: ", comb, " Probability=", str(100/combInt)) landscapeSubData = "This landscape has " + str(d[0]["distinctBirds"]) + " unique birds, " + str(d[0]["totalBirdSounds"]) + " songs, calls and other birdsounds, and " + str(d[0]["landscapeSounds"]) + " ambient soundscapes." channelsSubData = "With this setting, the number of birdsounds combinations possible are " + '{:,}'.format(combInt)[:-2] + ". Or in other words, the probability of listening to the same combination is " + f"{Decimal(100/combInt):.4E}" +"%" response["state"] = "successful" response["landscapeSubData"] = landscapeSubData response["channelsSubData"] = channelsSubData return json.dumps(response)
def idData(): ts = str(int(time.time())) t = request.args.get("t") comma_separated_ids = request.args.get("id") #ids = u.comma_separated_params_to_list(request.args.get("id")) #print ("\n\n############# ids: ",ids) #for id in ids: # print ("id: ",id) #print("local ts=",ts," t=",t," diff=",int(ts)-int(t)) jsonObj = { "state":"unknown", "ts":ts } if(int(ts) - int(t) > 2): #Request too old jsonObj["state"] = "request too old" print("Rejecting request because it is too old", jsonObj) return jsonify(jsonObj) entries = Models(mysql.connection).fetch(ModelType.METADATA_FOR_IDS, comma_separated_ids) print("\nConverting entries to JSON:") print(json.dumps(entries, cls=dte)) if(len(entries)==0): jsonObj["state"] = "empty" else: jsonObj["state"] = "successful" jsonObj["data"] = json.dumps(entries, cls=dte) return json.dumps(jsonObj)
def terminate(self): logger("_INFO_", "Terminating continuous playback") #Cancel existing callback if (self._futureTerminationCallback != None): self._futureTerminationCallback.cancel() #Update the database queryFrag = "'$.continuousPlayback.enabled', False" Models(Db(dbc.MYSQL_DB).connection()).push( ModelType.UPDATE_APP_SETTINGS_IN_PLACE, str(queryFrag)) self._rerun = False return
def disableAmbientChannelAndUpdateSettings(ch): if (ch == 'ambientAudioChannel1'): queryFrag = "'$.continuousPlayback.ambience1', 'None'" elif (ch == 'ambientAudioChannel2'): queryFrag = "'$.continuousPlayback.ambience2', 'None'" else: logger("_INFO_", "Unsupported channel: ", ch) return #Update app settings in-place a = Models(Db(dbc.MYSQL_DB).connection()).push( ModelType.UPDATE_APP_SETTINGS_IN_PLACE, str(queryFrag)) logger("_INFO_", "Settings updated inplace in database. Return: ", str(a)) return
def onStage(): ts = str(int(time.time())) t = request.args.get("t") #print("local ts=",ts," t=",t," diff=",int(ts)-int(t)) jsonObj = { "state":"unknown", "ts":ts } if(int(ts) - int(t) > 2): #Request too old jsonObj["state"] = "request too old" print("Rejecting request because it is too old", jsonObj) return jsonify(jsonObj) refetch = True while refetch==True: entries = None entries = Models(mysql.connection).fetch(ModelType.ACTIVE_ENTRIES) entries = [e['id'] for e in entries] logger("_INFO_", "\nfetchActiveEntries: ", json.dumps(entries)) if(len(entries)==0): jsonObj["state"] = "empty" refetch = False else: purged = interface.purgeDeadEntries(60) print("Entries purged: ", purged) if(purged == 0): refetch = False jsonObj["state"] = "successful" jsonObj["data"] = json.dumps(entries) else: refetch = True return json.dumps(jsonObj)
def getCandidateAudioFiles(appSettings, **kwargs): #print("\n--> ",inspect.stack()[0][3], " CALLED BY ",inspect.stack()[1][3]) #2. TODO: Verify that required time has passed since last playback try: requestedChannels = kwargs['requestedChannels'] except: requestedChannels = 1 #Purge dead entries #Fetch activeEntries = Models(Db(dbc.MYSQL_DB).connection()).fetch( ModelType.ACTIVE_ENTRIES) activeEntries = [d[dbc.KEY_ID] for d in activeEntries] #3. Verify that number of active entries don't exceed maximum allowed if (len(activeEntries) > 0): purged = purgeDeadEntries(60) if (purged > 0): logger("_INFO_", purged, "Entries purged") activeEntries = Models(Db(dbc.MYSQL_DB).connection()).fetch( ModelType.ACTIVE_ENTRIES) logger("_INFO_", "Active entries:", activeEntries) logger("_INFO_", "Active/maxAllowed=", len(activeEntries), "/", appSettings.maxNumberOfAllowedSimultaneousChannels()) if (len(activeEntries) >= appSettings.maxNumberOfAllowedSimultaneousChannels()): logger("_INFO_", "Channels saturated. Ignoring trigger. Exiting\n") return [] #4. Compute number of channels to implement if (requestedChannels == 1): numberOfChannels = 1 elif (requestedChannels > 1): emptyChannels = appSettings.maxNumberOfAllowedSimultaneousChannels( ) - len(activeEntries) if (requestedChannels > emptyChannels): numberOfChannels = emptyChannels else: numberOfChannels = requestedChannels if (randomizeNumberOfChannels): numberOfChannels = random.randint(1, requestedChannels) else: logger("_ERROR_", "Unsupported requested number of channels:", str(requestedChannels)) return [] #Scope: # (1) Fetch all sorted by last_updated asc data = Models(Db(dbc.MYSQL_DB).connection()).fetch( ModelType.IDS_NAMES_AUDIOFILE_SORTED_BY_LAST_UPDATED_OLDEST_FIRST) # (2) Select one t random from top 75% of that list #for d in data: # print("New line") # print(d[0]) # print(d[1]) logger("_INFO_", "Total data rows: ", len(data)) #print(type(data)) #for d in data: # print(type(d)) #print(data) #print("\nJSON dumps:",json.dumps(data)) allIds = [d[dbc.KEY_ID] for d in data] logger("_INFO_", allIds) eligibleLength = int(0.75 * len(data)) candidates = [] #Choose first at random candidates.append(data[random.randint(0, eligibleLength - 1)]) logger("_INFO_", "CHOOSING 1st candidate:") logger( "_INFO_", "{:>4.4} {:32.32} {}".format(str(candidates[0][dbc.KEY_ID]), candidates[0][dbc.KEY_NAME], candidates[0][dbc.KEY_AUDIO_FILE])) data.remove(candidates[0]) #Remove last 25% indicesToRemove = len(data) - eligibleLength for i in range(0, indicesToRemove): data.remove(data[len(data) - 1]) logger("_INFO_", "Total number of channels to implement: ", numberOfChannels) if (numberOfChannels > 1): speciesConstrainedSet = [] logger("INFO_", "Limit to same species: ", appSettings.isBirdChoiceLimitedToSameSpecies()) if (appSettings.isBirdChoiceLimitedToSameSpecies()): for d in data: if (d == candidates[0]): print(d, " :Already exists. Skipping") #data.remove(candidates[0]) continue elif (d[dbc.KEY_NAME] == candidates[0][dbc.KEY_NAME]): speciesConstrainedSet.append(d) data = speciesConstrainedSet logger("_INFO_", "Curated candidate data set: size=", len(data)) for element in data: logger( "_INFO_", "{:>4.4} {:32.32} {}".format(str(element[dbc.KEY_ID]), element[dbc.KEY_NAME], element[dbc.KEY_AUDIO_FILE])) if (numberOfChannels >= len(data)): logger( "_INFO_", "Number of channels to implement " + numberOfChannels + " is more or equal to data set at hand ", len(data)) for d in data: candidates.append(d) else: for i in range(0, numberOfChannels - 1): logger("_INFO_", "\nSelecting For channel ", i + 2) randomlyChosenRowIdx = random.randint(0, len(data) - 1) logger( "_INFO_", "Size of data:", len(data), " Chosen idx:", randomlyChosenRowIdx, "id={} {} {}".format( str(data[randomlyChosenRowIdx][dbc.KEY_ID]), data[randomlyChosenRowIdx][dbc.KEY_NAME], data[randomlyChosenRowIdx][dbc.KEY_AUDIO_FILE])) candidates.append(data[randomlyChosenRowIdx]) #remove that row to avoid duplication data.remove(data[randomlyChosenRowIdx]) #logger("_INFO_", "Final candidate list: ") #candidateAudioFiles = [] #for c in candidates: # print (c) # candidateAudioFiles.append(c[1]) return candidates
def processUpstageSoundscape(ch, **kwargs): try: if (kwargs['terminate']): if (kwargs.get("terminate") == True): terminateSoundscapeAudioThread(ch) return except KeyError: logger("_INFO_", ch, " won't be terminated") atSettings = {} for key, value in kwargs.items(): if (key == 'name'): if (value == 'None'): terminateSoundscapeAudioThread(ch) return else: d = Models(Db(dbc.MYSQL_DB).connection()).fetch( ModelType.ID_FILE_FOR_NAME, value)[0] atSettings['fp'] = getAudioBasePath() + d[dbc.KEY_AUDIO_FILE] elif (key == 'endTime'): atSettings['terminateAt'] = value elif (key == 'vol'): atSettings['vol'] = value else: print("Unsupported key/value pair: ", str(key), ":", str(value)) if (globals()[ch] == None or globals()[ch].isAlive() == False): #start new globals()[ch] = at(**atSettings) globals()[ch].start() #Update database if required logger("_INFO_", "audiothread started and waiting for completion") #Wait for completion globals()[ch].join() #Update database: appSettings logger("_INFO_", "Update appSettings here to reflect thread termination") disableAmbientChannelAndUpdateSettings(ch) else: #update existing for k, v in atSettings.items(): if (k == 'fp'): try: globals()[ch].changeFile(v) except: logger("_ERROR_", "Fatal error: Could not change ", k, "on", ch) elif (k == 'vol'): try: globals()[ch].changeVolume(v) except: logger("_ERROR_", "Fatal error: Could not change ", k, "on", ch) elif (k == 'terminateAt'): try: globals()[ch].setFutureTerminationTime(v) except: logger("_ERROR_", "Fatal error: Could not change ", k, "on", ch) else: print("Unsupported AT key/value pair: ", str(k), ":", str(v)) return
def purgeDeadEntries(seconds): return int( Models(Db(dbc.MYSQL_DB).connection()).push( ModelType.UNSET_ACTIVE_FOR_DEAD_ENTRIES, seconds))
def settings(): ts = str(int(time.time())) jsInclude = '<script src="/static/js/scripts.js?t='+ts+'"></script>' jsInclude += '<script src="/static/js/settings.js?t='+ts+'"></script>' jsInclude += '<script src="https://kit.fontawesome.com/7daabcbab0.js" crossorigin="anonymous"></script>' cssInclude = '<link rel="stylesheet" href="static/css/styles.css?t='+ts+'">' cssInclude += '<link rel="stylesheet" href="static/css/settings.css?t='+ts+'">' templateData = { 'jsInclude' : jsInclude, 'cssInclude' : cssInclude } a = AppSettings() d = a.getSettings() #print("Type of d: ", type(d)) #print(settings['settings']['continuousPlayback']['enabled']) #print(d['landscape']) #print(d['continuousPlayback']['enabled']) #print(d['continuousPlayback']['endTime']) #print(d['continuousPlayback']['ambience1']) #print(d['continuousPlayback']['ambience2']) #print(d['motionTriggers']['enabled']) #print(d['motionTriggers']['frequency']) #print(d['symphony']['enabled']) #print(d['symphony']['maximum']) #print(d['symphony']['limitToSameType']) #print(d['silentPeriod']['enabled']) #print(d['silentPeriod']['startTime']) #print(d['silentPeriod']['endTime']) #print(d['volume']) #Fetch options for 'landscape' landscapeLocations = Models(mysql.connection).fetch(ModelType.LIST_OF_LOCATIONS) #Fetch options for 'ambience' soundscapes = Models(mysql.connection).fetch(ModelType.LIST_OF_SOUNDSCAPES_FOR_LOC, d['landscape']) #Insert an empty item as an option for user to select emptyItem = {'name' : 'None'} soundscapes.insert(0, emptyItem) #Prepopulate endTime if continuousPlayback is disabled if(d['continuousPlayback']['enabled'] == False): defaultEndTime = datetime.datetime.now() + datetime.timedelta(minutes = 30) d['continuousPlayback']['endTime'] = defaultEndTime.strftime("%H:%M") settingsTemplateData = { 'last_updated' : a.getLastUpdated(), 'landscape' : d['landscape'], 'landscapeLocations' : landscapeLocations, 'cbEnabled' : d['continuousPlayback']['enabled'], 'birdsEnabled' : d['continuousPlayback']['birdsEnabled'], 'ambienceEnabled' : d['continuousPlayback']['upStageEnabled'], 'ambience1' : d['continuousPlayback']['ambience1'], 'amb1Vol' : d['continuousPlayback']['amb1Vol'], 'ambience2' : d['continuousPlayback']['ambience2'], 'amb2Vol' : d['continuousPlayback']['amb2Vol'], 'cbEndTime' : d['continuousPlayback']['endTime'], 'ambientLocations' : soundscapes, 'mtEnabled' : d['motionTriggers']['enabled'], 'mtPeriod' : d['motionTriggers']['frequency'], 'symphony' : d['symphony']['enabled'], 'symMaxBirds' : d['symphony']['maximum'], 'symLimitToSame' : d['symphony']['limitToSameType'], 'silentPeriod' : d['silentPeriod']['enabled'], 'spStartTime' : d['silentPeriod']['startTime'], 'spEndTime' : d['silentPeriod']['endTime'], #'volume' : d['volume'] 'volume' : av.getCurrentVolume() } templateData.update(settingsTemplateData) return render_template('settings.html', **templateData)
def iterate(): print("\nNew Iteration:", datetime.now()) response = Models().fetch(ModelType.NODE_TO_QUERY) id = response[0][0] nodeIp = response[0][1] nodeId = response[0][2] lastRecord = Models().fetch(ModelType.LAST_NODE_TELEMETRY_RECORD, nodeId) apiIdToFetch = "Identity" if (lastRecord[0][3] == 'System statistics'): apiIdToFetch = "Identity" elif (lastRecord[0][3] == 'Identity'): apiIdToFetch = "System statistics" else: apiIdToFetch = "Identity" api = Models().fetch(ModelType.FETCH_ENDPOINT_FOR_API_ID, apiIdToFetch) api_id = api[0][0] ep = api[0][1] #request url = 'http://' + nodeIp + ep print("-> URL to GET: ", url) jsonResponse = '{}' http_status_code = -1 status_str = "SUCCESS" try: td = requests.get(url, timeout=10) jsonResponse = td.text http_status_code = td.status_code print("-> Response HTTP status code:", http_status_code) if (http_status_code != 200): status_str = "FAIL" print("-> Response: ", jsonResponse) if (nodeId != td.json()['node']): status_str = "Mismatched NodeId" except requests.exceptions.Timeout as e: print("-> Exception: Timeout: ", e) status_str = str(e) except requests.exceptions.RequestsWarning as e: print("-> Exception: RequestsWarning: ", e) status_str = str(e) except requests.exceptions.RetryError as e: print("-> Exception: RetryError: ", e) status_str = str(e) except requests.exceptions.RequestException as e: print("-> Exception: RequestException: ", e) status_str = str(e) #Add telemetry data rowsAdded = Models().push(ModelType.TELEMETRY_DATA, nodeId, api_id, http_status_code, jsonResponse, status_str) print("-> Number of telemetry data-rows added successfully:", rowsAdded) if (rowsAdded != 1): print(" ERROR! Expected 1. Received:", rowsAdded) #Update node_list with ts rowsAdded = Models().push(ModelType.UPDATE_TIMESTAMP_FOR_ID, id) print("-> Timestamp updated for node", nodeId, "| Rows updated:", rowsAdded) if (rowsAdded != 1): print(" ERROR! Expected 1. Received:", rowsAdded) numberOfNodesToQuery = Models().fetch( ModelType.NUMBER_OF_ACTIVE_QUERIES)[0][0] nodeQueryPeriodicity = 120 #seconds - TODO: fetch from database secondsToPause = nodeQueryPeriodicity / numberOfNodesToQuery print("-> Next thread will run in ", secondsToPause, " seconds") nextThread = threading.Timer(secondsToPause, iterate) nextThread.start()
def save(self, jsonStr): Models(Db(dbc.MYSQL_DB).connection()).push(ModelType.APP_SETTINGS, jsonStr) self.refresh() return
def main(): ts = str(int(time.time())) jsInclude = '<script src="/static/js/scripts.js?t='+ts+'"></script>' jsInclude += '<script src="https://kit.fontawesome.com/7daabcbab0.js" crossorigin="anonymous"></script>' cssInclude = '<link rel="stylesheet" href="static/css/styles.css?t='+ts+'">' results = Models(mysql.get_db()).fetch(ModelType.NODE_LIST) print("Type of results: ", type(results)) newResults = () nodeArrayJson = [] for var in results: nodeJson = { "nodeId" : var[3], "name" : var[4], "status" : var[6], "last_updated": var[2], "last_seen": passed_time_string_for_past_dto(var[2]) } r = Models(mysql.get_db()).fetch(ModelType.NODE_TELEMETRY_LATEST_RECORD_FOR_NODE_ID, nodeJson['nodeId'], 'System statistics')[0] nodeJson["apiIdKey"] = r[3] nodeJson["apiIdValue"] = r[5] s = Models(mysql.get_db()).fetch(ModelType.NODE_TELEMETRY_LATEST_RECORD_FOR_NODE_ID, nodeJson['nodeId'], 'Identity') try: identityJson = json.loads(s[0][5]) nodeJson["firmware"] = identityJson['current']['version'] nodeJson["firmware build"] = identityJson['current']['build'] nodeJson["board"] = identityJson['current']['board'] nodeJson["First boot"] = identityJson['current']['fBoot'] except: nodeJson["firmware"] = "Unavailabe" nodeArrayJson.append(nodeJson) element = var + r eList = list(element) #[*element] ctr=0 for i in eList: print(ctr, ":::", i) if(ctr == 12): try: jsonE = json.loads(i) except: jsonE = {} print("Unable to convert to json") try: print("JSON: ", jsonE['ip']) itemIp = jsonE['ip'] except: itemIp = "IPV4 unknown" print("ip not found") try: print("JSON: ", jsonE['session']['uptime']) itemUptime = "Up since "+jsonE['session']['uptime'] except: itemUptime = "Uptime unknown" print("uptime not found") try: print("JSON: ", jsonE['calendarEvents']) itemCe = str(jsonE['calendarEvents']) + " calendar events" except: itemCe = "Calendar events unknown" print("calendarEvents not found") try: print("JSON: ", jsonE['scheduledEvents']) itemSe = str(jsonE['scheduledEvents']) + " scheduled events" except: itemSe = "Scheduled events unknown" print("scheduledEvents not found") try: print("JSON: ", jsonE['temperature']) if(jsonE['temperature'] == -100): itemTe = "No onboard Temp. sensor" else: itemTe = "Onboard temp:" + str(jsonE['temperature']) except: itemTe = "Temp. unknown" print("temp not found") tupToAdd = (itemIp, itemUptime, itemCe, itemSe, itemTe, ) element = element + tupToAdd ctr = ctr+1 print ("\nElement: ", element, "\n") newResults = newResults + (element,) print("\n\n") print("\n\n\nNew Results: ", newResults) print("\n\n********************\nConstructed JSON: ", json.dumps(nodeArrayJson, cls=dte, indent=4, sort_keys=False)) print("\n\nNodeArrayJson size: ", len(nodeArrayJson)) for nr in newResults: ctx = 0 for i in nr: print(ctx," >>> ", i) ctx=ctx+1 templateData = { 'jsInclude' : jsInclude, 'cssInclude' : cssInclude } return render_template('index.html', **templateData, data=newResults)