diff --git a/.gitignore b/.gitignore
index ca51fc1..f16f23d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
src/credentials.h
*.sublime-workspace
web/node_modules/
+web/update/
diff --git a/build.bat b/build.bat
deleted file mode 100644
index e63c79f..0000000
--- a/build.bat
+++ /dev/null
@@ -1 +0,0 @@
-@platformio run
\ No newline at end of file
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 0000000..e1c1404
--- /dev/null
+++ b/build.ps1
@@ -0,0 +1,3 @@
+& .\updateversion.ps1
+& platformio run
+Copy-Item .\.pioenvs\esp12e\firmware.elf .\web\update\
\ No newline at end of file
diff --git a/docs/protocol.md b/docs/protocol.md
index 436ac43..ffd7339 100644
--- a/docs/protocol.md
+++ b/docs/protocol.md
@@ -23,6 +23,7 @@ The first byte of a request is the command. Further data depends on the command.
| 0x04 | SetMode |
| 0x05 | GetRange |
| 0x06 | SetRange |
+| 0xFF | UpdateFirmware |
Response
@@ -101,6 +102,31 @@ Output parameters:
_Same as input parameters_
+### UpdateFirmware
+Updates the firmware over WiFi. This functionality may be limited in the configuration which is hardcoded into the firmware. There are three modes of operation:
+
+1. Disabled, firmware can not be updated over WiFi
+2. Enabled with a hardcoded URL. You may request an update check but any source provided will be ignored.
+3. Enabled with the URL as input parameters.
+
+You can only request an update once every 5 seconds (by default), otherwise an error will be returned.
+
+Please note that the entire packet must not exceed 254 bytes or the remainder will be discarded.
+
+
+For further information on implementing an update server, see [Arduino ESP8266's HTTPUpdate reference](https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/readme.md#advanced-updater-1).
+
+
+Input parameters:
+_If enabled_
+*port* (word): the port on which the HTTP update server runs
+*host* (null-terminated string): the host name or IP address on which the HTTP update server runs
+*path* (null-terminated string): the path to
+
+Output parameters:
+1 if succesful, 0 if no updates are available.
+
+
Modes
=====
diff --git a/src/config.h b/src/config.h
index ed66d1c..5f4266b 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,9 +1,14 @@
#ifndef __Config
#define __Config
+#include
#include
#include "credentials.h"
+
+#define SerialDebug
+
+
// The name of this device on the network
static const char* WiFiHostname = "Stairs";
@@ -25,4 +30,27 @@ static const uint8_t PinSCL = 12;
static const uint8_t PWMDriverAddress = 0x40;
static const uint16_t PWMDriverPWMFrequency = 1600;
+
+// Determines if OTA firmware updates are enabled
+// 0 - Disabled
+// 1 - Enabled (fixed URL)
+// 2 - Enabled (use URL in command)
+static const uint8_t OTAUpdateEnabled = 2;
+
+static const char* OTAUpdateFixedHost = "";
+static const uint16_t OTAUpdateFixedPort = 80;
+static const char* OTAUpdateFixedPath = "/";
+
+// The minimum amount of time (in milliseconds) between update requests
+static const uint32_t OTAUpdateThrottle = 5000;
+
+
+#ifdef SerialDebug
+#define _d(msg) Serial.print(msg)
+#define _dln(msg) Serial.println(msg)
+#else
+#define _d(msg) do { } while (0)
+#define _dln(msg) do { } while (0)
+#endif
+
#endif
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 40b52a3..b99e866 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include "config.h"
#include "protocol.h"
@@ -16,6 +17,7 @@
#include "modes\slide.h"
#include "modes\static.h"
#include "stairs.h"
+#include "version.h"
PCA9685* pwmDriver;
@@ -33,20 +35,35 @@ void setCurrentMode(IMode *mode, uint8_t identifier);
void handleCurrentMode();
+
+
void setup()
{
+ #ifdef SerialDebug
+ Serial.begin(115200);
+ delay(5000);
+ #endif
+
+
+ _dln("Initializing PCA9685");
+ _d("Version: ");
+ _dln(FirmwareVersion);
+
pwmDriver = new PCA9685();
pwmDriver->setAddress(PWMDriverAddress, PinSDA, PinSCL);
pwmDriver->setPWMFrequency(PWMDriverPWMFrequency);
+ _dln("Initializing Stairs");
+
stairs = new Stairs();
stairs->init(pwmDriver);
+ _dln("Initializing WiFi");
WiFi.hostname(WiFiHostname);
WiFi.begin(WiFiSSID, WiFiPassword);
- // Run a little startup test sequence
+ _dln("Starting initialization sequence");
stairs->setAll(IStairs::Off);
stairs->set(0, IStairs::On);
delay(300);
@@ -61,6 +78,8 @@ void setup()
stairs->set(StepCount - 1, IStairs::Off);
+ _dln("Waiting for WiFi");
+
// Pulsate the bottom step while WiFi is connecting
uint16_t brightness = 0;
uint16_t speed = 16;
@@ -78,6 +97,11 @@ void setup()
setCurrentMode(new StaticMode(), Mode::Static);
+ _d("IP address: ");
+ _dln(WiFi.localIP());
+
+ _dln("Starting UDP server");
+
// Start the UDP server
udpServer.begin(UDPPort);
}
@@ -88,11 +112,9 @@ uint32_t currentTime;
// Note: the packet size must at least be able to accomodate the
// command with the largest parameter list, there is no overflow
// checking in the mode classes!
-//
-// At the time of writing, Get/SetRange is the largest:
-// Set Range (1) + UseScaling (1) + StepCount * 2x Word (4)
-// = 66 for the maximum of 16 steps
-uint8_t packet[100];
+const uint8_t maxPacketSize = 255;
+
+uint8_t packet[maxPacketSize];
uint8_t* packetRef;
void loop()
@@ -109,8 +131,10 @@ void checkRequest()
int packetSize = udpServer.parsePacket();
if (packetSize)
{
+ _dln("Handling incoming packet");
+
memset(packet, 0, sizeof(packet));
- int length = udpServer.read(packet, 50);
+ int length = udpServer.read(packet, maxPacketSize - 1);
if (length && packet[0])
{
packetRef = packet;
@@ -120,8 +144,137 @@ void checkRequest()
}
+void handlePing(uint8_t* packet)
+{
+ _dln("Handling Ping");
+
+ udpServer.write(Command::Ping);
+ udpServer.write(StepCount);
+}
+
+
+void handleGetMode(uint8_t* packet)
+{
+ _dln("Handling GetMode");
+
+ udpServer.write(Command::GetMode);
+ udpServer.write(currentModeIdentifier);
+ currentMode->write(&udpServer);
+}
+
+
+void handleSetMode(uint8_t* packet)
+{
+ _dln("Handling SetMode");
+ uint8_t newIdentifier = *packet;
+ packet++;
+
+ IMode* newMode = createMode(newIdentifier);
+
+ if (newMode != NULL)
+ {
+ newMode->read(packet);
+
+ udpServer.write(Command::SetMode);
+ udpServer.write(newIdentifier);
+ newMode->write(&udpServer);
+
+ _dln("Updating current mode");
+ setCurrentMode(newMode, newIdentifier);
+ }
+ else
+ {
+ udpServer.write(Command::Error);
+ udpServer.write(Command::SetMode);
+ udpServer.write(newIdentifier);
+ }
+}
+
+
+uint32_t lastUpdateCheck = 0;
+
+void handleUpdateFirmware(uint8_t* packet)
+{
+ _dln("Handling UpdateFirmware");
+
+ HTTPUpdateResult result;
+
+ if (currentTime - lastUpdateCheck <= OTAUpdateThrottle)
+ {
+ udpServer.write(Command::Error);
+ udpServer.write(Command::UpdateFirmware);
+ udpServer.write((uint8_t)2);
+ return;
+ }
+
+ lastUpdateCheck = currentTime;
+
+ switch (OTAUpdateEnabled)
+ {
+ case 1:
+ result = ESPhttpUpdate.update(OTAUpdateFixedHost, OTAUpdateFixedPort, OTAUpdateFixedPath, FirmwareVersion);
+ break;
+
+ case 2:
+ {
+ uint16_t port;
+ memcpy(&port, packet, sizeof(port));
+ packet += sizeof(port);
+
+ _d("Port: ");
+ _dln(port);
+
+ char host[255];
+ char path[255];
+
+ strcpy(host, (char*)packet);
+ packet += strlen(host) + 1;
+
+ strcpy(path, (char*)packet);
+
+ _d("Host: ");
+ _dln(host);
+ _d("Path: ");
+ _dln(path);
+
+ result = ESPhttpUpdate.update(host, port, path, FirmwareVersion);
+ break;
+ }
+
+ default:
+ udpServer.write(Command::Error);
+ udpServer.write(Command::UpdateFirmware);
+ udpServer.write((uint8_t)0);
+ return;
+ }
+
+ switch (result)
+ {
+ case HTTP_UPDATE_NO_UPDATES:
+ udpServer.write(Command::UpdateFirmware);
+ udpServer.write((uint8_t)0);
+ break;
+
+ case HTTP_UPDATE_OK:
+ udpServer.write(Command::UpdateFirmware);
+ udpServer.write((uint8_t)1);
+ break;
+
+ default:
+ udpServer.write(Command::Error);
+ udpServer.write(Command::UpdateFirmware);
+ udpServer.write((uint8_t)1);
+ break;
+ }
+}
+
+
void handleRequest(uint8_t* packet)
{
+ _d("Handling request: ");
+ _dln(*packet);
+
+
// Every request will result in a reply, either containing the
// requested data or a copy of the input parameters for verification.
//
@@ -130,50 +283,19 @@ void handleRequest(uint8_t* packet)
udpServer.beginPacket(udpServer.remoteIP(), udpServer.remotePort());
udpServer.write(Command::Reply);
- switch (*packet)
+ uint8_t command = *packet;
+ packet++;
+
+ switch (command)
{
- case Command::Ping:
- udpServer.write(Command::Ping);
- udpServer.write(StepCount);
- break;
-
- case Command::GetMode:
- udpServer.write(Command::GetMode);
- udpServer.write(currentModeIdentifier);
- currentMode->write(&udpServer);
- break;
-
- case Command::SetMode:
- {
- packet++;
- uint8_t newIdentifier = *packet;
- packet++;
-
- IMode* newMode = createMode(newIdentifier);
-
- if (newMode != NULL)
- {
- newMode->read(packet);
-
- udpServer.write(Command::SetMode);
- udpServer.write(newIdentifier);
- newMode->write(&udpServer);
-
- setCurrentMode(newMode, newIdentifier);
- }
- else
- {
- udpServer.write(Command::Error);
- udpServer.write(Command::SetMode);
- udpServer.write(newIdentifier);
- }
-
- break;
- }
+ case Command::Ping: handlePing(packet); break;
+ case Command::GetMode: handleGetMode(packet); break;
+ case Command::SetMode: handleSetMode(packet); break;
+ case Command::UpdateFirmware: handleUpdateFirmware(packet); break;
default:
udpServer.write(Command::Error);
- udpServer.write(*packet);
+ udpServer.write(command);
break;
}
diff --git a/src/modes/base.h b/src/modes/base.h
index f25e0ed..255756e 100644
--- a/src/modes/base.h
+++ b/src/modes/base.h
@@ -3,6 +3,7 @@
#include
#include
+#include "../config.h"
#include "../mode.h"
@@ -15,12 +16,16 @@ class BaseMode : public IMode
public:
virtual void read(uint8_t* data)
{
- this->parameters = *reinterpret_cast(data);
+ _d("Reading parameters, size ");
+ _dln(sizeof(T));
+ memcpy(&this->parameters, data, sizeof(T));
}
virtual void write(Stream* stream)
{
- stream->write(reinterpret_cast(&this->parameters), sizeof(T));
+ _d("Writing parameters, size ");
+ _dln(sizeof(T));
+ stream->write((uint8_t*)&this->parameters, sizeof(T));
}
};
diff --git a/src/modes/custom.cpp b/src/modes/custom.cpp
index 2430e33..49119e2 100644
--- a/src/modes/custom.cpp
+++ b/src/modes/custom.cpp
@@ -13,7 +13,7 @@ void CustomMode::read(uint8_t* data)
void CustomMode::write(Stream* stream)
{
- stream->write(reinterpret_cast(&this->values), sizeof(this->values));
+ stream->write((uint8_t*)&this->values, sizeof(this->values));
}
diff --git a/src/modes/static.cpp b/src/modes/static.cpp
index 7b5fc88..26144bf 100644
--- a/src/modes/static.cpp
+++ b/src/modes/static.cpp
@@ -2,10 +2,48 @@
void StaticMode::init(IStairs* stairs, uint32_t currentTime)
{
- stairs->setAll(this->parameters.brightness);
+ _dln("Initializing static mode:");
+ _d("currentBrightness: "); _dln(this->currentBrightness);
+ _d("brightness: "); _dln(this->parameters.brightness);
+ _d("easeTime: "); _dln(this->parameters.easeTime);
+
+ if (this->parameters.easeTime > 0 && this->parameters.brightness != this->currentBrightness)
+ {
+ _dln("Easing...");
+
+ this->easeStartTime = currentTime;
+ this->easeStartBrightness = currentBrightness;
+
+ if (this->parameters.brightness > this->currentBrightness)
+ this->easeState = Up;
+ else
+ this->easeState = Down;
+ }
+ else
+ {
+ _dln("Updating immediately...");
+ this->easeState = None;
+
+ stairs->setAll(this->parameters.brightness);
+ this->currentBrightness = this->parameters.brightness;
+ }
}
void StaticMode::tick(IStairs* stairs, uint32_t currentTime)
{
+ if (this->easeState == None)
+ return;
+
+ this->currentBrightness = (currentTime - this->easeStartTime) * ((this->parameters.brightness - this->easeStartBrightness) / this->parameters.easeTime);
+
+
+ if ((this->easeState == Up && this->currentBrightness >= this->parameters.brightness) ||
+ (this->easeState == Down && this->currentBrightness <= this->parameters.brightness))
+ {
+ this->currentBrightness = this->parameters.brightness;
+ this->easeState = None;
+ }
+
+ stairs->setAll(this->currentBrightness);
}
\ No newline at end of file
diff --git a/src/modes/static.h b/src/modes/static.h
index c06edc3..8db5895 100644
--- a/src/modes/static.h
+++ b/src/modes/static.h
@@ -8,16 +8,34 @@
struct StaticModeParameters
{
- uint16_t brightness = 0;
+ uint16_t brightness;
+ uint32_t easeTime;
+};
+
+enum EaseState
+{
+ None,
+ Up,
+ Down
};
class StaticMode : public BaseMode
{
+ private:
+ uint16_t currentBrightness;
+ uint32_t easeStartTime;
+ uint16_t easeStartBrightness;
+ EaseState easeState;
+
public:
StaticMode()
{
parameters.brightness = 0;
+ parameters.easeTime = 0;
+
+ easeState = None;
+ currentBrightness = 0;
}
void init(IStairs* stairs, uint32_t currentTime);
diff --git a/src/protocol.h b/src/protocol.h
index 789893e..d64defd 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -12,8 +12,12 @@ class Command
static const uint8_t Reply = 0x02;
static const uint8_t GetMode = 0x03;
static const uint8_t SetMode = 0x04;
+ /*
static const uint8_t GetRange = 0x05;
static const uint8_t SetRange = 0x06;
+ */
+
+ static const uint8_t UpdateFirmware = 0xFF;
};
diff --git a/src/stairs.cpp b/src/stairs.cpp
index 6d95f8b..0594b45 100644
--- a/src/stairs.cpp
+++ b/src/stairs.cpp
@@ -17,8 +17,12 @@ struct Header
void Stairs::init(PCA9685* pwmDriver)
{
+ this->useScaling = false;
+ memset(this->ranges, 0, sizeof(this->ranges));
+
this->pwmDriver = pwmDriver;
+ _dln("Loading range configuration");
SPIFFS.begin();
this->readRange();
}
@@ -53,7 +57,7 @@ uint16_t Stairs::getPWMValue(uint8_t step, uint16_t brightness)
if (step < 0 || step >= StepCount)
return brightness;
- Range* range = this->ranges[step];
+ Range* range = &this->ranges[step];
if (this->useScaling)
{
@@ -73,7 +77,7 @@ uint16_t Stairs::getPWMValue(uint8_t step, uint16_t brightness)
void Stairs::getRange(Stream* stream)
{
stream->write(this->useScaling ? 1 : 0);
- stream->write(reinterpret_cast(&this->ranges), sizeof(this->ranges));
+ stream->write((uint8_t*)&this->ranges, sizeof(this->ranges));
}
@@ -96,16 +100,17 @@ void Stairs::readRange()
return;
Header header;
- f.readBytes(reinterpret_cast(&header), sizeof(Header));
+ f.readBytes((char*)&header, sizeof(Header));
if (header.version != 1)
return;
this->useScaling = (header.useScaling == 1);
-
- memset(this->ranges, 0, sizeof(this->ranges));
- f.readBytes(reinterpret_cast(&this->ranges), header.rangeCount * sizeof(Range));
+ f.readBytes((char*)&this->ranges, header.rangeCount * sizeof(Range));
f.close();
+
+ _d("- useScaling: ");
+ _dln(this->useScaling);
}
@@ -120,7 +125,7 @@ void Stairs::writeRange()
header.useScaling = this->useScaling;
header.rangeCount = StepCount;
- f.write(reinterpret_cast(&header), sizeof(Header));
- f.write(reinterpret_cast(&this->ranges), sizeof(this->ranges));
+ f.write((uint8_t*)&header, sizeof(Header));
+ f.write((uint8_t*)&this->ranges, sizeof(this->ranges));
f.close();
}
\ No newline at end of file
diff --git a/src/stairs.h b/src/stairs.h
index 8b543e8..e98a171 100644
--- a/src/stairs.h
+++ b/src/stairs.h
@@ -19,7 +19,7 @@ class Stairs : public IStairs
PCA9685* pwmDriver;
bool useScaling;
- Range* ranges[StepCount];
+ Range ranges[StepCount];
protected:
void readRange();
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..e73acae
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,6 @@
+#ifndef __Version
+#define __Version
+
+static const char* FirmwareVersion = "0.1.0+8";
+
+#endif
diff --git a/updateversion.ps1 b/updateversion.ps1
new file mode 100644
index 0000000..b183a54
--- /dev/null
+++ b/updateversion.ps1
@@ -0,0 +1,23 @@
+$output = & GitVersion /output json /nofetch
+if ($LASTEXITCODE -ne 0) {
+ Write-Verbose "$output"
+ throw "GitVersion failed with exit code: $LASTEXITCODE"
+}
+
+$version = $output | ConvertFrom-Json
+
+@"
+#ifndef __Version
+#define __Version
+
+static const char* FirmwareVersion = "{0}";
+
+#endif
+"@ -f $version.FullSemVer | Out-File -Encoding UTF8 .\src\version.h
+
+@"
+module.exports =
+{{
+ Version: "{0}"
+}};
+"@ -f $version.FullSemVer | Out-File -Encoding UTF8 .\web\version.js
diff --git a/upload.bat b/upload.bat
deleted file mode 100644
index 73e7389..0000000
--- a/upload.bat
+++ /dev/null
@@ -1 +0,0 @@
-@platformio run --target upload
\ No newline at end of file
diff --git a/upload.ps1 b/upload.ps1
new file mode 100644
index 0000000..32ed813
--- /dev/null
+++ b/upload.ps1
@@ -0,0 +1,2 @@
+& .\updateversion.ps1
+& platformio run --target upload
\ No newline at end of file
diff --git a/web/app.js b/web/app.js
index 3dc80ef..a75b138 100644
--- a/web/app.js
+++ b/web/app.js
@@ -1,11 +1,38 @@
+var fs = require('fs');
var express = require('express');
+var semverUtils = require('semver-utils')
var client = require('./client');
var httpPort = 3127;
-var stairsHost = '10.138.2.12';
+var stairsHost = '10.138.2.25';
var stairsUdpPort = 3126;
+var firmwareFile = './update/firmware.elf';
+
+
+function requireNoCache(filename)
+{
+ delete require.cache[require.resolve(filename)];
+ return require(filename);
+}
+
+
+function isNewer(version1, version2)
+{
+ if (version1.major > version2.major) return true;
+ if (version1.major < version2.major) return false;
+
+ if (version1.minor > version2.minor) return true;
+ if (version1.minor < version2.minor) return false;
+
+ if (version1.patch > version2.patch) return true;
+ if (version1.patch < version2.patch) return false;
+
+ if (version1.build > version2.build) return true;
+ return false;
+}
+
client.init(stairsHost, stairsUdpPort);
@@ -45,6 +72,46 @@ app.get('/setMode/:mode', function(req, res)
});
});
+app.get('/updateFirmware', function(req, res)
+{
+ client.updateFirmware(req.query, function(data, error)
+ {
+ if (error)
+ res.status(500);
+
+ res.send(data);
+ });
+})
+
+app.get('/checkUpdate', function(req, res)
+{
+ if (!fs.existsSync(firmwareFile))
+ {
+ console.log('checkUpdate: ' + firmwareFile + ' not found!');
+ res.sendStatus(304);
+ return;
+ }
+
+ var version = requireNoCache('./version.js');
+ var deviceVersion = semverUtils.parse(req.headers['x-esp8266-version']);
+ var localVersion = semverUtils.parse(version.Version);
+
+ console.log('checkUpdate:');
+ console.log(' Device version = ' + semverUtils.stringify(deviceVersion));
+ console.log(' Local version = ' + semverUtils.stringify(localVersion));
+
+ if (isNewer(localVersion, deviceVersion))
+ {
+ res.download(firmwareFile);
+ }
+ else
+ {
+ res.sendStatus(304);
+ }
+
+ res.send(304);
+});
+
app.use(express.static(__dirname + '/static'));
diff --git a/web/client.js b/web/client.js
index fcc8673..3776861 100644
--- a/web/client.js
+++ b/web/client.js
@@ -53,7 +53,7 @@ client.on('message', function (message, remote)
});
-function requestResponse(buffer, callback)
+function requestResponse(buffer, callback, withTimeout)
{
if (buffer === null || buffer.length == 0) return;
console.log('> ' + buffer.toString('hex'));
@@ -61,17 +61,21 @@ function requestResponse(buffer, callback)
var command = buffer.readInt8(0);
var cancelled = false;
- var timeout = setTimeout(function()
+ if (typeof(withTimeout) == 'undefined') withTimeout = true;
+ if (withTimeout)
{
- cancelled = true;
- callback(null, true);
- clearTimeout(timeout);
- }, 2000);
+ var timeout = setTimeout(function()
+ {
+ cancelled = true;
+ callback(null, true);
+ clearTimeout(timeout);
+ }, 2000);
+ }
registerResponseHandler(command, function(reader, error)
{
if (cancelled) return;
- clearTimeout(timeout);
+ if (withTimeout) clearTimeout(timeout);
callback(reader, error);
return true;
@@ -149,11 +153,11 @@ function writeModeData(mode, data)
var valueCount = Math.min(16, brightness.length);
var buffer = Buffer.alloc(2 + (valueCount * 2));
- buffer.writeInt8(protocol.Command.SetMode, 0);
- buffer.writeInt8(mode, 1);
+ buffer.writeUInt8(protocol.Command.SetMode, 0);
+ buffer.writeUInt8(mode, 1);
for (var index = 0; index < valueCount; index++)
- buffer.writeInt16LE(getBrightness(brightness[index]), 2 + (index * 2));
+ buffer.writeUInt16LE(getBrightness(brightness[index]), 2 + (index * 2));
return buffer;
@@ -239,5 +243,40 @@ module.exports =
else
callback(null, true);
});
+ },
+
+ updateFirmware: function(data, callback)
+ {
+ if (typeof(data.host) == 'undefined') data.host = '';
+ if (typeof(data.port) == 'undefined') data.port = 80;
+ if (typeof(data.path) == 'undefined') data.path = '';
+
+ var buffer = Buffer.alloc(1 + (data.host.length + 1) + 2 + (data.path.length + 1));
+ buffer.writeUInt8(protocol.Command.UpdateFirmware, 0);
+ var position = 1;
+
+ buffer.writeUInt16LE(data.port, position);
+ position += 2;
+
+ buffer.write(data.host, position);
+ position += data.host.length;
+ buffer.writeUInt8(0, position);
+ position++;
+
+ buffer.write(data.path, position);
+ position += data.path.length;
+ buffer.writeUInt8(0, position);
+
+ requestResponse(buffer,
+ function(reader, error)
+ {
+ if (!error)
+ {
+ var data = { hasUpdates: reader.nextInt8() == 1 };
+ callback(data, false);
+ }
+ else
+ callback(null, true);
+ }, false);
}
}
\ No newline at end of file
diff --git a/web/package.json b/web/package.json
index 77a8089..52f2816 100644
--- a/web/package.json
+++ b/web/package.json
@@ -7,6 +7,7 @@
"license": "ISC",
"dependencies": {
"buffer-reader": "^0.1.0",
- "express": "^4.15.2"
+ "express": "^4.15.2",
+ "semver-utils": "^1.1.1"
}
}
diff --git a/web/protocol.js b/web/protocol.js
index 667173e..f2d5b98 100644
--- a/web/protocol.js
+++ b/web/protocol.js
@@ -6,7 +6,8 @@ module.exports =
Ping: 0x01,
Reply: 0x02,
GetMode: 0x03,
- SetMode: 0x04
+ SetMode: 0x04,
+ UpdateFirmware: 0xFF
},
diff --git a/web/static/index.html b/web/static/index.html
index 1a88fdb..4263b1e 100644
--- a/web/static/index.html
+++ b/web/static/index.html
@@ -20,23 +20,29 @@
-
+
+
Mode
+
Settings
+
Update
+
+
+
+
+
+
+
+
+
+
+
+
+
+