Skip to content

Commit

Permalink
fix(add_sqlite3_for_persistent_storage): adding sqlite3 for persisten…
Browse files Browse the repository at this point in the history
…t storage between browsers.

  -- fixes dump download archive to actually download archive and alert
 user to not navigate from page until archive finishes.
  -- adds a view current playlist dialog to assist user of firestorm to
 quickly see whats in the current playlist being sent to PixelBlazes
  -- adds brightness slider to Firestorm UI to control brightness on all
 PixelBlazes in network.
  -- add SQLite to backend, and send messages to backend via websockets
 to assist in user expereince verus using fetch to send requests to
 backend node-server. Node-server on backend will process playlist now.
  -- add Enable/Disable All buttons to quickly enable all patterns in
 Firestorm list so a user doesn't have to click through them one by one
 if they dont want to.
  -- minor styling issues to better use the bootstrap templating.
  -- upgraded webstorm to 8.13.0 and modified controller to look at
 binary messages due to spec changes
  • Loading branch information
caleyg committed Apr 27, 2023
1 parent 483d41d commit 4bbc1da
Show file tree
Hide file tree
Showing 18 changed files with 1,957 additions and 118 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

.idea
94 changes: 94 additions & 0 deletions app/brightness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const _ = require("lodash");
const {updateBrightness, getCurrentBrightness} = require("../db/controllers/brightness");
const {discoverPixelBlazes, sendCommand} = require("./pixelBlazeUtils");

let currentBrightness
let pixelBlazeData = []
let pixelBlazeIds = []
init = async () => {
getCurrentBrightness()
.then((brightness) => {
try {
currentBrightness = brightness[0].value
} catch (err) {
console.warn(`Error: ${err}`)
}
})
pixelBlazeData = discoverPixelBlazes()
pixelBlazeIds = _.map(pixelBlazeData, 'id')
}

initInterval = setInterval(init, 100)

class Brightness {
constructor(utils) {
this.utils = utils ? utils : null
}
adjustBrightness = async (brightness) => {
await new Promise((resolve) => {
const tempBrightness = (brightness) ? brightness : currentBrightness
this.delayedSaveBrightness(resolve, tempBrightness)
})
}
delayedSaveBrightness = _.debounce(async (resolve, brightness) => {
sendCommand(pixelBlazeIds, null, brightness)
await this.storeBrightness(brightness);
currentBrightness = brightness
await this.sendBrightnessMessage(currentBrightness)
}, 1000)
getBrightness = async () =>{
await this.sendBrightnessMessage(currentBrightness)
}
storeBrightness = async (brightness) => {
const body = {
value: brightness
}
await updateBrightness(body)
}
sendBrightnessMessage = async (currentBrightness) => {
// skipping this if utils is not initialized due to no websocket connections
if (this.utils) {
await this.utils.broadcastMessage({currentBrightness: currentBrightness})
}
}
}
// Initializing the brightness loop outside the websocket
// because we might not always have a browser open when
// starting/restarting the node-server... it should send
// commands and operate on the brightness w/o the need of an
// active websocket connection
initThis = async () => {
// halting the brightness message until we get it from the db
while (currentBrightness === undefined) {
await new Promise(resolve => {
setTimeout(resolve, 100)
})
}
let initThe = new Brightness()
await initThe.adjustBrightness(currentBrightness)
}
initThis().then(()=>{})


module.exports.BrightnessWebsocketMessageHandler = function (utils) {
const brightness = new Brightness(utils)
this.utils = utils

this.receiveMessage = async function (data) {
let message
try {
message = JSON.parse(data);
} catch (err) {
this.utils.sendError(err)
return
}
if (message.type === 'ADJUST_BRIGHTNESS') {
// console.log('received adjust brightness message!')
await brightness.adjustBrightness(parseFloat(message.brightness))
}
if (message.type === 'GET_CURRENT_BRIGHTNESS') {
// console.log('received get current brightness message!')
await brightness.getBrightness()
}
}
}
4 changes: 2 additions & 2 deletions app/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ module.exports = class PixelblazeController {
this.reconectTimeout = setTimeout(this.connect, 1000);
}

handleMessage(msg) {
handleMessage(msg, isBinary) {
this.lastSeen = new Date().getTime();
// console.log("data from " + this.props.id + " at " + this.props.address, typeof msg, msg);

let props = this.props;
if (typeof msg === "string") {
if (!isBinary) {
try {
_.assign(this.props, _.pick(JSON.parse(msg), PROPFIELDS));
} catch (err) {
Expand Down
32 changes: 32 additions & 0 deletions app/firestormWebsocket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const WebSocketServer = require('ws').Server
const {PlaylistWebSocketMessageHandler} = require('./playlist')
const {Utils} = require('./utils')
const {BrightnessWebsocketMessageHandler} = require("./brightness");

// start FireStorm WebSocket server
const address = '0.0.0.0';
const port = 1890;
const firestormServer = new WebSocketServer({host: address , port: port});
console.log(`Firestorm server is running on ${address}:${port}`);

firestormServer.on('connection', function (connection) {
const utils = new Utils(connection)
const brightnessWebsocketMessageHandler = new BrightnessWebsocketMessageHandler(utils)
const playlistWebSocketMessageHandler = new PlaylistWebSocketMessageHandler(utils)
if(utils.addFirestormClient(connection)) {
return
}
connection.on('message', async function message(data, isBinary) {
const message = isBinary ? data : data.toString();
// console.log(`incoming msg from: ${utils.getFirestormClientBySocket(connection)}, message: ${message}`)
if (await playlistWebSocketMessageHandler.receiveMessage(message)) {
return
}
if (await brightnessWebsocketMessageHandler.receiveMessage(message)) {
return
}
})
connection.on('close', function() {
console.log('closed connection')
})
})
35 changes: 35 additions & 0 deletions app/pixelBlazeUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const _ = require("lodash");
const {discoveries} = require("./discovery");

module.exports.discoverPixelBlazes = () => {
return _.map(discoveries, function (v, k) {
let res = _.pick(v, ['lastSeen', 'address']);
_.assign(res, v.controller.props);
return res;
})
}

module.exports.sendCommand = (pixelBlazeIds, name, brightness) => {
_.each(pixelBlazeIds, async id => {
id = String(id);
let controller = discoveries[id] && discoveries[id].controller;
if (controller) {
let command = null
if(name !== null && name !== undefined) {
command = {
programName: name
}
}
if(brightness !== null && brightness !== undefined){
command = {
brightness: brightness
}
}
if (command) {
await controller.setCommand(command);
} else {
console.log(`No command sent to Pixelblazes command is ${command}`)
}
}
})
}
181 changes: 181 additions & 0 deletions app/playlist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
const _ = require("lodash");
const {getPlaylistFromDB, addPatternToPlaylist, removeAllPatterns} = require("../db/controllers/playlist");
const {discoverPixelBlazes, sendCommand} = require("./pixelBlazeUtils");

let currentPlaylist = []
let currentRunningPattern = null
let initInterval
let pixelBlazeData = []
let pixelBlazeIds = []
let playlistLoopTimeout
let playlistTimeout

init = async () => {
getPlaylistFromDB()
.then((data) => {
try {
currentPlaylist = [] // resetting current playlist so it doesn't grow to infinity
currentPlaylist.push(...data) // adding new playlist items to list
} catch (err) {
console.warn(`Error: ${err}`)
}
})
.catch('there was an error gathering playlist details')

// gather pixelBlaze data
pixelBlazeData = discoverPixelBlazes()
pixelBlazeIds = _.map(pixelBlazeData, 'id')
}

initInterval = setInterval(init, 100)

class Playlist {
constructor(utils) {
this.utils = utils ? utils : null
}

playlistLoop = async () => {
while(true) {
await new Promise(resolve => {
playlistLoopTimeout = setTimeout(resolve, 100)
});
if(pixelBlazeIds.length) {
await this.iterateOnPlaylist()
}
initInterval = null
playlistLoopTimeout = null
playlistTimeout = null
}
}
iterateOnPlaylist = async () => {
for (let index = 0; index < currentPlaylist.length; index++) {
const pattern = currentPlaylist[index]
await this.delaySendPattern(pattern)
await new Promise(resolve => {
playlistTimeout = setTimeout(resolve, pattern.duration * 1000)
});
}
}
delaySendPattern = async (pattern) => {
await new Promise((resolve) => {
resolve(
this.sendPattern(pattern)
)
})
}
disableAllPatterns = async () => {
await removeAllPatterns()
await this.runPlaylistLoopNow()
}
enableAllPatterns = async (duration) => {
const pixelBlazePatterns = this.gatherPatternData(pixelBlazeData)
const enableAll = new Promise((resolve) => {
_.each(pixelBlazePatterns, pattern => {
pattern['duration'] = duration
let body = {
name: pattern.name,
duration: pattern.duration
}
addPatternToPlaylist(body)
})
resolve();
});
enableAll
.then(() => {
this.runPlaylistLoopNow()
})
}
gatherPatternData = (pixelBlazeData) => {
let groupByPatternName = {};
_.each(pixelBlazeData, d => {
d.name = d.name || "Pixelblaze_" + d.id // set name if missing
_.each(d.programList, p => {
let pb = {
id: d.id,
name: d.name
};
if (groupByPatternName[p.name]) {
groupByPatternName[p.name].push(pb);
} else {
groupByPatternName[p.name] = [pb];
}
})
})
let groups = _.chain(groupByPatternName)
.map((v, k) => ({name: k}))
.sortBy('name')
.value();
return groups
}
getCurrentProgramState = async () => {
let message = {
currentRunningPattern: currentRunningPattern,
currentPlaylist: currentPlaylist
}
await this.sendPlaylistMessage(message)
}
runPlaylistLoopNow = async () => {
clearInterval(initInterval)
clearInterval(playlistTimeout)
clearInterval(playlistLoopTimeout)

await this.playlistLoop()
}
sendPattern = async (pattern) => {
const name = pattern.name
currentRunningPattern = name
sendCommand(pixelBlazeIds, name)
let message = {
currentRunningPattern: name,
currentPlaylist: currentPlaylist
}
await this.sendPlaylistMessage(message)
}
sendPlaylistMessage = async (message) => {
// skipping this if utils is not initialized due to no websocket connections
if(this.utils) {
this.utils.broadcastMessage(message)
}
}

}
// Initializing the playlist loop outside the websocket
// because we might not always have a browser open when
// starting/restarting the node-server... it should send
// commands and operate on the playlist w/o the need of an
// active websocket connection
initThe = new Playlist()
initThe.playlistLoop()
.then(() => {})


module.exports.PlaylistWebSocketMessageHandler = function (utils) {
const playlist = new Playlist(utils)
this.utils = utils

this.receiveMessage = async function (data) {
let message
try {
message = JSON.parse(data);
} catch (err) {
this.utils.sendError(err)
return
}
if (message.type === 'DISABLE_ALL_PATTERNS') {
// console.log('received message to disable all patterns!')
await playlist.disableAllPatterns(message.duration)
}
if (message.type === 'ENABLE_ALL_PATTERNS') {
// console.log('received message to enable all patterns!')
await playlist.enableAllPatterns(message.duration)
}
if (message.type === 'GET_CURRENT_PROGRAM_STATE') {
// console.log('received get current program state message!')
await playlist.getCurrentProgramState()
}
if (message.type === 'LAUNCH_PLAYLIST_NOW') {
// console.log('received launch playlist now message!')
await playlist.runPlaylistLoopNow()
}
}
}
Loading

0 comments on commit 4bbc1da

Please sign in to comment.