Skip to content

Commit

Permalink
Merge pull request #279 from faradaym/chat
Browse files Browse the repository at this point in the history
Public and Private Chat Feature Between Ducks
  • Loading branch information
nfeuer committed Jun 1, 2022
2 parents 163c3b6 + c77a696 commit 51d858e
Show file tree
Hide file tree
Showing 16 changed files with 831 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -49,7 +49,7 @@ This project is licensed under the Apache 2 License - see the [LICENSE](LICENSE)

## Version

v3.2.5
v3.3.5



Expand Down
2 changes: 1 addition & 1 deletion library.json
Expand Up @@ -8,7 +8,7 @@
"email": "info@project-owl.com",
"url": "https://www.project-owl.com"
},
"version": "3.2.5",
"version": "3.3.5",
"repository":
{
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion library.properties
@@ -1,5 +1,5 @@
name=ClusterDuck Protocol
version=3.2.5
version=3.3.5
author=Project OWL <info@project-owl.com>
maintainer=Project OWL <info@project-owl.com>
sentence=Mesh communication protocol.
Expand Down
10 changes: 10 additions & 0 deletions src/CdpPacket.h
Expand Up @@ -96,6 +96,10 @@ enum topics {
health = 0x15,
// Send duck commands
dcmd = 0x16,
//global chat message
gchat = 0x17,
//private chat message
pchat = 0x18,
// MQ7 Gas Sensor
mq7 = 0xEF,
// GP2Y Dust Sensor
Expand Down Expand Up @@ -203,6 +207,12 @@ class CdpPacket {
return duckutils::convertToHex(path.data(), path.size());
}

std::vector<byte> converToBuffer(){
std::vector<byte> sendBuffer;
sendBuffer.insert(sendBuffer.end(), sduid.begin(), sduid.end());
//sendBuffer.pushback for topic
return sendBuffer;
}
/**
* @brief Resets the cdp packet and underlying byte buffers.
*
Expand Down
169 changes: 158 additions & 11 deletions src/DuckNet.cpp
Expand Up @@ -12,6 +12,9 @@ DuckNet::DuckNet(Duck* duckIn):
//when initializing the buffer, the buffer created will be one larger than the provided size
//one slot in the buffer is used as a waste slot
CircularBuffer messageBuffer = CircularBuffer(5);
CircularBuffer chatBuffer = CircularBuffer(20);
std::map<std::string, CircularBuffer*> chatHistories;
std::string duckSession = duckutils::toString(BROADCAST_DUID).c_str();

#ifndef CDPCFG_WIFI_NONE
IPAddress apIP(CDPCFG_AP_IP1, CDPCFG_AP_IP2, CDPCFG_AP_IP3, CDPCFG_AP_IP4);
Expand Down Expand Up @@ -74,40 +77,66 @@ String DuckNet::createMuidResponseJson(muidStatus status) {
return "{\"status\":\"" + statusStr + "\", \"message\":\"" + message + "\"}";
}

void DuckNet::addMessageToBuffer(CdpPacket message)
void DuckNet::addToMessageBoardBuffer(CdpPacket message)
{
messageBuffer.push(message);
events.send("refresh" ,"refreshPage",millis());
}
std::string DuckNet::retrieveMessageHistory(){
int tail = messageBuffer.getTail();

void DuckNet::addToChatBuffer(CdpPacket message)
{
chatBuffer.push(message);
events.send("refresh" ,"refreshPage",millis());
}

void DuckNet::addToPrivateChatBuffer(CdpPacket message, std::string chatSession)
{
chatHistories.find(chatSession)->second->push(message);
events.send("refresh" ,"refreshPage",millis());
}

void DuckNet::createPrivateHistory(std::string session)
{
if(chatHistories.find(session) == chatHistories.end()){
CircularBuffer* privateChatBuffer = new CircularBuffer(20);
if(chatHistories.size() >= 3){
delete chatHistories.begin()->second;
chatHistories.erase(chatHistories.begin());
}
chatHistories.insert({session, privateChatBuffer});
}
}
std::string DuckNet::retrieveMessageHistory(CircularBuffer* buffer)
{
int tail = buffer->getTail();
std::string json = "{\"posts\":[";
bool firstMessage = true;

while(tail != messageBuffer.getHead()){
while(tail != buffer->getHead()){
if(firstMessage){
firstMessage = false;
} else{
json = json + ", ";
}

CdpPacket packet = messageBuffer.getMessage(tail);
CdpPacket packet = buffer->getMessage(tail);
unsigned long messageAge = millis() - packet.timeReceived;
std::string messageAgeString = String(messageAge).c_str();
std::string messageBody(packet.data.begin(),packet.data.end());
std::string sduid(packet.sduid.begin(), packet.sduid.end());

json = json + "{\"title\":\"PLACEHOLDER TITLE\", \"body\":\"" + messageBody + "\" , \"messageAge\":\"" + messageAgeString + "\"}";
json = json + "{\"sduid\":\"" + sduid + "\" , \"title\":\"PLACEHOLDER TITLE\", \"body\":\"" + messageBody + "\", \"messageAge\":\"" + messageAgeString + "\"}";

tail++;
if(tail == messageBuffer.getBufferEnd()){
if(tail == buffer->getBufferEnd()){
tail = 0;
}
}
json = json + "]}";
Serial.print(json.c_str());
return json;

}

int DuckNet::setupWebServer(bool createCaptivePortal, String html) {
loginfo("Setting up Web Server");
events.onConnect([](AsyncEventSourceClient *client){
Expand Down Expand Up @@ -135,6 +164,15 @@ int DuckNet::setupWebServer(bool createCaptivePortal, String html) {
webServer.on("/message-board", HTTP_GET, [&](AsyncWebServerRequest* request) {
request->send(200, "text/html", message_board);
});
webServer.on("/chat", HTTP_GET, [&](AsyncWebServerRequest* request) {
request->send(200, "text/html", chat_page);
});
webServer.on("/new-private-chat", HTTP_GET, [&](AsyncWebServerRequest* request) {
request->send(200, "text/html", private_chat_prompt);
});
webServer.on("/private-chat", HTTP_GET, [&](AsyncWebServerRequest* request) {
request->send(200, "text/html", private_chat_page);
});

webServer.on("/main", HTTP_GET, [&](AsyncWebServerRequest* request) {
request->send(200, "text/html", MAIN_page);
Expand Down Expand Up @@ -297,9 +335,118 @@ int DuckNet::setupWebServer(bool createCaptivePortal, String html) {
});

webServer.on("/messageHistory", HTTP_GET, [&](AsyncWebServerRequest* request){
std::string response = DuckNet::retrieveMessageHistory();
const char *cstr = response.c_str();
request->send(200, "text/json", cstr);
std::string response = DuckNet::retrieveMessageHistory(&messageBuffer);
const char* res = response.c_str();
request->send(200, "text/json", res);
});

webServer.on("/chatHistory", HTTP_GET, [&](AsyncWebServerRequest* request){
std::string response = DuckNet::retrieveMessageHistory(&chatBuffer);
const char* res = response.c_str();
request->send(200, "text/json", res);
});
webServer.on("/privateChatHistory", HTTP_GET, [&](AsyncWebServerRequest* request){
if(chatHistories.find(duckSession) != chatHistories.end()){
CircularBuffer* history = chatHistories[duckSession];
std::string privateHistory = DuckNet::retrieveMessageHistory(history);
request->send(200, "text/json", privateHistory.c_str());
} else{
request->send(500, "text/html", "could not retrieve chat history");
}
});
webServer.on("/privateChatHistories", HTTP_GET, [&](AsyncWebServerRequest* request){
std::string json = "{\"histories\":[";
int keyNum = 0;
for(const auto &myPair : chatHistories) {
if(keyNum != 0){
json = json + ", ";
}
json = json + "\"" + myPair.first + "\"";
keyNum++;
}
json = json + "]}";


request->send(200, "text/json", String(json.c_str()));
});

webServer.on("/chatSubmit.json", HTTP_POST, [&](AsyncWebServerRequest* request) {
int err = DUCK_ERR_NONE;

std::vector<byte> message;
String clientId = "";

AsyncWebParameter* p = request->getParam(0);
std::string msg = p->value().c_str();
message.insert(message.end(), msg.begin(), msg.end());

std::vector<byte> muid;
std::vector<byte> session;
session.insert(session.end(), duckSession.begin(), duckSession.end());

err = duck->sendData(topics::gchat, message, session, &muid);
addToChatBuffer(duck->buildCdpPacket(topics::gchat, message, session, muid));

switch (err) {
case DUCK_ERR_NONE:
{
request->send(200, "text/html", "OK.");
}
break;
case DUCKLORA_ERR_MSG_TOO_LARGE:
request->send(413, "text/html", "Message payload too big!");
break;
case DUCKLORA_ERR_HANDLE_PACKET:
request->send(400, "text/html", "BadRequest");
break;
default:
request->send(500, "text/html", "Oops! Unknown error.");
break;
}
});

webServer.on("/privateChatSubmit.json", HTTP_POST, [&](AsyncWebServerRequest* request) {
int err = DUCK_ERR_NONE;

std::vector<byte> message;
String clientId = "";

AsyncWebParameter* p = request->getParam(0);
std::string msg = p->value().c_str();
message.insert(message.end(), msg.begin(), msg.end());

std::vector<byte> muid;
std::vector<byte> session;
session.insert(session.end(), duckSession.begin(), duckSession.end());

err = duck->sendData(topics::pchat, message, session, &muid);
addToPrivateChatBuffer(duck->buildCdpPacket(topics::pchat, message, session, muid), duckSession);

switch (err) {
case DUCK_ERR_NONE:
{
request->send(200, "text/html", "OK.");
}
break;
case DUCKLORA_ERR_MSG_TOO_LARGE:
request->send(413, "text/html", "Message payload too big!");
break;
case DUCKLORA_ERR_HANDLE_PACKET:
request->send(400, "text/html", "BadRequest");
break;
default:
request->send(500, "text/html", "Oops! Unknown error.");
break;
}
});

webServer.on("/openPrivateChat.json", HTTP_POST, [&](AsyncWebServerRequest* request) {
AsyncWebParameter* p = request->getParam(0);
duckSession = p->value().c_str();

createPrivateHistory(duckSession);

request->send(200, "text/html", "OK.");
});

// Captive Portal form submission
Expand Down
2 changes: 1 addition & 1 deletion src/DuckUtils.cpp
Expand Up @@ -8,7 +8,7 @@
namespace duckutils {

namespace {
std::string cdpVersion = "3.2.5";
std::string cdpVersion = "3.3.5";
}

Timer<> duckTimer = timer_create_default();
Expand Down
23 changes: 23 additions & 0 deletions src/Ducks/Duck.cpp
Expand Up @@ -377,6 +377,29 @@ muidStatus Duck::getMuidStatus(const std::vector<byte> & muid) const {
}
}

CdpPacket Duck::buildCdpPacket(byte topic, const std::vector<byte> data,
const std::vector<byte> targetDevice, const std::vector<byte> &muid)
{
if (data.size() > MAX_DATA_LENGTH)
{
logerr("ERROR send data failed, message too large: " + String(data.size()) +
" bytes");
logerr(DUCKPACKET_ERR_SIZE_INVALID);
}
int err = txPacket->prepareForSending(&filter, targetDevice, this->getType(), topic, data);
//txPacket unintended side effects?

if (err != DUCK_ERR_NONE)
{
logerr("prepare for sending failed. " + err);
}
//todo error not handled properly
CdpPacket packet = CdpPacket(txPacket->getBuffer());
packet.muid = muid;

return packet;
}

// TODO: implement this using new packet format
bool Duck::reboot(void*) {
/*
Expand Down
26 changes: 17 additions & 9 deletions src/Ducks/MamaDuck.cpp
Expand Up @@ -90,7 +90,6 @@ void MamaDuck::handleReceivedPacket() {

//Check if Duck is desitination for this packet before relaying
if (duckutils::isEqual(BROADCAST_DUID, packet.dduid)) {

switch(packet.topic) {
case reservedTopic::ping:
loginfo("ping received");
Expand Down Expand Up @@ -122,11 +121,12 @@ void MamaDuck::handleReceivedPacket() {
}
break;
case reservedTopic::mbm:
{
CdpPacket packet = CdpPacket(rxPacket->getBuffer());
packet.timeReceived = millis();
duckNet->addMessageToBuffer(packet);
}
duckNet->addToMessageBoardBuffer(packet);
break;
case topics::gchat:
packet.timeReceived = millis();
duckNet->addToChatBuffer(packet);
break;
default:
err = duckRadio.relayPacket(rxPacket);
Expand Down Expand Up @@ -163,7 +163,6 @@ void MamaDuck::handleReceivedPacket() {

err = duckRadio.sendData(txPacket->getBuffer());
if (err == DUCK_ERR_NONE) {
CdpPacket packet = CdpPacket(txPacket->getBuffer());
filter.bloom_add(packet.muid.data(), MUID_LENGTH);
} else {
logerr("ERROR handleReceivedPacket. Failed to send ack. Error: " +
Expand All @@ -178,10 +177,19 @@ void MamaDuck::handleReceivedPacket() {
handleAck(packet);
break;
case reservedTopic::mbm:
{
CdpPacket packet = CdpPacket(rxPacket->getBuffer());
packet.timeReceived = millis();
duckNet->addMessageToBuffer(packet);
duckNet->addToMessageBoardBuffer(packet);
break;
case topics::gchat:
packet.timeReceived = millis();
duckNet->addToChatBuffer(packet);
break;
case topics::pchat:{
packet.timeReceived = millis();
std::string session(packet.sduid.begin(), packet.sduid.end());

duckNet->createPrivateHistory(session);
duckNet->addToPrivateChatBuffer(packet, session);
}
break;
default:
Expand Down
8 changes: 6 additions & 2 deletions src/Ducks/PapaDuck.cpp
Expand Up @@ -120,8 +120,12 @@ void PapaDuck::handleReceivedPacket() {
rxPacket->getBuffer().size()));
loginfo("invoking callback in the duck application...");

recvDataCallback(rxPacket->getBuffer());

if(rxPacket->getTopic() == topics::gchat){
duckNet->addToChatBuffer(CdpPacket(rxPacket->getBuffer()));
} else{
recvDataCallback(rxPacket->getBuffer());
}

if (acksEnabled) {
const CdpPacket packet = CdpPacket(rxPacket->getBuffer());
if (needsAck(packet)) {
Expand Down
2 changes: 1 addition & 1 deletion src/circularBuffer.cpp
Expand Up @@ -35,7 +35,7 @@ CdpPacket CircularBuffer::getMessage(int index)
}
CircularBuffer::~CircularBuffer()
{
delete buffer;
delete [] buffer;
}


0 comments on commit 51d858e

Please sign in to comment.