Added mock master

Fixed communication bugs
This commit is contained in:
Mark van Renswoude 2018-11-22 23:31:08 +01:00
parent ff315ae20e
commit da09f40443
8 changed files with 494 additions and 15 deletions

View File

@ -0,0 +1,263 @@
/*
* Stairs lighting
* Copyright 2017 (c) Mark van Renswoude
*
* https://git.x2software.net/pub/Stairs
*/
#include <RS485_non_blocking.h>
#include <SoftwareSerial.h>
#define SERIAL_COMMANDS_DEBUG
#include <SerialCommands.h>
#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 = <missing>");
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: <missing>");
if (length > 0)
{
Serial.println(" Sensor 2 value: " + String(*data)); data++; length--;
}
else
Serial.println(" Sensor 2 value: <missing>");
break;
case ResponseUhmWhat:
Serial.println("< Uhm, what? module index = " + String(*data));
break;
}
}

View File

@ -0,0 +1,155 @@
/*
* 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 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

View File

@ -15,7 +15,7 @@
// Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html // Source: http://www.robotroom.com/Asynchronous-Serial-Communication-2.html
const uint32_t CommBaudRate = 76800; 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; const uint8_t CommWriteEnablePin = 2;
// How long the display should stay on once it's idle and showing the // 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. // so no need to panic immediately.
const uint32_t CommIdleTimeout = 1000; 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 #endif

View File

@ -30,6 +30,7 @@ void Display::update()
break; break;
case State::Linking: case State::Linking:
case State::LinkingRequest:
drawLinking(); drawLinking();
break; break;
@ -38,7 +39,7 @@ void Display::update()
break; break;
case State::DisplayModuleIndex: case State::DisplayModuleIndex:
if (currentTime - mLastStateChange >= DisplayIdleTimeout) if (mLastDrawnState == state && currentTime - mLastStateChange >= DisplayIdleTimeout)
{ {
state = State::DisplayOff; state = State::DisplayOff;
off(); off();
@ -140,14 +141,26 @@ void Display::setLastDrawnState()
void Display::off() void Display::off()
{ {
if (mLastDrawnState != State::DisplayOff) 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_DISPLAYOFF);
mDisplay->ssd1306_command(SSD1306_CHARGEPUMP);
mDisplay->ssd1306_command(0x10); // disable charge pump
//mDisplay->clearDisplay();
//mDisplay->display();
}
} }
void Display::checkOn() void Display::checkOn()
{ {
if (mLastDrawnState == State::DisplayOff) if (mLastDrawnState == State::DisplayOff)
{
mDisplay->ssd1306_command(SSD1306_CHARGEPUMP);
mDisplay->ssd1306_command(0x14); // enable charge pump
mDisplay->ssd1306_command(SSD1306_DISPLAYON); mDisplay->ssd1306_command(SSD1306_DISPLAYON);
}
} }

View File

@ -26,6 +26,8 @@ void setup()
Serial.begin(CommBaudRate); Serial.begin(CommBaudRate);
pinMode(CommWriteEnablePin, OUTPUT); pinMode(CommWriteEnablePin, OUTPUT);
comm.begin(); 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 handleSetPWM(uint8_t* data, uint8_t length);
void handleGetSensors(uint8_t* data, uint8_t length); void handleGetSensors(uint8_t* data, uint8_t length);
void checkButtonPress();
void loop() void loop()
{ {
@ -48,6 +52,7 @@ void loop()
if (comm.update()) if (comm.update())
handleCommMessage(); handleCommMessage();
checkButtonPress();
display.update(); display.update();
} }
@ -57,6 +62,18 @@ void sendCommMessage(const uint8_t* data, uint8_t size)
{ {
digitalWrite(CommWriteEnablePin, HIGH); digitalWrite(CommWriteEnablePin, HIGH);
comm.sendMsg(data, size); 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); digitalWrite(CommWriteEnablePin, LOW);
} }
@ -75,7 +92,7 @@ void handleCommMessage()
uint8_t command = *data; data++; length--; uint8_t command = *data; data++; length--;
uint8_t moduleIndex = ModuleIndexUndefined; uint8_t moduleIndex = ModuleIndexUndefined;
if (command & MaskModuleCommand) if ((command & MaskMessageType) == MaskModuleCommand)
{ {
if (!settings.hasModuleIndex()) if (!settings.hasModuleIndex())
// We're not linked yet // We're not linked yet
@ -99,7 +116,7 @@ void handleCommMessage()
case CommandGetSensors: handleGetSensors(data, length); break; case CommandGetSensors: handleGetSensors(data, length); break;
default: default:
if (command & MaskModuleCommand) if ((command & MaskMessageType) == MaskModuleCommand)
{ {
// Sender expects a response from us // Sender expects a response from us
const uint8_t msg[] = { ResponseUhmWhat, moduleIndex }; const uint8_t msg[] = { ResponseUhmWhat, moduleIndex };
@ -134,7 +151,7 @@ void handleRequestLinkResponse(uint8_t* data, uint8_t length)
if (length == 0) if (length == 0)
return; return;
if (state != State::Linking) if (state != State::LinkingRequest)
return; return;
settings.setModuleIndex(*data); settings.setModuleIndex(*data);
@ -144,7 +161,7 @@ void handleRequestLinkResponse(uint8_t* data, uint8_t length)
void handleStopLink(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; state = State::DisplayModuleIndex;
} }
@ -187,3 +204,21 @@ void handleGetSensors(uint8_t* data, uint8_t length)
const uint8_t msg[] = { ResponseSetPWM, settings.getModuleIndex(), sensor1, sensor2 }; const uint8_t msg[] = { ResponseSetPWM, settings.getModuleIndex(), sensor1, sensor2 };
sendCommMessage(msg, sizeof(msg)); 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;
}
}

View File

@ -18,6 +18,8 @@
* MaskModuleCommand: commands which do expect a command. respond with ResponseUhmWhat to satisfy the response requirement. * MaskModuleCommand: commands which do expect a command. respond with ResponseUhmWhat to satisfy the response requirement.
* MaskResponse: responses to commands * MaskResponse: responses to commands
*/ */
const uint8_t MaskMessageType = 0b11000000;
const uint8_t MaskBroadcastCommand = 0b10000000; const uint8_t MaskBroadcastCommand = 0b10000000;
const uint8_t MaskModuleCommand = 0b11000000; const uint8_t MaskModuleCommand = 0b11000000;
const uint8_t MaskResponse = 0b01000000; const uint8_t MaskResponse = 0b01000000;
@ -38,8 +40,9 @@ const uint8_t ResponseUhmWhat = 0x00 | MaskResponse;
* [0] ResponsePing * [0] ResponsePing
* [1] Module index * [1] Module index
*/ */
const uint8_t CommandPing = 0x01 | MaskModuleCommand; const uint8_t BasePing = 0x01;
const uint8_t ResponsePing = CommandPing | MaskResponse; const uint8_t CommandPing = BasePing | MaskModuleCommand;
const uint8_t ResponsePing = BasePing | MaskResponse;
/* /*
@ -88,8 +91,9 @@ const uint8_t CommandStartLink = 0x10 | MaskBroadcastCommand;
* [0] ResponseRequestLink * [0] ResponseRequestLink
* [1] New module index * [1] New module index
*/ */
const uint8_t CommandRequestLink = 0x11 | MaskBroadcastCommand; const uint8_t BaseRequestLink = 0x11;
const uint8_t ResponseRequestLink = CommandRequestLink | MaskResponse; const uint8_t CommandRequestLink = BaseRequestLink | MaskBroadcastCommand;
const uint8_t ResponseRequestLink = BaseRequestLink | MaskResponse;
/* /*
@ -120,8 +124,9 @@ const uint8_t CommandStopLink = 0x12 | MaskBroadcastCommand;
* [0] ResponseSetPWM * [0] ResponseSetPWM
* [1] Module index * [1] Module index
*/ */
const uint8_t CommandSetPWM = 0x20 | MaskModuleCommand; const uint8_t BaseSetPWM = 0x20;
const uint8_t ResponseSetPWM = CommandSetPWM | MaskResponse; const uint8_t CommandSetPWM = BaseSetPWM | MaskModuleCommand;
const uint8_t ResponseSetPWM = BaseSetPWM | MaskResponse;
// If included, the on-board verification LEDs should light up with // If included, the on-board verification LEDs should light up with
// the specified PWM values as well. Otherwise they should be off. // 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 * [2] Analog (0-255) or digital (0, 255) value for sensor 1
* [3] Analog (0-255) or digital (0, 255) value for sensor 2 * [3] Analog (0-255) or digital (0, 255) value for sensor 2
*/ */
const uint8_t CommandGetSensors = 0x30 | MaskModuleCommand; const uint8_t BaseGetSensors = 0x30;
const uint8_t ResponseGetSensors = CommandGetSensors | MaskResponse; const uint8_t CommandGetSensors = BaseGetSensors | MaskModuleCommand;
const uint8_t ResponseGetSensors = BaseGetSensors | MaskResponse;
#endif #endif

View File

@ -54,5 +54,5 @@ void Settings::setModuleIndex(uint8_t index)
return; return;
mModuleIndex = index; mModuleIndex = index;
// ToDo re-enable: EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex); EEPROM.put(EEPROMAddressModuleIndex, mModuleIndex);
} }

View File

@ -11,6 +11,7 @@ enum struct State: uint8_t
{ {
WaitingForComm, WaitingForComm,
Linking, Linking,
LinkingRequest,
LinkingSet, LinkingSet,
DisplayModuleIndex, DisplayModuleIndex,
DisplayOff DisplayOff