Implemented protocol and message handling

This commit is contained in:
Mark van Renswoude 2018-11-20 21:55:31 +01:00
parent a5eab1427d
commit 06b5a0e720
10 changed files with 432 additions and 68 deletions

22
module/src/config.h Normal file
View 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

View File

@ -6,10 +6,12 @@
*/ */
#include "display.h" #include "display.h"
#include <Fonts/FreeSans12pt7b.h> #include <Fonts/FreeSans12pt7b.h>
#include "global.h" #include "config.h"
#include "icons.h" #include "icons.h"
#include "global.h"
#define WaitAnimationInterval 150
const uint8_t WaitAnimationInterval = 150;
void Display::init() 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: case State::WaitingForComm:
off();
break;
case Screen::WaitingForComm:
drawWaitingForComm(); drawWaitingForComm();
break; break;
case Screen::ModuleIndex: case State::Linking:
drawLinking();
break;
case State::LinkingSet:
drawModuleIndex(); drawModuleIndex();
break; break;
case State::DisplayModuleIndex:
if (currentTime - mLastStateChange >= DisplayIdleTimeout)
{
state = State::DisplayOff;
off();
} }
else
drawModuleIndex();
break;
case State::DisplayOff:
off();
break;
}
setLastDrawnState();
} }
void Display::drawWaitingForComm() void Display::drawWaitingForComm()
{ {
uint32_t currentTime = millis(); if (mLastDrawnState == State::WaitingForComm && currentTime - mLastWaiting < WaitAnimationInterval)
if (mLastScreen == Screen::WaitingForComm && currentTime - mLastWaiting < WaitAnimationInterval)
return; return;
checkOn(); checkOn();
@ -58,7 +76,6 @@ void Display::drawWaitingForComm()
if (mWaitAnimationStep > 3) if (mWaitAnimationStep > 3)
mWaitAnimationStep = 0; mWaitAnimationStep = 0;
mLastScreen = Screen::WaitingForComm;
mLastWaiting = currentTime; mLastWaiting = currentTime;
} }
@ -66,7 +83,7 @@ void Display::drawWaitingForComm()
void Display::drawModuleIndex() void Display::drawModuleIndex()
{ {
uint8_t moduleIndex = settings.getModuleIndex(); uint8_t moduleIndex = settings.getModuleIndex();
if (mLastScreen == Screen::ModuleIndex && mLastModuleIndex == moduleIndex) if (mLastDrawnState == State::DisplayModuleIndex && mLastModuleIndex == moduleIndex)
return; return;
checkOn(); checkOn();
@ -75,34 +92,61 @@ void Display::drawModuleIndex()
drawCommIcon(); drawCommIcon();
mDisplay->setFont(&FreeSans12pt7b); 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; uint8_t firstStep = (moduleIndex * 2) + 1;
mDisplay->print(firstStep); mDisplay->print(firstStep);
mDisplay->print(" - "); mDisplay->print(" - ");
mDisplay->print(firstStep + 1); mDisplay->print(firstStep + 1);
}
mDisplay->display(); 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() void Display::off()
{ {
if (mLastScreen != Screen::Blank) if (mLastDrawnState != State::DisplayOff)
{
mDisplay->ssd1306_command(SSD1306_DISPLAYOFF); mDisplay->ssd1306_command(SSD1306_DISPLAYOFF);
mLastScreen = Screen::Blank;
}
} }
void Display::checkOn() void Display::checkOn()
{ {
if (mLastScreen == Screen::Blank) if (mLastDrawnState == State::DisplayOff)
mDisplay->ssd1306_command(SSD1306_DISPLAYON); mDisplay->ssd1306_command(SSD1306_DISPLAYON);
} }
@ -120,8 +164,9 @@ void Display::drawTitle(const char* title)
void Display::drawCommIcon() 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,
mDisplay->drawBitmap(mDisplay->width() - IconCommWidth, 0, IconCommOff, IconCommWidth, IconCommHeight, 1); currentTime - comm.getPacketStartTime() <= CommIdleTimeout ? IconCommOn : IconCommOff,
IconCommWidth, IconCommHeight, 1);
} }

View File

@ -11,21 +11,15 @@
#include <Wire.h> #include <Wire.h>
#include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
#include "lib/Adafruit_SSD1306.h" #include "lib/Adafruit_SSD1306.h"
#include "state.h"
enum struct Screen: uint8_t
{
Blank,
WaitingForComm,
ModuleIndex
};
class Display class Display
{ {
private: private:
Adafruit_SSD1306* mDisplay; Adafruit_SSD1306* mDisplay;
Screen mLastScreen = Screen::Blank; State mLastDrawnState = State::DisplayOff;
uint32_t mLastStateChange;
uint8_t mLastModuleIndex; uint8_t mLastModuleIndex;
uint32_t mLastWaiting; uint32_t mLastWaiting;
uint8_t mWaitAnimationStep = 0; uint8_t mWaitAnimationStep = 0;
@ -38,10 +32,13 @@ class Display
void drawWaitingForComm(); void drawWaitingForComm();
void drawModuleIndex(); void drawModuleIndex();
void drawLinking();
void off();
void setLastDrawnState();
public: public:
void init(); void init();
void show(Screen screen); void update();
void off();
}; };
#endif #endif

View File

@ -26,4 +26,6 @@ size_t serialWrite(const byte what)
Settings settings; Settings settings;
Display display; Display display;
RS485 comm(serialRead, serialAvailable, serialWrite, 20); RS485 comm(serialRead, serialAvailable, serialWrite, 20);
PCA9685 ledDriver; PCA9685 pwmDriver;
State state = State::WaitingForComm;
uint32_t currentTime;

View File

@ -11,10 +11,13 @@
#include "lib/PCA9685.h" #include "lib/PCA9685.h"
#include "settings.h" #include "settings.h"
#include "display.h" #include "display.h"
#include "state.h"
extern Settings settings; extern Settings settings;
extern Display display; extern Display display;
extern RS485 comm; extern RS485 comm;
extern PCA9685 ledDriver; extern PCA9685 pwmDriver;
extern State state;
extern uint32_t currentTime;
#endif #endif

View File

@ -8,16 +8,7 @@
#include <Wire.h> #include <Wire.h>
#include "global.h" #include "global.h"
#include "display.h" #include "display.h"
#include "protocol.h"
enum struct State: uint8_t
{
WaitingForComm,
Connected
};
State currentState = State::WaitingForComm;
void setup() void setup()
@ -27,7 +18,7 @@ void setup()
// Set up I2C devices: the SSD1306 OLED display and PCA9685 LED PWM driver // Set up I2C devices: the SSD1306 OLED display and PCA9685 LED PWM driver
Wire.begin(); Wire.begin();
display.init(); display.init();
ledDriver.setAddress(0x40); pwmDriver.setAddress(0x40);
// At 16 Mhz we pick a baud rate with a very acceptable 0.2% error rate // 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 // Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html
@ -35,22 +26,154 @@ void setup()
comm.begin(); 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() void loop()
{ {
currentTime = millis();
if (comm.update()) 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 if (!settings.hasModuleIndex())
currentState = State::Connected; // 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: case CommandPing: handlePing(data, length); break;
display.show(Screen::WaitingForComm); case CommandDisplayModuleIndex: handleDisplayModuleIndex(data, length); break;
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: default:
display.show(Screen::ModuleIndex); if (command & MaskModuleCommand)
break; {
// 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
View 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

View File

@ -8,10 +8,6 @@
#include <EEPROM.h> #include <EEPROM.h>
const uint8_t ModuleIndexUndefined = 0xff;
// First byte is 0xAA to recognize uninitialised EEPROM // First byte is 0xAA to recognize uninitialised EEPROM
const uint8_t EEPROMHeader = 0xAA; const uint8_t EEPROMHeader = 0xAA;
const uint8_t EEPROMCurrentVersion = 1; const uint8_t EEPROMCurrentVersion = 1;
@ -40,6 +36,12 @@ void Settings::init()
} }
bool Settings::hasModuleIndex()
{
return mModuleIndex != ModuleIndexUndefined;
}
uint8_t Settings::getModuleIndex(void) uint8_t Settings::getModuleIndex(void)
{ {
return mModuleIndex; return mModuleIndex;
@ -52,5 +54,5 @@ void Settings::setModuleIndex(uint8_t index)
return; return;
mModuleIndex = index; mModuleIndex = index;
EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex); // ToDo re-enable: EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex);
} }

View File

@ -8,8 +8,9 @@
#define __settings #define __settings
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
extern const uint8_t ModuleIndexUndefined; const uint8_t ModuleIndexUndefined = 0xff;
class Settings class Settings
@ -20,6 +21,7 @@ class Settings
public: public:
void init(); void init();
bool hasModuleIndex();
uint8_t getModuleIndex(); uint8_t getModuleIndex();
void setModuleIndex(uint8_t index); void setModuleIndex(uint8_t index);
}; };

19
module/src/state.h Normal file
View 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