Implemented protocol and message handling
This commit is contained in:
parent
a5eab1427d
commit
06b5a0e720
22
module/src/config.h
Normal file
22
module/src/config.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Stairs lighting
|
||||
* Copyright 2017 (c) Mark van Renswoude
|
||||
*
|
||||
* https://git.x2software.net/pub/Stairs
|
||||
*/
|
||||
#ifndef __config
|
||||
#define __config
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// How long the display should stay on once it's idle and showing the
|
||||
// current step numbers.
|
||||
const uint32_t DisplayIdleTimeout = 5000;
|
||||
|
||||
// How long since the last packet before the communication icon shows
|
||||
// as 'off'. Note that if a module is not configured for sensors and
|
||||
// the light doesn't change either there will be no communication,
|
||||
// so no need to panic immediately.
|
||||
const uint32_t CommIdleTimeout = 1000;
|
||||
|
||||
#endif
|
@ -6,10 +6,12 @@
|
||||
*/
|
||||
#include "display.h"
|
||||
#include <Fonts/FreeSans12pt7b.h>
|
||||
#include "global.h"
|
||||
#include "config.h"
|
||||
#include "icons.h"
|
||||
#include "global.h"
|
||||
|
||||
#define WaitAnimationInterval 150
|
||||
|
||||
const uint8_t WaitAnimationInterval = 150;
|
||||
|
||||
|
||||
void Display::init()
|
||||
@ -19,29 +21,45 @@ void Display::init()
|
||||
}
|
||||
|
||||
|
||||
void Display::show(Screen screen)
|
||||
void Display::update()
|
||||
{
|
||||
switch (screen)
|
||||
switch (state)
|
||||
{
|
||||
case Screen::Blank:
|
||||
off();
|
||||
break;
|
||||
|
||||
case Screen::WaitingForComm:
|
||||
case State::WaitingForComm:
|
||||
drawWaitingForComm();
|
||||
break;
|
||||
|
||||
case Screen::ModuleIndex:
|
||||
case State::Linking:
|
||||
drawLinking();
|
||||
break;
|
||||
|
||||
case State::LinkingSet:
|
||||
drawModuleIndex();
|
||||
break;
|
||||
|
||||
case State::DisplayModuleIndex:
|
||||
if (currentTime - mLastStateChange >= DisplayIdleTimeout)
|
||||
{
|
||||
state = State::DisplayOff;
|
||||
off();
|
||||
}
|
||||
else
|
||||
drawModuleIndex();
|
||||
|
||||
break;
|
||||
|
||||
case State::DisplayOff:
|
||||
off();
|
||||
break;
|
||||
}
|
||||
|
||||
setLastDrawnState();
|
||||
}
|
||||
|
||||
|
||||
void Display::drawWaitingForComm()
|
||||
{
|
||||
uint32_t currentTime = millis();
|
||||
if (mLastScreen == Screen::WaitingForComm && currentTime - mLastWaiting < WaitAnimationInterval)
|
||||
if (mLastDrawnState == State::WaitingForComm && currentTime - mLastWaiting < WaitAnimationInterval)
|
||||
return;
|
||||
|
||||
checkOn();
|
||||
@ -58,7 +76,6 @@ void Display::drawWaitingForComm()
|
||||
if (mWaitAnimationStep > 3)
|
||||
mWaitAnimationStep = 0;
|
||||
|
||||
mLastScreen = Screen::WaitingForComm;
|
||||
mLastWaiting = currentTime;
|
||||
}
|
||||
|
||||
@ -66,7 +83,7 @@ void Display::drawWaitingForComm()
|
||||
void Display::drawModuleIndex()
|
||||
{
|
||||
uint8_t moduleIndex = settings.getModuleIndex();
|
||||
if (mLastScreen == Screen::ModuleIndex && mLastModuleIndex == moduleIndex)
|
||||
if (mLastDrawnState == State::DisplayModuleIndex && mLastModuleIndex == moduleIndex)
|
||||
return;
|
||||
|
||||
checkOn();
|
||||
@ -75,34 +92,61 @@ void Display::drawModuleIndex()
|
||||
drawCommIcon();
|
||||
|
||||
mDisplay->setFont(&FreeSans12pt7b);
|
||||
mDisplay->setCursor(0, mDisplay->height());
|
||||
mDisplay->setCursor(0, mDisplay->height() - 1);
|
||||
|
||||
// ToDo show numbers based on module index
|
||||
if (moduleIndex == ModuleIndexUndefined)
|
||||
{
|
||||
mDisplay->print("Not set");
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t firstStep = (moduleIndex * 2) + 1;
|
||||
mDisplay->print(firstStep);
|
||||
mDisplay->print(" - ");
|
||||
mDisplay->print(firstStep + 1);
|
||||
}
|
||||
|
||||
mDisplay->display();
|
||||
}
|
||||
|
||||
mLastScreen = Screen::ModuleIndex;
|
||||
mLastModuleIndex = moduleIndex;
|
||||
|
||||
void Display::drawLinking()
|
||||
{
|
||||
if (mLastDrawnState == State::Linking)
|
||||
return;
|
||||
|
||||
checkOn();
|
||||
mDisplay->clearDisplay();
|
||||
drawTitle("Steps");
|
||||
drawCommIcon();
|
||||
|
||||
mDisplay->setFont(&FreeSans12pt7b);
|
||||
mDisplay->setCursor(0, mDisplay->height() - 1);
|
||||
mDisplay->print("Click to set");
|
||||
mDisplay->display();
|
||||
}
|
||||
|
||||
|
||||
void Display::setLastDrawnState()
|
||||
{
|
||||
if (state != mLastDrawnState)
|
||||
{
|
||||
mLastDrawnState = state;
|
||||
mLastStateChange = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Display::off()
|
||||
{
|
||||
if (mLastScreen != Screen::Blank)
|
||||
{
|
||||
if (mLastDrawnState != State::DisplayOff)
|
||||
mDisplay->ssd1306_command(SSD1306_DISPLAYOFF);
|
||||
mLastScreen = Screen::Blank;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Display::checkOn()
|
||||
{
|
||||
if (mLastScreen == Screen::Blank)
|
||||
if (mLastDrawnState == State::DisplayOff)
|
||||
mDisplay->ssd1306_command(SSD1306_DISPLAYON);
|
||||
}
|
||||
|
||||
@ -120,8 +164,9 @@ void Display::drawTitle(const char* title)
|
||||
|
||||
void Display::drawCommIcon()
|
||||
{
|
||||
// ToDo show actual state depending on last received message (should never be more than half a second ago)
|
||||
mDisplay->drawBitmap(mDisplay->width() - IconCommWidth, 0, IconCommOff, IconCommWidth, IconCommHeight, 1);
|
||||
mDisplay->drawBitmap(mDisplay->width() - IconCommWidth, 0,
|
||||
currentTime - comm.getPacketStartTime() <= CommIdleTimeout ? IconCommOn : IconCommOff,
|
||||
IconCommWidth, IconCommHeight, 1);
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,21 +11,15 @@
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include "lib/Adafruit_SSD1306.h"
|
||||
|
||||
|
||||
enum struct Screen: uint8_t
|
||||
{
|
||||
Blank,
|
||||
WaitingForComm,
|
||||
ModuleIndex
|
||||
};
|
||||
#include "state.h"
|
||||
|
||||
|
||||
class Display
|
||||
{
|
||||
private:
|
||||
Adafruit_SSD1306* mDisplay;
|
||||
Screen mLastScreen = Screen::Blank;
|
||||
State mLastDrawnState = State::DisplayOff;
|
||||
uint32_t mLastStateChange;
|
||||
uint8_t mLastModuleIndex;
|
||||
uint32_t mLastWaiting;
|
||||
uint8_t mWaitAnimationStep = 0;
|
||||
@ -38,10 +32,13 @@ class Display
|
||||
|
||||
void drawWaitingForComm();
|
||||
void drawModuleIndex();
|
||||
void drawLinking();
|
||||
void off();
|
||||
|
||||
void setLastDrawnState();
|
||||
public:
|
||||
void init();
|
||||
void show(Screen screen);
|
||||
void off();
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif
|
@ -26,4 +26,6 @@ size_t serialWrite(const byte what)
|
||||
Settings settings;
|
||||
Display display;
|
||||
RS485 comm(serialRead, serialAvailable, serialWrite, 20);
|
||||
PCA9685 ledDriver;
|
||||
PCA9685 pwmDriver;
|
||||
State state = State::WaitingForComm;
|
||||
uint32_t currentTime;
|
@ -11,10 +11,13 @@
|
||||
#include "lib/PCA9685.h"
|
||||
#include "settings.h"
|
||||
#include "display.h"
|
||||
#include "state.h"
|
||||
|
||||
extern Settings settings;
|
||||
extern Display display;
|
||||
extern RS485 comm;
|
||||
extern PCA9685 ledDriver;
|
||||
extern PCA9685 pwmDriver;
|
||||
extern State state;
|
||||
extern uint32_t currentTime;
|
||||
|
||||
#endif
|
@ -8,16 +8,7 @@
|
||||
#include <Wire.h>
|
||||
#include "global.h"
|
||||
#include "display.h"
|
||||
|
||||
|
||||
enum struct State: uint8_t
|
||||
{
|
||||
WaitingForComm,
|
||||
Connected
|
||||
};
|
||||
|
||||
|
||||
State currentState = State::WaitingForComm;
|
||||
#include "protocol.h"
|
||||
|
||||
|
||||
void setup()
|
||||
@ -27,7 +18,7 @@ void setup()
|
||||
// Set up I2C devices: the SSD1306 OLED display and PCA9685 LED PWM driver
|
||||
Wire.begin();
|
||||
display.init();
|
||||
ledDriver.setAddress(0x40);
|
||||
pwmDriver.setAddress(0x40);
|
||||
|
||||
// At 16 Mhz we pick a baud rate with a very acceptable 0.2% error rate
|
||||
// Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html
|
||||
@ -35,22 +26,154 @@ void setup()
|
||||
comm.begin();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Forward declarations
|
||||
void handleCommMessage();
|
||||
void handlePing(uint8_t* data, uint8_t length);
|
||||
void handleDisplayModuleIndex(uint8_t* data, uint8_t length);
|
||||
void handleStartLink(uint8_t* data, uint8_t length);
|
||||
void handleRequestLinkResponse(uint8_t* data, uint8_t length);
|
||||
void handleStopLink(uint8_t* data, uint8_t length);
|
||||
void handleSetPWM(uint8_t* data, uint8_t length);
|
||||
void handleGetSensors(uint8_t* data, uint8_t length);
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
currentTime = millis();
|
||||
|
||||
if (comm.update())
|
||||
handleCommMessage();
|
||||
|
||||
display.update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void handleCommMessage()
|
||||
{
|
||||
uint8_t* data = comm.getData();
|
||||
uint8_t length = comm.getLength();
|
||||
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
if (state == State::WaitingForComm)
|
||||
state = State::DisplayModuleIndex;
|
||||
|
||||
uint8_t command = *data; data++; length--;
|
||||
uint8_t moduleIndex = ModuleIndexUndefined;
|
||||
|
||||
if (command & MaskModuleCommand)
|
||||
{
|
||||
// TODO get data
|
||||
currentState = State::Connected;
|
||||
if (!settings.hasModuleIndex())
|
||||
// We're not linked yet
|
||||
return;
|
||||
|
||||
moduleIndex = *data; data++; length--;
|
||||
|
||||
if (settings.getModuleIndex() != moduleIndex)
|
||||
// This message is meant for another module
|
||||
return;
|
||||
}
|
||||
|
||||
switch (currentState)
|
||||
switch (command)
|
||||
{
|
||||
case State::WaitingForComm:
|
||||
display.show(Screen::WaitingForComm);
|
||||
break;
|
||||
case CommandPing: handlePing(data, length); break;
|
||||
case CommandDisplayModuleIndex: handleDisplayModuleIndex(data, length); break;
|
||||
case CommandStartLink: handleStartLink(data, length); break;
|
||||
case ResponseRequestLink: handleRequestLinkResponse(data, length); break;
|
||||
case CommandStopLink: handleStopLink(data, length); break;
|
||||
case CommandSetPWM: handleSetPWM(data, length); break;
|
||||
case CommandGetSensors: handleGetSensors(data, length); break;
|
||||
|
||||
case State::Connected:
|
||||
display.show(Screen::ModuleIndex);
|
||||
break;
|
||||
default:
|
||||
if (command & MaskModuleCommand)
|
||||
{
|
||||
// Sender expects a response from us
|
||||
const uint8_t msg[] = { ResponseUhmWhat, moduleIndex };
|
||||
comm.sendMsg(msg, sizeof(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void handlePing(uint8_t* data, uint8_t length)
|
||||
{
|
||||
const uint8_t msg[] = { ResponsePing, settings.getModuleIndex() };
|
||||
comm.sendMsg(msg, sizeof(msg));
|
||||
}
|
||||
|
||||
|
||||
void handleDisplayModuleIndex(uint8_t* data, uint8_t length)
|
||||
{
|
||||
if (state == State::DisplayOff)
|
||||
state = State::DisplayModuleIndex;
|
||||
}
|
||||
|
||||
|
||||
void handleStartLink(uint8_t* data, uint8_t length)
|
||||
{
|
||||
state = State::Linking;
|
||||
}
|
||||
|
||||
|
||||
void handleRequestLinkResponse(uint8_t* data, uint8_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
if (state != State::Linking)
|
||||
return;
|
||||
|
||||
settings.setModuleIndex(*data);
|
||||
state = State::LinkingSet;
|
||||
}
|
||||
|
||||
|
||||
void handleStopLink(uint8_t* data, uint8_t length)
|
||||
{
|
||||
if (state == State::Linking || state == State::LinkingSet)
|
||||
state = State::DisplayModuleIndex;
|
||||
}
|
||||
|
||||
|
||||
void handleSetPWM(uint8_t* data, uint8_t length)
|
||||
{
|
||||
if (length < 5)
|
||||
return;
|
||||
|
||||
uint8_t flags = *data; data++;
|
||||
uint16_t value1 = *(reinterpret_cast<uint16_t*>(data)); data += 2;
|
||||
uint16_t value2 = *(reinterpret_cast<uint16_t*>(data)); data += 2;
|
||||
|
||||
pwmDriver.setPWM(0, value1);
|
||||
pwmDriver.setPWM(1, value2);
|
||||
|
||||
if (flags & SetPWMFlagModuleLEDs)
|
||||
{
|
||||
pwmDriver.setPWM(2, value1);
|
||||
pwmDriver.setPWM(3, value2);
|
||||
}
|
||||
else
|
||||
{
|
||||
pwmDriver.setPWM(2, 0);
|
||||
pwmDriver.setPWM(3, 0);
|
||||
}
|
||||
|
||||
const uint8_t msg[] = { ResponseSetPWM, settings.getModuleIndex() };
|
||||
comm.sendMsg(msg, sizeof(msg));
|
||||
}
|
||||
|
||||
|
||||
void handleGetSensors(uint8_t* data, uint8_t length)
|
||||
{
|
||||
// TODO get sensor values
|
||||
|
||||
uint8_t sensor1 = 0;
|
||||
uint8_t sensor2 = 0;
|
||||
|
||||
const uint8_t msg[] = { ResponseSetPWM, settings.getModuleIndex(), sensor1, sensor2 };
|
||||
comm.sendMsg(msg, sizeof(msg));
|
||||
}
|
||||
|
149
module/src/protocol.h
Normal file
149
module/src/protocol.h
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Stairs lighting
|
||||
* Copyright 2017 (c) Mark van Renswoude
|
||||
*
|
||||
* https://git.x2software.net/pub/Stairs
|
||||
*/
|
||||
#ifndef __protocol
|
||||
#define __protocol
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/*
|
||||
* There are three classes of messages, these masks provide a way to identify
|
||||
* unknown messages if the protocol ever changes.
|
||||
*
|
||||
* MaskBroadcastCommand: commands which do not expect a response
|
||||
* MaskModuleCommand: commands which do expect a command. respond with ResponseUhmWhat to satisfy the response requirement.
|
||||
* MaskResponse: responses to commands
|
||||
*/
|
||||
const uint8_t MaskBroadcastCommand = 0b10000000;
|
||||
const uint8_t MaskModuleCommand = 0b11000000;
|
||||
const uint8_t MaskResponse = 0b01000000;
|
||||
|
||||
const uint8_t ResponseUhmWhat = 0x00 | MaskResponse;
|
||||
|
||||
|
||||
/*
|
||||
* Ping:
|
||||
* Aimed at a specific module, which must respond with a
|
||||
* ResponsePing message.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandPing
|
||||
* [1] Module index
|
||||
*
|
||||
* Response:
|
||||
* [0] ResponsePing
|
||||
* [1] Module index
|
||||
*/
|
||||
const uint8_t CommandPing = 0x01 | MaskModuleCommand;
|
||||
const uint8_t ResponsePing = CommandPing | MaskResponse;
|
||||
|
||||
|
||||
/*
|
||||
* Display module index:
|
||||
* Broadcast to all modules which should turn on their display
|
||||
* and show the current settings. No response is expected.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandDisplayModuleIndex
|
||||
*/
|
||||
const uint8_t CommandDisplayModuleIndex = 0x02 | MaskBroadcastCommand;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Start linking:
|
||||
* Broadcast to all modules which should change to link mode.
|
||||
* During link mode the master will disable all other communication,
|
||||
* allowing the module to respond at will in response to user input.
|
||||
*
|
||||
* Each module should send a CommandRequestLink message when user input
|
||||
* is provided.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandStartLink
|
||||
*
|
||||
* Response (eventually):
|
||||
* See CommandRequestLink
|
||||
*/
|
||||
const uint8_t CommandStartLink = 0x10 | MaskBroadcastCommand;
|
||||
|
||||
|
||||
/*
|
||||
* Request link:
|
||||
* Sent by a module when user input is provided to link this module
|
||||
* as the next in line. All other modules should disable sending this
|
||||
* message until the currently requesting module receives a response.
|
||||
*
|
||||
* The master must respond with the new module index, which the
|
||||
* module must apply and store immediately.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandRequestLink
|
||||
*
|
||||
* Response:
|
||||
* [0] ResponseRequestLink
|
||||
* [1] New module index
|
||||
*/
|
||||
const uint8_t CommandRequestLink = 0x11 | MaskBroadcastCommand;
|
||||
const uint8_t ResponseRequestLink = CommandRequestLink | MaskResponse;
|
||||
|
||||
|
||||
/*
|
||||
* Stop linking:
|
||||
* Broadcast to all modules when the master takes back control
|
||||
* over the communication line. No response is expected.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandStopLink
|
||||
*/
|
||||
const uint8_t CommandStopLink = 0x12 | MaskBroadcastCommand;
|
||||
|
||||
|
||||
/*
|
||||
* Set PWM value:
|
||||
* Aimed at a specific module, which must apply the specified
|
||||
* PWM values to the LED strips and respond with a
|
||||
* ResponseSetPWM message.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandSetPWM
|
||||
* [1] Module index
|
||||
* [2] Flags, see below
|
||||
* [3,4] PWM value for step 1 (0-4095, uint16_t)
|
||||
* [5,6] PWM value for step 2 (0-4095, uint16_t)
|
||||
*
|
||||
* Response:
|
||||
* [0] ResponseSetPWM
|
||||
* [1] Module index
|
||||
*/
|
||||
const uint8_t CommandSetPWM = 0x20 | MaskModuleCommand;
|
||||
const uint8_t ResponseSetPWM = CommandSetPWM | MaskResponse;
|
||||
|
||||
// If included, the on-board verification LEDs should light up with
|
||||
// the specified PWM values as well. Otherwise they should be off.
|
||||
const uint8_t SetPWMFlagModuleLEDs = 0x01;
|
||||
|
||||
|
||||
/*
|
||||
* Get sensor values:
|
||||
* Aimed at a specific module, which must response with a
|
||||
* ResponseGetSensors message containing the current sensor states.
|
||||
*
|
||||
* Request:
|
||||
* [0] CommandGetSensors
|
||||
* [1] Module index
|
||||
*
|
||||
* Response:
|
||||
* [0] ResponseGetSensors
|
||||
* [1] Module index
|
||||
* [2] Analog (0-255) or digital (0, 255) value for sensor 1
|
||||
* [3] Analog (0-255) or digital (0, 255) value for sensor 2
|
||||
*/
|
||||
const uint8_t CommandGetSensors = 0x30 | MaskModuleCommand;
|
||||
const uint8_t ResponseGetSensors = CommandGetSensors | MaskResponse;
|
||||
|
||||
#endif
|
@ -8,10 +8,6 @@
|
||||
#include <EEPROM.h>
|
||||
|
||||
|
||||
const uint8_t ModuleIndexUndefined = 0xff;
|
||||
|
||||
|
||||
|
||||
// First byte is 0xAA to recognize uninitialised EEPROM
|
||||
const uint8_t EEPROMHeader = 0xAA;
|
||||
const uint8_t EEPROMCurrentVersion = 1;
|
||||
@ -40,6 +36,12 @@ void Settings::init()
|
||||
}
|
||||
|
||||
|
||||
bool Settings::hasModuleIndex()
|
||||
{
|
||||
return mModuleIndex != ModuleIndexUndefined;
|
||||
}
|
||||
|
||||
|
||||
uint8_t Settings::getModuleIndex(void)
|
||||
{
|
||||
return mModuleIndex;
|
||||
@ -52,5 +54,5 @@ void Settings::setModuleIndex(uint8_t index)
|
||||
return;
|
||||
|
||||
mModuleIndex = index;
|
||||
EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex);
|
||||
// ToDo re-enable: EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex);
|
||||
}
|
@ -8,8 +8,9 @@
|
||||
#define __settings
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
extern const uint8_t ModuleIndexUndefined;
|
||||
const uint8_t ModuleIndexUndefined = 0xff;
|
||||
|
||||
|
||||
class Settings
|
||||
@ -20,6 +21,7 @@ class Settings
|
||||
public:
|
||||
void init();
|
||||
|
||||
bool hasModuleIndex();
|
||||
uint8_t getModuleIndex();
|
||||
void setModuleIndex(uint8_t index);
|
||||
};
|
||||
|
19
module/src/state.h
Normal file
19
module/src/state.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Stairs lighting
|
||||
* Copyright 2017 (c) Mark van Renswoude
|
||||
*
|
||||
* https://git.x2software.net/pub/Stairs
|
||||
*/
|
||||
#ifndef __state
|
||||
#define __state
|
||||
|
||||
enum struct State: uint8_t
|
||||
{
|
||||
WaitingForComm,
|
||||
Linking,
|
||||
LinkingSet,
|
||||
DisplayModuleIndex,
|
||||
DisplayOff
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user