From da09f404430108c5c5f5b2bf6dce8fd98f8f5bd3 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Thu, 22 Nov 2018 23:31:08 +0100 Subject: [PATCH] Added mock master Fixed communication bugs --- module/mockmaster/mockmaster.ino | 263 +++++++++++++++++++++++++++++++ module/mockmaster/protocol.h | 155 ++++++++++++++++++ module/src/config.h | 8 +- module/src/display.cpp | 15 +- module/src/main.cpp | 43 ++++- module/src/protocol.h | 22 ++- module/src/settings.cpp | 2 +- module/src/state.h | 1 + 8 files changed, 494 insertions(+), 15 deletions(-) create mode 100644 module/mockmaster/mockmaster.ino create mode 100644 module/mockmaster/protocol.h diff --git a/module/mockmaster/mockmaster.ino b/module/mockmaster/mockmaster.ino new file mode 100644 index 0000000..529615f --- /dev/null +++ b/module/mockmaster/mockmaster.ino @@ -0,0 +1,263 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude + * + * https://git.x2software.net/pub/Stairs +*/ +#include +#include +#define SERIAL_COMMANDS_DEBUG +#include +#include "protocol.h" + + +const uint8_t PinRS485Receive = 2; +const uint8_t PinRS485Transmit = 3; +const uint8_t PinRS485WriteEnable = 4; + +const uint32_t RS485BaudRate = 76800; + + +/* + * RS485 communication + */ +SoftwareSerial rs485(PinRS485Receive, PinRS485Transmit); + +size_t doWrite(const byte what) +{ + return rs485.write (what); +} + +int doAvailable() +{ + return rs485.available (); +} + +int doRead() +{ + return rs485.read (); +} + +RS485 comm(doRead, doAvailable, doWrite, 20); + + + +/* + * Serial communication (user input) + */ +SerialCommand cmdPing("ping", handlePingCommand); +SerialCommand cmdDisplay("display", handleDisplayCommand); +SerialCommand cmdLink("link", handleLinkCommand); +SerialCommand cmdLinkAssign("assign", handleLinkAssignCommand); +SerialCommand cmdLinkStop("stop", handleLinkStopCommand); +SerialCommand cmdSetPWM("setPWM", handlesetPWMCommand); +SerialCommand cmdGetSensors("getSensors", handlegetSensorsCommand); + + +char commandBuffer[32]; +SerialCommands serialCommands(&Serial, commandBuffer, sizeof(commandBuffer), "\r\n", " "); + +void cmdUnrecognized(SerialCommands* sender, const char* cmd) +{ + sender->GetSerial()->print("ERROR: Unrecognized command ["); + sender->GetSerial()->print(cmd); + sender->GetSerial()->println("]"); +} + + +bool inLink = false; + + +void showCommands() +{ + Serial.println(""); + Serial.println("Available commands:"); + Serial.println(" ping [moduleIndex]"); + Serial.println(" display"); + Serial.println(" link"); + Serial.println(" setPWM [moduleIndex] [flags] [value1] [value2]"); + Serial.println(" getSensors [moduleIndex]"); + Serial.println(""); +} + +void setup() +{ + pinMode(PinRS485WriteEnable, OUTPUT); + + rs485.begin(RS485BaudRate); + comm.begin(); + + Serial.begin(115200); + serialCommands.AddCommand(&cmdPing); + serialCommands.AddCommand(&cmdDisplay); + serialCommands.AddCommand(&cmdLink); + serialCommands.AddCommand(&cmdLinkAssign); + serialCommands.AddCommand(&cmdLinkStop); + serialCommands.AddCommand(&cmdSetPWM); + serialCommands.AddCommand(&cmdGetSensors); + serialCommands.SetDefaultHandler(&cmdUnrecognized); + + Serial.println("Stairs module Mock Master ready"); + showCommands(); +} + + +void loop () +{ + serialCommands.ReadSerial(); + + if (comm.update()) + handleCommMessage(); +} + + +void sendCommMessage(const byte* data, size_t len) +{ + digitalWrite(PinRS485WriteEnable, HIGH); + comm.sendMsg(data, len); + digitalWrite(PinRS485WriteEnable, LOW); +} + + +void handlePingCommand(SerialCommands* sender) +{ + if (inLink) return; + + int moduleIndex = atoi(sender->Next()); + + sender->GetSerial()->println("> Ping"); + const byte msg[] = { CommandPing, moduleIndex }; + sendCommMessage(msg, sizeof(msg)); +} + + +void handleDisplayCommand(SerialCommands* sender) +{ + if (inLink) return; + + sender->GetSerial()->println("> DisplayModuleIndex"); + const byte msg[] = { CommandDisplayModuleIndex }; + sendCommMessage(msg, sizeof(msg)); +} + + +void handleLinkCommand(SerialCommands* sender) +{ + if (inLink) return; + + sender->GetSerial()->println("> Link"); + const byte msg[] = { CommandStartLink }; + sendCommMessage(msg, sizeof(msg)); + + sender->GetSerial()->println(""); + sender->GetSerial()->println("Available commands:"); + sender->GetSerial()->println(" assign [moduleIndex]"); + sender->GetSerial()->println(" stop"); + sender->GetSerial()->println(""); + + inLink = true; +} + + +void handleLinkAssignCommand(SerialCommands* sender) +{ + if (!inLink) return; + + int moduleIndex = atoi(sender->Next()); + + sender->GetSerial()->println("> LinkRequest response"); + const byte msg[] = { ResponseRequestLink, moduleIndex }; + sendCommMessage(msg, sizeof(msg)); +} + + +void handleLinkStopCommand(SerialCommands* sender) +{ + if (!inLink) return; + + sender->GetSerial()->println("> StopLink"); + const byte msg[] = { CommandStopLink }; + sendCommMessage(msg, sizeof(msg)); + + inLink = false; + showCommands(); +} + + +void handlesetPWMCommand(SerialCommands* sender) +{ + if (inLink) return; + + int flags = atoi(sender->Next()); + int value1 = atoi(sender->Next()); + int value2 = atoi(sender->Next()); + + sender->GetSerial()->println("> Ping"); + const byte msg[] = { CommandSetPWM, (uint8_t)flags, (uint16_t)value1, (uint16_t)value2 }; + sendCommMessage(msg, sizeof(msg)); +} + + +void handlegetSensorsCommand(SerialCommands* sender) +{ + if (inLink) return; + + int moduleIndex = atoi(sender->Next()); + + sender->GetSerial()->println("> GetSensors"); + const byte msg[] = { CommandGetSensors, moduleIndex }; + sendCommMessage(msg, sizeof(msg)); +} + + +void handleCommMessage() +{ + uint8_t* data = comm.getData(); + uint8_t length = comm.getLength(); + + if (length == 0) + return; + + uint8_t command = *data; data++; length--; + switch (command) + { + case ResponsePing: + if (length > 0) + Serial.println("< Ping response, module index = " + String(*data)); + else + Serial.println("< Ping response, module index = "); + + break; + + case CommandRequestLink: + Serial.println("< Request link"); + break; + + case ResponseSetPWM: + Serial.println("< Set PWM response, module index = " + String(*data)); + break; + + case ResponseGetSensors: + Serial.println("< Get sensors response, module index = " + String(*data)); data++; length--; + if (length > 0) + { + Serial.println(" Sensor 1 value: " + String(*data)); data++; length--; + } + else + Serial.println(" Sensor 1 value: "); + + if (length > 0) + { + Serial.println(" Sensor 2 value: " + String(*data)); data++; length--; + } + else + Serial.println(" Sensor 2 value: "); + + break; + + case ResponseUhmWhat: + Serial.println("< Uhm, what? module index = " + String(*data)); + break; + } +} + diff --git a/module/mockmaster/protocol.h b/module/mockmaster/protocol.h new file mode 100644 index 0000000..ec6a91a --- /dev/null +++ b/module/mockmaster/protocol.h @@ -0,0 +1,155 @@ +/* + * 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 MaskMessageType = 0b11000000; + +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 BasePing = 0x01; +const uint8_t CommandPing = BasePing | MaskModuleCommand; +const uint8_t ResponsePing = BasePing | 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 BaseRequestLink = 0x11; +const uint8_t CommandRequestLink = BaseRequestLink | MaskBroadcastCommand; +const uint8_t ResponseRequestLink = BaseRequestLink | 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 BaseSetPWM = 0x20; +const uint8_t CommandSetPWM = BaseSetPWM | MaskModuleCommand; +const uint8_t ResponseSetPWM = BaseSetPWM | 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 BaseGetSensors = 0x30; +const uint8_t CommandGetSensors = BaseGetSensors | MaskModuleCommand; +const uint8_t ResponseGetSensors = BaseGetSensors | MaskResponse; + +#endif diff --git a/module/src/config.h b/module/src/config.h index 4d4e1ca..6834630 100644 --- a/module/src/config.h +++ b/module/src/config.h @@ -15,7 +15,7 @@ // Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html const uint32_t CommBaudRate = 76800; -// Pin connected to the MAX485's Receiver and Driver Output Enable pins +// Arduino pin number connected to the MAX485's Receiver and Driver Output Enable pins const uint8_t CommWriteEnablePin = 2; // How long the display should stay on once it's idle and showing the @@ -28,4 +28,10 @@ const uint32_t DisplayIdleTimeout = 5000; // so no need to panic immediately. const uint32_t CommIdleTimeout = 1000; +// Arduino pin number connected to the push button. +const uint8_t PinButton = 8; + +// Debounce time for the push button. Since it is not used for fast operations, this can be relatively high. +const uint32_t ButtonDebounceTime = 500; + #endif \ No newline at end of file diff --git a/module/src/display.cpp b/module/src/display.cpp index ef775b0..3a002c1 100644 --- a/module/src/display.cpp +++ b/module/src/display.cpp @@ -30,6 +30,7 @@ void Display::update() break; case State::Linking: + case State::LinkingRequest: drawLinking(); break; @@ -38,7 +39,7 @@ void Display::update() break; case State::DisplayModuleIndex: - if (currentTime - mLastStateChange >= DisplayIdleTimeout) + if (mLastDrawnState == state && currentTime - mLastStateChange >= DisplayIdleTimeout) { state = State::DisplayOff; off(); @@ -140,14 +141,26 @@ void Display::setLastDrawnState() void Display::off() { if (mLastDrawnState != State::DisplayOff) + { + // I've had trouble waking the display up again, so just clear it for now mDisplay->ssd1306_command(SSD1306_DISPLAYOFF); + mDisplay->ssd1306_command(SSD1306_CHARGEPUMP); + mDisplay->ssd1306_command(0x10); // disable charge pump + //mDisplay->clearDisplay(); + //mDisplay->display(); + } } void Display::checkOn() { if (mLastDrawnState == State::DisplayOff) + { + mDisplay->ssd1306_command(SSD1306_CHARGEPUMP); + mDisplay->ssd1306_command(0x14); // enable charge pump + mDisplay->ssd1306_command(SSD1306_DISPLAYON); + } } diff --git a/module/src/main.cpp b/module/src/main.cpp index 2245662..f20a5ca 100644 --- a/module/src/main.cpp +++ b/module/src/main.cpp @@ -26,6 +26,8 @@ void setup() Serial.begin(CommBaudRate); pinMode(CommWriteEnablePin, OUTPUT); comm.begin(); + + pinMode(PinButton, INPUT_PULLUP); } @@ -40,6 +42,8 @@ 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 checkButtonPress(); + void loop() { @@ -48,6 +52,7 @@ void loop() if (comm.update()) handleCommMessage(); + checkButtonPress(); display.update(); } @@ -57,6 +62,18 @@ void sendCommMessage(const uint8_t* data, uint8_t size) { digitalWrite(CommWriteEnablePin, HIGH); comm.sendMsg(data, size); + + + // Wait for the hardware buffer to clear before turning + // off the write enable pin, or we'll cut off the message too early + // Straight from: http://www.gammon.com.au/forum/?id=11428 + + while (!(UCSR0A & (1 << UDRE0))) // Wait for empty transmit buffer + UCSR0A |= 1 << TXC0; // mark transmission not complete + + while (!(UCSR0A & (1 << TXC0))); // Wait for the transmission to complete + + digitalWrite(CommWriteEnablePin, LOW); } @@ -75,7 +92,7 @@ void handleCommMessage() uint8_t command = *data; data++; length--; uint8_t moduleIndex = ModuleIndexUndefined; - if (command & MaskModuleCommand) + if ((command & MaskMessageType) == MaskModuleCommand) { if (!settings.hasModuleIndex()) // We're not linked yet @@ -99,7 +116,7 @@ void handleCommMessage() case CommandGetSensors: handleGetSensors(data, length); break; default: - if (command & MaskModuleCommand) + if ((command & MaskMessageType) == MaskModuleCommand) { // Sender expects a response from us const uint8_t msg[] = { ResponseUhmWhat, moduleIndex }; @@ -134,7 +151,7 @@ void handleRequestLinkResponse(uint8_t* data, uint8_t length) if (length == 0) return; - if (state != State::Linking) + if (state != State::LinkingRequest) return; settings.setModuleIndex(*data); @@ -144,7 +161,7 @@ void handleRequestLinkResponse(uint8_t* data, uint8_t length) void handleStopLink(uint8_t* data, uint8_t length) { - if (state == State::Linking || state == State::LinkingSet) + if (state == State::Linking || state == State::LinkingRequest || state == State::LinkingSet) state = State::DisplayModuleIndex; } @@ -187,3 +204,21 @@ void handleGetSensors(uint8_t* data, uint8_t length) const uint8_t msg[] = { ResponseSetPWM, settings.getModuleIndex(), sensor1, sensor2 }; sendCommMessage(msg, sizeof(msg)); } + + +uint32_t lastButtonPress = 0; + +void checkButtonPress() +{ + if ((state == State::Linking || state == State::LinkingRequest) && + digitalRead(PinButton) == HIGH && + currentTime - lastButtonPress >= ButtonDebounceTime) + { + state = State::LinkingRequest; + + const uint8_t msg[] = { CommandRequestLink }; + sendCommMessage(msg, sizeof(msg)); + + lastButtonPress = currentTime; + } +} \ No newline at end of file diff --git a/module/src/protocol.h b/module/src/protocol.h index df8012f..c9cb449 100644 --- a/module/src/protocol.h +++ b/module/src/protocol.h @@ -18,6 +18,8 @@ * MaskModuleCommand: commands which do expect a command. respond with ResponseUhmWhat to satisfy the response requirement. * MaskResponse: responses to commands */ +const uint8_t MaskMessageType = 0b11000000; + const uint8_t MaskBroadcastCommand = 0b10000000; const uint8_t MaskModuleCommand = 0b11000000; const uint8_t MaskResponse = 0b01000000; @@ -38,8 +40,9 @@ const uint8_t ResponseUhmWhat = 0x00 | MaskResponse; * [0] ResponsePing * [1] Module index */ -const uint8_t CommandPing = 0x01 | MaskModuleCommand; -const uint8_t ResponsePing = CommandPing | MaskResponse; +const uint8_t BasePing = 0x01; +const uint8_t CommandPing = BasePing | MaskModuleCommand; +const uint8_t ResponsePing = BasePing | MaskResponse; /* @@ -88,8 +91,9 @@ const uint8_t CommandStartLink = 0x10 | MaskBroadcastCommand; * [0] ResponseRequestLink * [1] New module index */ -const uint8_t CommandRequestLink = 0x11 | MaskBroadcastCommand; -const uint8_t ResponseRequestLink = CommandRequestLink | MaskResponse; +const uint8_t BaseRequestLink = 0x11; +const uint8_t CommandRequestLink = BaseRequestLink | MaskBroadcastCommand; +const uint8_t ResponseRequestLink = BaseRequestLink | MaskResponse; /* @@ -120,8 +124,9 @@ const uint8_t CommandStopLink = 0x12 | MaskBroadcastCommand; * [0] ResponseSetPWM * [1] Module index */ -const uint8_t CommandSetPWM = 0x20 | MaskModuleCommand; -const uint8_t ResponseSetPWM = CommandSetPWM | MaskResponse; +const uint8_t BaseSetPWM = 0x20; +const uint8_t CommandSetPWM = BaseSetPWM | MaskModuleCommand; +const uint8_t ResponseSetPWM = BaseSetPWM | MaskResponse; // If included, the on-board verification LEDs should light up with // the specified PWM values as well. Otherwise they should be off. @@ -143,7 +148,8 @@ const uint8_t SetPWMFlagModuleLEDs = 0x01; * [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; +const uint8_t BaseGetSensors = 0x30; +const uint8_t CommandGetSensors = BaseGetSensors | MaskModuleCommand; +const uint8_t ResponseGetSensors = BaseGetSensors | MaskResponse; #endif \ No newline at end of file diff --git a/module/src/settings.cpp b/module/src/settings.cpp index af8d072..2ec2aa7 100644 --- a/module/src/settings.cpp +++ b/module/src/settings.cpp @@ -54,5 +54,5 @@ void Settings::setModuleIndex(uint8_t index) return; mModuleIndex = index; - // ToDo re-enable: EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex); + EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex); } \ No newline at end of file diff --git a/module/src/state.h b/module/src/state.h index 516a657..f913476 100644 --- a/module/src/state.h +++ b/module/src/state.h @@ -11,6 +11,7 @@ enum struct State: uint8_t { WaitingForComm, Linking, + LinkingRequest, LinkingSet, DisplayModuleIndex, DisplayOff