Commit 06b5a0e7 authored by Mark van Renswoude's avatar Mark van Renswoude

Implemented protocol and message handling

parent a5eab142
/*
* 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
\ No newline at end of file
......@@ -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();
case State::WaitingForComm:
drawWaitingForComm();
break;
case Screen::WaitingForComm:
drawWaitingForComm();
case State::Linking:
drawLinking();
break;
case Screen::ModuleIndex:
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);
if (moduleIndex == ModuleIndexUndefined)
{
mDisplay->print("Not set");
}
else
{
uint8_t firstStep = (moduleIndex * 2) + 1;
mDisplay->print(firstStep);
mDisplay->print(" - ");
mDisplay->print(firstStep + 1);
}
// ToDo show numbers based on module index
uint8_t firstStep = (moduleIndex * 2) + 1;
mDisplay->print(firstStep);
mDisplay->print(" - ");
mDisplay->print(firstStep + 1);
mDisplay->display();
}
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();
}
mLastScreen = Screen::ModuleIndex;
mLastModuleIndex = moduleIndex;
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
\ No newline at end of file
......@@ -26,4 +26,6 @@ size_t serialWrite(const byte what)
Settings settings;
Display display;
RS485 comm(serialRead, serialAvailable, serialWrite, 20);
PCA9685 ledDriver;
\ No newline at end of file
PCA9685 pwmDriver;
State state = State::WaitingForComm;
uint32_t currentTime;
\ No newline at end of file
......@@ -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
\ No newline at end of file
......@@ -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;
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;
}
case State::Connected:
display.show(Screen::ModuleIndex);
break;
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));
}
/*
* 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
\ No newline at end of file
......@@ -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);
}
\ No newline at end of file
......@@ -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);
};
......
/*
* 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
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment