From 06b5a0e72029015d825e37ab8ad59dc78939e68d Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Tue, 20 Nov 2018 21:55:31 +0100 Subject: [PATCH] Implemented protocol and message handling --- module/src/config.h | 22 ++++++ module/src/display.cpp | 103 ++++++++++++++++++------- module/src/display.h | 19 ++--- module/src/global.cpp | 4 +- module/src/global.h | 5 +- module/src/main.cpp | 163 +++++++++++++++++++++++++++++++++++----- module/src/protocol.h | 149 ++++++++++++++++++++++++++++++++++++ module/src/settings.cpp | 12 +-- module/src/settings.h | 4 +- module/src/state.h | 19 +++++ 10 files changed, 432 insertions(+), 68 deletions(-) create mode 100644 module/src/config.h create mode 100644 module/src/protocol.h create mode 100644 module/src/state.h diff --git a/module/src/config.h b/module/src/config.h new file mode 100644 index 0000000..f78eaaf --- /dev/null +++ b/module/src/config.h @@ -0,0 +1,22 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#ifndef __config +#define __config + +#include + +// 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 diff --git a/module/src/display.cpp b/module/src/display.cpp index df95c31..ef775b0 100644 --- a/module/src/display.cpp +++ b/module/src/display.cpp @@ -6,10 +6,12 @@ */ #include "display.h" #include -#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 - uint8_t firstStep = (moduleIndex * 2) + 1; - mDisplay->print(firstStep); - mDisplay->print(" - "); - mDisplay->print(firstStep + 1); + 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); } diff --git a/module/src/display.h b/module/src/display.h index a4c0b5b..a292cbd 100644 --- a/module/src/display.h +++ b/module/src/display.h @@ -11,21 +11,15 @@ #include #include #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 diff --git a/module/src/global.cpp b/module/src/global.cpp index 1c827ce..00478cc 100644 --- a/module/src/global.cpp +++ b/module/src/global.cpp @@ -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 diff --git a/module/src/global.h b/module/src/global.h index 7771e52..d96bd58 100644 --- a/module/src/global.h +++ b/module/src/global.h @@ -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 diff --git a/module/src/main.cpp b/module/src/main.cpp index c6c19f6..663c736 100644 --- a/module/src/main.cpp +++ b/module/src/main.cpp @@ -8,16 +8,7 @@ #include #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(data)); data += 2; + uint16_t value2 = *(reinterpret_cast(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)); +} diff --git a/module/src/protocol.h b/module/src/protocol.h new file mode 100644 index 0000000..df8012f --- /dev/null +++ b/module/src/protocol.h @@ -0,0 +1,149 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#ifndef __protocol +#define __protocol + +#include + + +/* + * 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 diff --git a/module/src/settings.cpp b/module/src/settings.cpp index 82cf595..af8d072 100644 --- a/module/src/settings.cpp +++ b/module/src/settings.cpp @@ -8,10 +8,6 @@ #include -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 diff --git a/module/src/settings.h b/module/src/settings.h index ce95509..a86866d 100644 --- a/module/src/settings.h +++ b/module/src/settings.h @@ -8,8 +8,9 @@ #define __settings #include +#include -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); }; diff --git a/module/src/state.h b/module/src/state.h new file mode 100644 index 0000000..516a657 --- /dev/null +++ b/module/src/state.h @@ -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 \ No newline at end of file