Added Ping command
Added prototype Node.js client app Added UDP protocol documentation Replaced brzo_i2c library with standard Wire.h while debugging Removed test code from PCA9685 library
This commit is contained in:
parent
abe13843b4
commit
1f5f0a7d42
114
docs/protocol.md
Normal file
114
docs/protocol.md
Normal file
@ -0,0 +1,114 @@
|
||||
The Stairs firmware on the ESP8266 can be accessed using a custom light-weight UDP protocol. It is not intended to be accessed directly from the internet, and thus there is no security on the device itself. This is by design. Authentication should in my opinion be handled by a device with more processing power, such as a Raspberry Pi.
|
||||
|
||||
A Node.js application is included which provides a ReST interface. It is also not intended to be accessible from the internet, and does not provide any authentication either!
|
||||
|
||||
|
||||
Protocol
|
||||
========
|
||||
|
||||
The default port for UDP communication is 3126. Every request message will result in a response message which is either an error, the requested information or a confirmation of the newly stored data. Each message should be a separate packet.
|
||||
|
||||
16-bit (word) values are expected in little endian order (least significant byte first).
|
||||
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
The first byte of a request is the command. Further data depends on the command.
|
||||
|
||||
| Command | Name |
|
||||
| ------- | ---- |
|
||||
| 0x01 | Ping |
|
||||
| 0x03 | GetMode |
|
||||
| 0x04 | SetMode |
|
||||
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
A response is sent to the source address and port and starts with the Reply command.
|
||||
|
||||
| Command | Name |
|
||||
| ------- | ---- |
|
||||
| 0x02 | Reply |
|
||||
|
||||
|
||||
The second byte is the request command for which this reply is intended, or Error if the request command was not recognized or contained invalid parameters.
|
||||
|
||||
| Command | Name |
|
||||
| ------- | ---- |
|
||||
| 0x00 | Error |
|
||||
|
||||
In the case of an Error, the third byte will be the actual request command (or unrecognized value).
|
||||
|
||||
|
||||
### Ping
|
||||
A no-op command which can be used to tell if the device is responding. Returns the number of steps.
|
||||
|
||||
Input parameters:<br>
|
||||
_none_
|
||||
|
||||
Output parameters:<br>
|
||||
**steps** (byte): The number of steps.
|
||||
|
||||
|
||||
### GetMode
|
||||
Returns the current mode.
|
||||
|
||||
Input parameters:<br>
|
||||
_none_
|
||||
|
||||
Output parameters:<br>
|
||||
**mode** (byte): The identifier of the current mode. See Modes below.<br>
|
||||
**data** (0..n bytes): The parameters specific to the current mode. See Modes below.
|
||||
|
||||
|
||||
### SetMode
|
||||
Changed the current mode.
|
||||
|
||||
Input parameters:<br>
|
||||
**mode** (byte): The identifier of the current mode.<br>
|
||||
**data** (0..n bytes): The parameters specific to the current mode.
|
||||
|
||||
Output parameters:<br>
|
||||
_Same as input parameters_
|
||||
|
||||
Modes
|
||||
=====
|
||||
|
||||
| Mode | Name |
|
||||
| ---- | ---- |
|
||||
| 0x01 | Static |
|
||||
| 0x02 | Custom |
|
||||
| 0x03 | Alternate |
|
||||
| 0x04 | Slide |
|
||||
|
||||
### Static
|
||||
Sets all steps to the same brightness.
|
||||
|
||||
Parameters:<br>
|
||||
**brightness** (word): value in range 0 (off) to 4096 (fully on).
|
||||
|
||||
|
||||
### Custom
|
||||
Sets the brightness for each of the steps individually.
|
||||
|
||||
Parameters:<br>
|
||||
**brightness** (word[stepCount]): array of brightness values in range 0 - 4096. The number of values must be equal to the number of steps are reported in the Ping response. Bottom step first.
|
||||
|
||||
|
||||
### Alternate
|
||||
Alternates between even and odd steps being lit. Bring out our next contestant!
|
||||
|
||||
Parameters:<br>
|
||||
**interval** (word): The time each set of steps is lit in milliseconds.<br>
|
||||
**brightness** (word): value in range 0 (off) to 4096 (fully on).
|
||||
|
||||
|
||||
### Slide
|
||||
Lights one step at a time, moving up or down.
|
||||
|
||||
Parameters:<br>
|
||||
**interval** (word): How long each step is lit before moving to the next.<br>
|
||||
**direction** (byte): Determines the starting step / direction. Either Bottom/Up (0) or Top/Down (1).<br>
|
||||
**fadeOutTime** (word): If greater than 0 each step will fade out instead of turning off instantly after moving to the next. Specified in milliseconds.
|
@ -1,91 +1,38 @@
|
||||
/*
|
||||
* PCA9685 library for ESP8266
|
||||
* Copyright 2017 (c) Mark van Renswoude
|
||||
*
|
||||
* Uses brzo I2C library.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <brzo_i2c.h>
|
||||
#include "PCA9685.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
|
||||
void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency, uint8_t pinSDA, uint8_t pinSCL, uint32_t timeoutUSec)
|
||||
void PCA9685::setAddress(uint8_t address, uint8_t pinSDA, uint8_t pinSCL)
|
||||
{
|
||||
Wire.begin(pinSDA, pinSCL);
|
||||
this->setAddress(address);
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
uint8_t PCA9685::read(uint8_t registerAddress)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::write(uint8_t registerAddress, uint8_t value)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
inline void PCA9685::write(uint8_t data)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::setPWMFrequency(float frequency)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::setPWM(uint8_t pin, uint16_t value)
|
||||
{
|
||||
this->setAll(value);
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::setPWM(uint8_t pin, uint16_t on, uint16_t off)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PCA9685::setAll(uint16_t value)
|
||||
{
|
||||
analogWrite(9, map(value, 0, 4096, 0, 255));
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::setAll(uint16_t on, uint16_t off)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency, uint8_t pinSDA, uint8_t pinSCL, uint32_t timeoutUSec)
|
||||
{
|
||||
brzo_i2c_setup(pinSDA, pinSCL, timeoutUSec);
|
||||
this->setAddress(address, sclFrequency);
|
||||
}
|
||||
|
||||
|
||||
void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency)
|
||||
void PCA9685::setAddress(uint8_t address)
|
||||
{
|
||||
this->address = address;
|
||||
this->sclFrequency = sclFrequency;
|
||||
}
|
||||
|
||||
|
||||
uint8_t PCA9685::read(uint8_t registerAddress)
|
||||
{
|
||||
uint8_t result;
|
||||
uint8_t result = 0;
|
||||
|
||||
brzo_i2c_write(®isterAddress, 1, true);
|
||||
brzo_i2c_read(&result, 1, false);
|
||||
Wire.beginTransmission(this->address);
|
||||
Wire.write(registerAddress);
|
||||
Wire.endTransmission();
|
||||
|
||||
Wire.requestFrom(this->address, (uint8_t)1);
|
||||
if (Wire.available())
|
||||
result = Wire.read();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -93,21 +40,20 @@ uint8_t PCA9685::read(uint8_t registerAddress)
|
||||
|
||||
void PCA9685::write(uint8_t registerAddress, uint8_t value)
|
||||
{
|
||||
uint8_t buffer[2];
|
||||
|
||||
buffer[0] = registerAddress;
|
||||
buffer[1] = value;
|
||||
brzo_i2c_write(buffer, 2, false);
|
||||
Wire.beginTransmission(this->address);
|
||||
Wire.write(registerAddress);
|
||||
Wire.write(value);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
|
||||
inline void PCA9685::write(uint8_t data)
|
||||
{
|
||||
brzo_i2c_write(&data, 1, false);
|
||||
Wire.write(data);
|
||||
}
|
||||
|
||||
|
||||
#define prescaleValue 25000000 / 4096
|
||||
static const float prescaleValue = 25000000 / 4096;
|
||||
|
||||
void PCA9685::setPWMFrequency(float frequency)
|
||||
{
|
||||
@ -115,10 +61,8 @@ void PCA9685::setPWMFrequency(float frequency)
|
||||
// https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/
|
||||
uint8_t prescale = floor(prescaleValue / (frequency * 0.9) - 0.5);
|
||||
|
||||
brzo_i2c_start_transaction(address, sclFrequency);
|
||||
|
||||
// Sleep while changing the frequency
|
||||
uint8_t oldMode = this->read(PCA9685::RegisterMode1);
|
||||
uint8_t oldMode = this->read(PCA9685::RegisterMode1) & !PCA9685::Mode1Sleep;
|
||||
uint8_t newMode = (oldMode & !PCA9685::Mode1Restart) | PCA9685::Mode1Sleep;
|
||||
|
||||
this->write(PCA9685::RegisterMode1, newMode);
|
||||
@ -134,8 +78,6 @@ void PCA9685::setPWMFrequency(float frequency)
|
||||
|
||||
// Restart and turn on auto-increment (required for SetPWM)
|
||||
this->write(PCA9685::RegisterMode1, oldMode | PCA9685::Mode1Restart | PCA9685::Mode1AI | PCA9685::Mode1AllCall);
|
||||
|
||||
brzo_i2c_end_transaction();
|
||||
}
|
||||
|
||||
|
||||
@ -155,13 +97,13 @@ void PCA9685::setPWM(uint8_t pin, uint16_t value)
|
||||
|
||||
void PCA9685::setPWM(uint8_t pin, uint16_t on, uint16_t off)
|
||||
{
|
||||
brzo_i2c_start_transaction(address, sclFrequency);
|
||||
Wire.beginTransmission(this->address);
|
||||
this->write(PCA9685::RegisterLED0OnL + (4 * pin));
|
||||
this->write(on);
|
||||
this->write(on >> 8);
|
||||
this->write(off);
|
||||
this->write(off >> 8);
|
||||
brzo_i2c_end_transaction();
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
|
||||
@ -182,12 +124,11 @@ void PCA9685::setAll(uint16_t value)
|
||||
|
||||
void PCA9685::setAll(uint16_t on, uint16_t off)
|
||||
{
|
||||
brzo_i2c_start_transaction(address, sclFrequency);
|
||||
Wire.beginTransmission(this->address);
|
||||
this->write(PCA9685::RegisterAllLEDOnL);
|
||||
this->write(on);
|
||||
this->write(on >> 8);
|
||||
this->write(off);
|
||||
this->write(off >> 8);
|
||||
brzo_i2c_end_transaction();
|
||||
}
|
||||
*/
|
||||
Wire.endTransmission();
|
||||
}
|
@ -4,16 +4,14 @@
|
||||
/*
|
||||
* PCA9685 library for ESP8266
|
||||
* Copyright 2017 (c) Mark van Renswoude
|
||||
*
|
||||
* Uses brzo I2C library.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
class PCA9685
|
||||
{
|
||||
private:
|
||||
uint8_t address;
|
||||
uint16_t sclFrequency;
|
||||
|
||||
protected:
|
||||
uint8_t read(uint8_t registerAddress);
|
||||
@ -35,11 +33,11 @@ class PCA9685
|
||||
static const uint8_t Mode1Sleep = 0x10;
|
||||
static const uint8_t Mode1AllCall = 0x01;
|
||||
|
||||
// Call this to initializes the brzo I2C library
|
||||
void setAddress(uint8_t address, uint16_t sclFrequency, uint8_t pinSDA, uint8_t pinSCL, uint32_t timeoutUSec);
|
||||
// Call this to initializes the I2C library
|
||||
void setAddress(uint8_t address, uint8_t pinSDA, uint8_t pinSCL);
|
||||
|
||||
// Call this if you already initialized the brzo I2C library
|
||||
void setAddress(uint8_t address, uint16_t sclFrequency);
|
||||
// Call this if you already initialized the I2C library
|
||||
void setAddress(uint8_t address);
|
||||
|
||||
void setPWMFrequency(float frequency);
|
||||
|
||||
|
13
src/config.h
13
src/config.h
@ -4,6 +4,9 @@
|
||||
#include <stdint.h>
|
||||
#include "credentials.h"
|
||||
|
||||
// The name of this device on the network
|
||||
static const char* WiFiHostname = "Stairs";
|
||||
|
||||
|
||||
// The number of steps (assumed to be <= 16, as the code currently only controls 1 PCA9685 board)
|
||||
static const uint8_t StepCount = 14;
|
||||
@ -14,14 +17,12 @@ static const uint16_t UDPPort = 3126;
|
||||
|
||||
|
||||
// Pins for the I2C bus
|
||||
static const uint8_t PinSDA = 12;
|
||||
static const uint8_t PinSCL = 13;
|
||||
static const uint8_t PinSDA = 13;
|
||||
static const uint8_t PinSCL = 12;
|
||||
|
||||
static const uint32_t I2CTimeout = 2000;
|
||||
|
||||
// I2C address and clock frequency of the PCA9685 board
|
||||
static const uint8_t PWMDriverAddress = 0x60;
|
||||
static const uint16_t PWMDriverSCLFrequency = 400;
|
||||
// I2C address and PWM frequency of the PCA9685 board
|
||||
static const uint8_t PWMDriverAddress = 0x40;
|
||||
static const uint16_t PWMDriverPWMFrequency = 1600;
|
||||
|
||||
#endif
|
21
src/main.cpp
21
src/main.cpp
@ -36,12 +36,13 @@ void handleCurrentMode();
|
||||
void setup()
|
||||
{
|
||||
pwmDriver = new PCA9685();
|
||||
pwmDriver->setAddress(PWMDriverAddress, PWMDriverSCLFrequency, PinSDA, PinSCL, I2CTimeout);
|
||||
pwmDriver->setAddress(PWMDriverAddress, PinSDA, PinSCL);
|
||||
pwmDriver->setPWMFrequency(PWMDriverPWMFrequency);
|
||||
|
||||
stairs = new Stairs();
|
||||
stairs->init(pwmDriver);
|
||||
|
||||
WiFi.hostname(WiFiHostname);
|
||||
WiFi.begin(WiFiSSID, WiFiPassword);
|
||||
|
||||
|
||||
@ -123,7 +124,12 @@ void handleRequest(uint8_t* packet)
|
||||
|
||||
switch (*packet)
|
||||
{
|
||||
case Command::Ping:
|
||||
udpServer.write(Command::Ping);
|
||||
break;
|
||||
|
||||
case Command::GetMode:
|
||||
udpServer.write(Command::GetMode);
|
||||
udpServer.write(currentModeIdentifier);
|
||||
break;
|
||||
|
||||
@ -138,14 +144,25 @@ void handleRequest(uint8_t* packet)
|
||||
if (newMode != NULL)
|
||||
{
|
||||
newMode->read(packet);
|
||||
|
||||
udpServer.write(Command::SetMode);
|
||||
newMode->write(&udpServer);
|
||||
|
||||
setCurrentMode(newMode, newIdentifier);
|
||||
}
|
||||
else
|
||||
udpServer.write((uint8_t)0);
|
||||
{
|
||||
udpServer.write(Command::Error);
|
||||
udpServer.write(Command::SetMode);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
udpServer.write(Command::Error);
|
||||
udpServer.write(*packet);
|
||||
break;
|
||||
}
|
||||
|
||||
udpServer.endPacket();
|
||||
|
@ -8,7 +8,8 @@
|
||||
class IStairs
|
||||
{
|
||||
public:
|
||||
virtual void setStep(uint8_t step, uint16_t value) = 0;
|
||||
virtual uint8_t getCount() = 0;
|
||||
virtual void set(uint8_t step, uint16_t value) = 0;
|
||||
virtual void setAll(uint16_t value) = 0;
|
||||
};
|
||||
|
||||
|
@ -6,9 +6,12 @@
|
||||
class Command
|
||||
{
|
||||
public:
|
||||
static const uint8_t Reply = 0x01;
|
||||
static const uint8_t GetMode = 0x02;
|
||||
static const uint8_t SetMode = 0x03;
|
||||
static const uint8_t Error = 0x00;
|
||||
|
||||
static const uint8_t Ping = 0x01;
|
||||
static const uint8_t Reply = 0x02;
|
||||
static const uint8_t GetMode = 0x03;
|
||||
static const uint8_t SetMode = 0x04;
|
||||
};
|
||||
|
||||
|
||||
|
37
web/app.js
Normal file
37
web/app.js
Normal file
@ -0,0 +1,37 @@
|
||||
var protocol = require('./protocol');
|
||||
var dgram = require('dgram');
|
||||
|
||||
var on = 0;
|
||||
var speed = 256;
|
||||
|
||||
|
||||
function lsb(value) { return value & 0xFF; }
|
||||
function msb(value) { return (value >> 8) & 0xFF; }
|
||||
|
||||
|
||||
setInterval(function()
|
||||
{
|
||||
// 0x00, 0x10 = 4096
|
||||
on += speed;
|
||||
if (on <= 0 || on >= 4096)
|
||||
speed = -speed;
|
||||
|
||||
var message = new Buffer([protocol.Command.SetMode, protocol.Mode.Static, lsb(on), msb(on)]);
|
||||
var client = dgram.createSocket('udp4');
|
||||
client.on('listening', function()
|
||||
{
|
||||
var address = client.address();
|
||||
console.log('UDP client listening on ' + address.address + ":" + address.port);
|
||||
});
|
||||
|
||||
client.on('message', function (message, remote)
|
||||
{
|
||||
console.log(remote.address + ':' + remote.port +' - ' + message.toString('hex'));
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.send(message, 0, message.length, 3126, '10.138.2.12', function(err, bytes) {
|
||||
if (err) throw err;
|
||||
console.log('UDP message sent');
|
||||
});
|
||||
}, 200);
|
8
web/package.json
Normal file
8
web/package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "stairs",
|
||||
"version": "1.0.0",
|
||||
"description": "Stairs lighting project",
|
||||
"main": "app.js",
|
||||
"author": "Mark van Renswoude",
|
||||
"license": "ISC"
|
||||
}
|
21
web/protocol.js
Normal file
21
web/protocol.js
Normal file
@ -0,0 +1,21 @@
|
||||
module.exports =
|
||||
{
|
||||
Command:
|
||||
{
|
||||
Error: 0x00,
|
||||
Ping: 0x01,
|
||||
Reply: 0x02,
|
||||
GetMode: 0x03,
|
||||
SetMode: 0x04
|
||||
},
|
||||
|
||||
|
||||
Mode:
|
||||
{
|
||||
Static: 0x01,
|
||||
Custom: 0x02,
|
||||
Alternate: 0x03,
|
||||
Slide: 0x04
|
||||
//ADC: 0x05
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user