diff --git a/examples/main/hackerpet_plus.ino b/examples/main/hackerpet_plus.ino new file mode 100644 index 0000000..67c473a --- /dev/null +++ b/examples/main/hackerpet_plus.ino @@ -0,0 +1,92 @@ +/* + * Project hackerpet_plus + * Description: + * Author: + * Date: + * VERSION: + */ + + +#include "Particle.h" +#include "softap_http.h" +#include "hotspot-http-server.h" + +# include "../lib/MDNS/src/Buffer.h" +# include "../lib/MDNS/src/Buffer.cpp" +# include "../lib/MDNS/src/Record.h" +# include "../lib/MDNS/src/Record.cpp" +# include "../lib/MDNS/src/Label.h" +# include "../lib/MDNS/src/Label.cpp" +# include "../lib/MDNS/src/MDNS.h" +# include "../lib/MDNS/src/MDNS.cpp" + +// new + +#include "config-manager.h" +#include "game-manager.h" + + +// Use primary serial over USB interface for logging output (9600) +// Choose logging level here (ERROR, WARN, INFO) or TRACE +// *** important *** if you want to enable TRACE level debugging, this will display A LOT of logs. +// If you need to debug at that level, it is recommended to "slow down" the hub by executing the contents of the main loop() only i.e. once a second. + +SerialLogHandler logHandler(LOG_LEVEL_INFO, { // Logging level for all messages + { "app.hackerpet", LOG_LEVEL_ERROR }, // Logging level for library messages + { "app", LOG_LEVEL_INFO } // Logging level for application messages +}); + + +// classes +HubInterface hub; +GameManager gameMan(&hub); // which game to play given gameId from config +ConfigManager configMan(&hub, &gameMan); // handles config of hub and game via webpage and eeprom + +// enables simultaneous execution of application and system thread +SYSTEM_THREAD(ENABLED); + +// hotspot +STARTUP(softap_set_application_page_handler(myPages, nullptr)); + +// setup() runs once, when the device is first turned on. +void setup() { + + configMan.Initialize(); // also initializes game manager + + // this is used by hackerpet for reports + hub.Initialize("game_ID_here_TODO"); + // in games it was: hub.Initialize(__FILE__); +} + +unsigned long lastmemcheck = 0; +unsigned long FREE_MEMORY; + +// loop() runs over and over again, as quickly as it can execute. +void loop() { + + Log.trace("[[calling]]: configMan.Run();"); + // serve webpage, read/write eeprom as config changes + configMan.Run(); + + Log.trace("[[calling]]: hub.Run(20);"); + // run the hub + // if testing on a particle photon by itself with no hub, comment this line to avoid seg fault + hub.Run(20); + + // run the loop for the current active game + Log.trace("[[calling]]: gameMan.Run();"); + gameMan.Run(); + + // every 10 seconds, print the free memory as a serial heartbeat + + if ((millis() - lastmemcheck) > 10000) { + Log.trace(Time.timeStr()); + Log.trace("[[calling]]: free memory"); + + FREE_MEMORY = System.freeMemory(); + + lastmemcheck = millis(); + + Serial.printlnf("\tMILLIS: %lu\tSYSTEM MEMORY=%lu", lastmemcheck, FREE_MEMORY); + } +} diff --git a/examples/main/particle-test-local.ino b/examples/main/particle-test-local.ino deleted file mode 100644 index 54ffd78..0000000 --- a/examples/main/particle-test-local.ino +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Project particle-test-local - * Description: - * Author: - * Date: - * VERSION: - */ - - -#include "Particle.h" -#include "softap_http.h" -#include "hotspot-http-server.h" - -// new - -#include "config-manager.h" -#include "game-manager.h" - -// classes -HubInterface hub; -GameManager gameMan(&hub); // which game to play given gameId from config -ConfigManager configMan(&hub, &gameMan); // handles config of hub and game via webpage and eeprom - -// enables simultaneous execution of application and system thread -SYSTEM_THREAD(ENABLED); - -// hotspot -STARTUP(softap_set_application_page_handler(myPages, nullptr)); - -// setup() runs once, when the device is first turned on. -void setup() { - - configMan.Initialize(); // also initializes game manager - hub.Initialize("game_ID_here_TODO"); - // in games it was: hub.Initialize(__FILE__); -} - -unsigned long lastmemcheck = 0; -unsigned long FREE_MEMORY; - -// loop() runs over and over again, as quickly as it can execute. -void loop() { - - // serve webpage, read/write eeprom as config changes - configMan.Run(); - - // ************************************ DISABLE FOR TESTING WITHOUT HUB ************************************ - // run the hub - hub.Run(20); - // ************************************ ************************************ ************************************ - - // run the loop for the current active game - gameMan.Run(); - - FREE_MEMORY = System.freeMemory(); - - if ((millis() - lastmemcheck) > 10000) { - lastmemcheck = millis(); - Serial.print(Time.timeStr()); - Serial.println("in7399"); - Serial.printlnf("\tMILLIS: %lu\tSYSTEM MEMORY=%lu", lastmemcheck, FREE_MEMORY); - } -} diff --git a/lib/MDNS/LICENSE b/lib/MDNS/LICENSE new file mode 100644 index 0000000..abaf418 --- /dev/null +++ b/lib/MDNS/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Mark Hornsby 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/MDNS/README.md b/lib/MDNS/README.md new file mode 100644 index 0000000..61423ae --- /dev/null +++ b/lib/MDNS/README.md @@ -0,0 +1,4 @@ +spark-core-mdns +=============== + +Multicast DNS and DNS-SD for the Spark Core diff --git a/lib/MDNS/library.properties b/lib/MDNS/library.properties new file mode 100644 index 0000000..03f0ba3 --- /dev/null +++ b/lib/MDNS/library.properties @@ -0,0 +1,9 @@ +name=MDNS +version=2.0.0 +license=MIT +author=Mark Hornsby +sentence=Support for multicast name and DNS service registration +# paragraph=a longer description of this library, always prepended with sentence when shown +# url=the url for the project +repository=https://github.com/mrhornsby/spark-core-mdns.git +# architectures=a list of supported boards if this library is hardware dependent, like particle-photon,particle-electron diff --git a/lib/MDNS/src/Buffer.cpp b/lib/MDNS/src/Buffer.cpp new file mode 100644 index 0000000..07c5b65 --- /dev/null +++ b/lib/MDNS/src/Buffer.cpp @@ -0,0 +1,73 @@ +#include "Buffer.h" + +mdns::Buffer::Buffer(uint16_t size) { + this->data = (uint8_t *) malloc(size); + this->size = data != NULL? size : 0; +} + +uint16_t mdns::Buffer::available() { + return offset < limit? limit - offset : offset - limit; +} + +void mdns::Buffer::mark() { + if (markOffset == INVALID_MARK_OFFSET) { + markOffset = offset; + } +} + +void mdns::Buffer::reset() { + if (markOffset != INVALID_MARK_OFFSET) { + offset = markOffset; + markOffset = INVALID_MARK_OFFSET; + } +} + +void mdns::Buffer::setOffset(uint16_t offset) { + this->offset = offset; +} + +uint16_t mdns::Buffer::getOffset() { + return offset; +} + +void mdns::Buffer::read(UDP * udp) { + offset = 0; + limit = udp->read(data, size); +} + +uint8_t mdns::Buffer::readUInt8() { + return data[offset++]; +} + +uint16_t mdns::Buffer::readUInt16() { + return readUInt8() << 8 | readUInt8(); +} + +void mdns::Buffer::writeUInt8(uint8_t value) { + if (offset < size) { + data[offset++] = value; + } +} + +void mdns::Buffer::writeUInt16(uint16_t value) { + writeUInt8(value >> 8); + writeUInt8(value); +} + +void mdns::Buffer::writeUInt32(uint32_t value) { + writeUInt8(value >> 24); + writeUInt8(value >> 16); + writeUInt8(value >> 8); + writeUInt8(value); +} + +void mdns::Buffer::write(UDP * udp) { + udp->write(data, offset); + + offset = 0; +} + +void mdns::Buffer::clear() { + offset = 0; + limit = 0; +} diff --git a/lib/MDNS/src/Buffer.h b/lib/MDNS/src/Buffer.h new file mode 100644 index 0000000..16d27b0 --- /dev/null +++ b/lib/MDNS/src/Buffer.h @@ -0,0 +1,46 @@ +#include "application.h" + +#ifndef _INCL_BUFFER +#define _INCL_BUFFER + +#define INVALID_MARK_OFFSET 0xffff + +namespace mdns { + + class Buffer { + public: + Buffer(uint16_t size); + + uint16_t available(); + + void mark(); + void reset(); + void setOffset(uint16_t offset); + uint16_t getOffset(); + + void read(UDP * udp); + + uint8_t readUInt8(); + uint16_t readUInt16(); + + void write(UDP * udp); + + void writeUInt8(uint8_t value); + void writeUInt16(uint16_t value); + void writeUInt32(uint32_t value); + + void clear(); + + private: + + uint8_t * data; + uint16_t size; + + uint16_t limit = 0; + uint16_t offset = 0; + uint16_t markOffset = INVALID_MARK_OFFSET; + }; + +} + +#endif diff --git a/lib/MDNS/src/Label.cpp b/lib/MDNS/src/Label.cpp new file mode 100644 index 0000000..9dabbc1 --- /dev/null +++ b/lib/MDNS/src/Label.cpp @@ -0,0 +1,318 @@ +#include "Label.h" + +mdns::Label::Label(String name, Label * nextLabel, bool caseSensitive) { + data = (uint8_t *) malloc(name.length() + 1); + + if (data) { + data[0] = name.length(); + for (uint8_t i = 0; i < name.length(); i++) { + data[i + 1] = name.charAt(i); + } + } else { + data = EMPTY_DATA; + } + + this->nextLabel = nextLabel; + this->caseSensitive = caseSensitive; +} + +uint8_t mdns::Label::getSize() { + return data[0]; +} + +uint8_t mdns::Label::getWriteSize() { + Label * label = this; + uint8_t size = 0; + + while (label != NULL) { + if (label->writeOffset == INVALID_OFFSET) { + size += label->data[0] + 1; + label = label->nextLabel; + } else { + size += 2; + label = NULL; + } + } + + return size; +} + +void mdns::Label::write(Buffer * buffer) { + Label * label = this; + + while (label) { + if (label->writeOffset == INVALID_OFFSET) { + label->writeOffset = buffer->getOffset(); + + uint8_t size = label->data[0] + 1; + + for (uint8_t i = 0; i < size; i++) { + buffer->writeUInt8(label->data[i]); + } + + label = label->nextLabel; + } else { + buffer->writeUInt16((LABEL_POINTER << 8) | label->writeOffset); + label = NULL; + } + } +} + +void mdns::Label::reset() { + Label * label = this; + + while (label != NULL) { + label->writeOffset = INVALID_OFFSET; + + label = label->nextLabel; + } +} + +mdns::Label::Reader::Reader(Buffer * buffer) { + this->buffer = buffer; +} + +bool mdns::Label::Reader::hasNext() { + return c != END_OF_NAME && buffer->available() > 0; +} + +uint8_t mdns::Label::Reader::next() { + Log.trace(" mdns::Label::Reader::next: [1]"); + c = buffer->readUInt8(); + Log.trace(" mdns::Label::Reader::next: [2]"); + while ((c & LABEL_POINTER) == LABEL_POINTER) { + Log.trace(" mdns::Label::Reader::next: [3]"); + + if (buffer->available() > 0) { + Log.trace(" mdns::Label::Reader::next: [4]"); + uint8_t c2 = buffer->readUInt8(); + Log.trace(" mdns::Label::Reader::next: [5]"); + uint16_t pointerOffset = ((c & ~LABEL_POINTER) << 8) | c2; + Log.trace(" mdns::Label::Reader::next: [6]"); + buffer->mark(); + Log.trace(" mdns::Label::Reader::next: [7]"); + buffer->setOffset(pointerOffset); + Log.trace(" mdns::Label::Reader::next: [8]"); + c = buffer->readUInt8(); + Log.trace(" mdns::Label::Reader::next: [9]"); + } + else + { + Serial.println("mdns::Label::Reader::next():: AVOIDING INFINITE LOOP, RETURNING!"); + return c; + } + + Log.trace(" mdns::Label::Reader::next: [10]"); + } + Log.trace(" mdns::Label::Reader::next: [11]"); + return c; +} + +bool mdns::Label::Reader::endOfName() { + return c == END_OF_NAME; +} + +mdns::Label::Iterator::Iterator(Label * label) { + this->label = label; + this->startLabel = label; + this->size = label->data[0]; +} + +bool mdns::Label::Iterator::match(uint8_t c) { + if (matches) { + while (offset > size && label) { + label = label->nextLabel; + size = label->data[0]; + offset = 0; + } + + matches = offset <= size && label && (label->data[offset] == c || (!label->caseSensitive && equalsIgnoreCase(c))); + + offset++; + } + + return matches; +} + +bool mdns::Label::Iterator::matched() { + return matches; +} + +bool mdns::Label::Iterator::equalsIgnoreCase(uint8_t c) { + return (c >= 'a' && c <= 'z' && label->data[offset] == c - 32) || (c >= 'A' && c <= 'Z' && label->data[offset] == c + 32); +} + +mdns::Label * mdns::Label::Iterator::getStartLabel() { + return startLabel; +} + +mdns::Label * mdns::Label::Matcher::match(std::map labels, Buffer * buffer) { + + Log.trace(" mdns::Label::Matcher::match: [1]"); + Iterator * iterators[labels.size()]; + Log.trace(" mdns::Label::Matcher::match: [2]"); + std::map::const_iterator i; + + uint8_t idx = 0; + Log.trace(" mdns::Label::Matcher::match: [3]"); + for (i = labels.begin(); i != labels.end(); ++i) { + iterators[idx++] = new Iterator(i->second); + } + Log.trace(" mdns::Label::Matcher::match: [4]"); + + Reader * reader = new Reader(buffer); + Log.trace(" mdns::Label::Matcher::match: [5]"); + while (reader->hasNext()) { + Log.trace(" mdns::Label::Matcher::match: [6]"); + uint8_t size = reader->next(); + + uint8_t idx = 0; + Log.trace(" mdns::Label::Matcher::match: [7]"); + for (uint8_t i = 0; i < labels.size(); i++) { + iterators[i]->match(size); + } + Log.trace(" mdns::Label::Matcher::match: [8]"); + while(idx < size && reader->hasNext()) { + Log.trace(" mdns::Label::Matcher::match: [8.1]"); + uint8_t c = reader->next(); + Log.trace(" mdns::Label::Matcher::match: [8.2]"); + for (uint8_t i = 0; i < labels.size(); i++) { + Log.trace(" mdns::Label::Matcher::match: [8.3]"); + iterators[i]->match(c); + } + Log.trace(" mdns::Label::Matcher::match: [8.4]"); + idx++; + } + Log.trace(" mdns::Label::Matcher::match: [9]"); + } + + Log.trace(" mdns::Label::Matcher::match: [10]"); + buffer->reset(); + Log.trace(" mdns::Label::Matcher::match: [11]"); + Label * label = NULL; + Log.trace(" mdns::Label::Matcher::match: [12]"); + if (reader->endOfName()) { + uint8_t idx = 0; + Log.trace(" mdns::Label::Matcher::match: [13]"); + while (label == NULL && idx < labels.size()) { + Log.trace(" mdns::Label::Matcher::match: [14]"); + if (iterators[idx]->matched()) { + label = iterators[idx]->getStartLabel(); + } + Log.trace(" mdns::Label::Matcher::match: [15]"); + idx++; + } + } +Log.trace(" mdns::Label::Matcher::match: [16]"); + for (uint8_t i = 0; i < labels.size(); i++) { + delete iterators[i]; + } +Log.trace(" mdns::Label::Matcher::match: [17]"); + delete reader; + Log.trace(" mdns::Label::Matcher::match: [18]"); + return label; +} + +void mdns::Label::matched(uint16_t type, uint16_t cls) { +} + +mdns::HostLabel::HostLabel(Record * aRecord, Record * nsecRecord, String name, Label * nextLabel, bool caseSensitive):Label(name, nextLabel, caseSensitive) { + this->aRecord = aRecord; + this->nsecRecord = nsecRecord; +} + +void mdns::HostLabel::matched(uint16_t type, uint16_t cls) { + switch(type) { + case A_TYPE: + case ANY_TYPE: + aRecord->setAnswerRecord(); + nsecRecord->setAdditionalRecord(); + break; + + default: + nsecRecord->setAnswerRecord(); + } +} + +mdns::ServiceLabel::ServiceLabel(Record * aRecord, String name, Label * nextLabel, bool caseSensitive):Label(name, nextLabel, caseSensitive) { + this->aRecord = aRecord; +} + +void mdns::ServiceLabel::addInstance(Record * ptrRecord, Record * srvRecord, Record * txtRecord) { + ptrRecords.push_back(ptrRecord); + srvRecords.push_back(srvRecord); + txtRecords.push_back(txtRecord); +} + +void mdns::ServiceLabel::matched(uint16_t type, uint16_t cls) { + switch(type) { + case PTR_TYPE: + case ANY_TYPE: + for (std::vector::const_iterator i = ptrRecords.begin(); i != ptrRecords.end(); ++i) { + (*i)->setAnswerRecord(); + } + for (std::vector::const_iterator i = srvRecords.begin(); i != srvRecords.end(); ++i) { + (*i)->setAdditionalRecord(); + } + for (std::vector::const_iterator i = txtRecords.begin(); i != txtRecords.end(); ++i) { + (*i)->setAdditionalRecord(); + } + aRecord->setAdditionalRecord(); + break; + } +} + +mdns::InstanceLabel::InstanceLabel(Record * srvRecord, Record * txtRecord, Record * nsecRecord, Record * aRecord, String name, Label * nextLabel, bool caseSensitive):Label(name, nextLabel, caseSensitive) { + this->srvRecord = srvRecord; + this->txtRecord = txtRecord; + this->nsecRecord = nsecRecord; + this->aRecord = aRecord; +} + +void mdns::InstanceLabel::matched(uint16_t type, uint16_t cls) { + switch(type) { + case SRV_TYPE: + srvRecord->setAnswerRecord(); + txtRecord->setAdditionalRecord(); + nsecRecord->setAdditionalRecord(); + aRecord->setAdditionalRecord(); + break; + + case TXT_TYPE: + txtRecord->setAnswerRecord(); + srvRecord->setAdditionalRecord(); + nsecRecord->setAdditionalRecord(); + aRecord->setAdditionalRecord(); + break; + + case ANY_TYPE: + srvRecord->setAnswerRecord(); + txtRecord->setAnswerRecord(); + nsecRecord->setAdditionalRecord(); + aRecord->setAdditionalRecord(); + break; + + default: + nsecRecord->setAnswerRecord(); + } +} + +mdns::MetaLabel::MetaLabel(String name, Label * nextLabel):Label(name, nextLabel) { + // Do nothing +} + +void mdns::MetaLabel::addService(Record * ptrRecord) { + records.push_back(ptrRecord); +} + +void mdns::MetaLabel::matched(uint16_t type, uint16_t cls) { + switch(type) { + case PTR_TYPE: + case ANY_TYPE: + for (std::vector::const_iterator i = this->records.begin(); i != this->records.end(); ++i) { + (*i)->setAnswerRecord(); + } + break; + } +} + diff --git a/lib/MDNS/src/Label.h b/lib/MDNS/src/Label.h new file mode 100644 index 0000000..1a0366c --- /dev/null +++ b/lib/MDNS/src/Label.h @@ -0,0 +1,149 @@ +#include "application.h" + +#ifndef _INCL_LABEL +#define _INCL_LABEL + +#include "Buffer.h" +#include "Record.h" +#include +#include + +#define DOT '.' + +#define END_OF_NAME 0x0 +#define LABEL_POINTER 0xc0 +#define MAX_LABEL_SIZE 63 +#define INVALID_OFFSET -1 + +#define UNKNOWN_NAME -1 +#define BUFFER_UNDERFLOW -2 + +namespace mdns { + + class Label { + private: + + class Iterator; + + public: + class Matcher { + public: + Label * match(std::map labels, Buffer * buffer); + }; + + Label(String name, Label * nextLabel = NULL, bool caseSensitive = false); + + uint8_t getSize(); + + uint8_t getWriteSize(); + + void write(Buffer * buffer); + + virtual void matched(uint16_t type, uint16_t cls); + + void reset(); + + private: + class Reader { + public: + Reader(Buffer * buffer); + + bool hasNext(); + + uint8_t next(); + + bool endOfName(); + private: + Buffer * buffer; + uint8_t c = 1; + }; + + class Iterator { + public: + Iterator(Label * label); + + bool match(uint8_t c); + + bool matched(); + + Label * getStartLabel(); + + private: + Label * startLabel; + Label * label; + uint8_t size; + uint8_t offset = 0; + bool matches = true; + + bool equalsIgnoreCase(uint8_t c); + }; + + uint8_t * EMPTY_DATA = { END_OF_NAME }; + uint8_t * data; + bool caseSensitive; + Label * nextLabel; + int16_t writeOffset = INVALID_OFFSET; + }; + + class HostLabel : public Label { + + public: + + HostLabel(Record * aRecord, Record * nsecRecord, String name, Label * nextLabel = NULL, bool caseSensitive = false); + + virtual void matched(uint16_t type, uint16_t cls); + + private: + Record * aRecord; + Record * nsecRecord; + }; + + class ServiceLabel : public Label { + + public: + + ServiceLabel(Record * aRecord, String name, Label * nextLabel = NULL, bool caseSensitive = false); + + void addInstance(Record * ptrRecord, Record * srvRecord, Record * txtRecord); + + virtual void matched(uint16_t type, uint16_t cls); + + private: + Record * aRecord; + std::vector ptrRecords; + std::vector srvRecords; + std::vector txtRecords; + }; + + class InstanceLabel : public Label { + + public: + + InstanceLabel(Record * srvRecord, Record * txtRecord, Record * nsecRecord, Record * aRecord, String name, Label * nextLabel = NULL, bool caseSensitive = false); + + virtual void matched(uint16_t type, uint16_t cls); + + private: + Record * srvRecord; + Record * txtRecord; + Record * nsecRecord; + Record * aRecord; + }; + + class MetaLabel : public Label { + + public: + + MetaLabel(String name, Label * nextLabel); + + void addService(Record * ptrRecord); + + virtual void matched(uint16_t type, uint16_t cls); + + private: + std::vector records; + }; + +} + +#endif diff --git a/lib/MDNS/src/MDNS.cpp b/lib/MDNS/src/MDNS.cpp new file mode 100644 index 0000000..38577e6 --- /dev/null +++ b/lib/MDNS/src/MDNS.cpp @@ -0,0 +1,292 @@ +#include "MDNS.h" + +bool mdns::MDNS::setHostname(String hostname) { + bool success = true; + String status = "Ok"; + + if (labels[HOSTNAME]) { + status = "Hostname already set"; + success = false; + } + + if (success && hostname.length() < MAX_LABEL_SIZE && isAlphaDigitHyphen(hostname)) { + aRecord = new ARecord(); + + HostNSECRecord * hostNSECRecord = new HostNSECRecord(); + + records.push_back(aRecord); + records.push_back(hostNSECRecord); + + Label * label = new HostLabel(aRecord, hostNSECRecord, hostname, LOCAL); + + labels[HOSTNAME] = label; + labels[META_SERVICE] = META; + + aRecord->setLabel(label); + hostNSECRecord->setLabel(label); + } else { + status = success? "Invalid hostname" : status; + success = false; + } + + return success; +} + +bool mdns::MDNS::addService(String protocol, String service, uint16_t port, String instance, std::vector subServices) { + bool success = true; + String status = "Ok"; + + if (!labels[HOSTNAME]) { + status = "Hostname not set"; + success = false; + } + + if (success && protocol.length() < MAX_LABEL_SIZE - 1 && service.length() < MAX_LABEL_SIZE - 1 && + instance.length() < MAX_LABEL_SIZE && isAlphaDigitHyphen(protocol) && isAlphaDigitHyphen(service) && isNetUnicode(instance)) { + + PTRRecord * ptrRecord = new PTRRecord(); + SRVRecord * srvRecord = new SRVRecord(); + txtRecord = new TXTRecord(); + InstanceNSECRecord * instanceNSECRecord = new InstanceNSECRecord(); + PTRRecord * enumerationRecord = new PTRRecord(true); + + records.push_back(ptrRecord); + records.push_back(srvRecord); + records.push_back(txtRecord); + records.push_back(instanceNSECRecord); + records.push_back(enumerationRecord); + + String serviceString = "_" + service + "._" + protocol; + + Label * protocolLabel = new Label("_" + protocol, LOCAL); + + if (labels[serviceString] == NULL) { + labels[serviceString] = new ServiceLabel(aRecord, "_" + service, protocolLabel); + } + + ((ServiceLabel *) labels[serviceString])->addInstance(ptrRecord, srvRecord, txtRecord); + + String instanceString = instance + "._" + service + "._" + protocol; + + labels[instanceString] = new InstanceLabel(srvRecord, txtRecord, instanceNSECRecord, aRecord, instance, labels[serviceString], true); + META->addService(enumerationRecord); + + for (std::vector::const_iterator i = subServices.begin(); i != subServices.end(); ++i) { + String subServiceString = "_" + *i + "._sub." + serviceString; + + if (labels[subServiceString] == NULL) { + labels[subServiceString] = new ServiceLabel(aRecord, "_" + *i, new Label("_sub", labels[serviceString])); + } + + PTRRecord * subPTRRecord = new PTRRecord(); + PTRRecord * enumerationSubPTRRecord = new PTRRecord(true); + + subPTRRecord->setLabel(labels[subServiceString]); + subPTRRecord->setTargetLabel(labels[instanceString]); + + enumerationSubPTRRecord->setLabel(META); + enumerationSubPTRRecord->setTargetLabel(labels[subServiceString]); + + records.push_back(subPTRRecord); + records.push_back(enumerationSubPTRRecord); + + ((ServiceLabel *) labels[subServiceString])->addInstance(subPTRRecord, srvRecord, txtRecord); + META->addService(enumerationSubPTRRecord); + } + + ptrRecord->setLabel(labels[serviceString]); + ptrRecord->setTargetLabel(labels[instanceString]); + srvRecord->setLabel(labels[instanceString]); + srvRecord->setPort(port); + srvRecord->setHostLabel(labels[HOSTNAME]); + txtRecord->setLabel(labels[instanceString]); + instanceNSECRecord->setLabel(labels[instanceString]); + enumerationRecord->setLabel(META); + enumerationRecord->setTargetLabel(labels[serviceString]); + } else { + status = success? "Invalid name" : status; + success = false; + } + + return success; +} + +void mdns::MDNS::addTXTEntry(String key, String value) { + txtRecord->addEntry(key, value); +} + +bool mdns::MDNS::begin(bool announce) { + // Wait for WiFi to connect + while (!WiFi.ready()) { + } + + udp->begin(MDNS_PORT); + udp->joinMulticast(MDNS_ADDRESS); + + // TODO: Probing + + if (announce) { + for (std::vector::const_iterator i = records.begin(); i != records.end(); ++i) { + (*i)->announceRecord(); + } + + writeResponses(); + } + + return true; +} + +bool mdns::MDNS::processQueries() { + uint16_t n = udp->parsePacket(); + + if (n > 0) { + Log.trace(" MDNS::processQueries(): buffer->read(udp);"); + buffer->read(udp); + Log.trace(" MDNS::processQueries(): udp->flush();"); + udp->flush(); + Log.trace(" MDNS::processQueries(): getResponses();"); + getResponses(); + Log.trace(" MDNS::processQueries(): buffer->clear();"); + buffer->clear(); + Log.trace(" MDNS::processQueries(): writeResponses();"); + writeResponses(); + Log.trace(" MDNS::processQueries(): returning"); + } + + return n > 0; +} + +void mdns::MDNS::getResponses() { + Log.trace(" MDNS::getResponses(): [1]"); + QueryHeader header = readHeader(buffer); + Log.trace(" MDNS::getResponses(): [2]"); + if ((header.flags & 0x8000) == 0 && header.qdcount > 0) { + uint8_t count = 0; + Log.trace(" MDNS::getResponses(): [3]"); + while (count++ < header.qdcount && buffer->available() > 0) { + Log.trace(" MDNS::getResponses(): [4]"); + Label * label = matcher->match(labels, buffer); + Log.trace(" MDNS::getResponses(): [5]"); + if (buffer->available() >= 4) { + Log.trace(" MDNS::getResponses(): [6]"); + uint16_t type = buffer->readUInt16(); + Log.trace(" MDNS::getResponses(): [7]"); + uint16_t cls = buffer->readUInt16(); + Log.trace(" MDNS::getResponses(): [8]"); + + if (label != NULL) { + Log.trace(" MDNS::getResponses(): [9]"); + label->matched(type, cls); + Log.trace(" MDNS::getResponses(): [10]"); + } + } else { + Log.trace(" MDNS::getResponses(): [11]"); + status = "Buffer underflow at index " + buffer->getOffset(); + } + Log.trace(" MDNS::getResponses(): [12]"); + } + Log.trace(" MDNS::getResponses(): [13]"); + } + Log.trace(" MDNS::getResponses(): [14]"); +} + +mdns::MDNS::QueryHeader mdns::MDNS::readHeader(Buffer * buffer) { + QueryHeader header; + + if (buffer->available() >= 12) { + header.id = buffer->readUInt16(); + header.flags = buffer->readUInt16(); + header.qdcount = buffer->readUInt16(); + header.ancount = buffer->readUInt16(); + header.nscount = buffer->readUInt16(); + header.arcount = buffer->readUInt16(); + } + + return header; +} + +void mdns::MDNS::writeResponses() { + + uint8_t answerCount = 0; + uint8_t additionalCount = 0; + + for (std::vector::const_iterator i = records.begin(); i != records.end(); ++i) { + if ((*i)->isAnswerRecord()) { + answerCount++; + } + if ((*i)->isAdditionalRecord()) { + additionalCount++; + } + } + + Log.trace(" MDNS::writeResponses(): [1]"); + if (answerCount > 0) { + buffer->writeUInt16(0x0); + buffer->writeUInt16(0x8400); + buffer->writeUInt16(0x0); + buffer->writeUInt16(answerCount); + buffer->writeUInt16(0x0); + buffer->writeUInt16(additionalCount); + Log.trace(" MDNS::writeResponses(): [2]"); + + for (std::vector::const_iterator i = records.begin(); i != records.end(); ++i) { + if ((*i)->isAnswerRecord()) { + (*i)->write(buffer); + } + } + Log.trace(" MDNS::writeResponses(): [3]"); + + for (std::vector::const_iterator i = records.begin(); i != records.end(); ++i) { + if ((*i)->isAdditionalRecord()) { + (*i)->write(buffer); + } + } + Log.trace(" MDNS::writeResponses(): [4]"); + } + Log.trace(" MDNS::writeResponses(): [5]"); + if (buffer->available() > 0) { + Log.trace(" MDNS::writeResponses(): [6]"); + udp->beginPacket(MDNS_ADDRESS, MDNS_PORT); + Log.trace(" MDNS::writeResponses(): [7]"); + buffer->write(udp); + Log.trace(" MDNS::writeResponses(): [8]"); + udp->endPacket(); + Log.trace(" MDNS::writeResponses(): [9]"); + } + Log.trace(" MDNS::writeResponses(): [10]"); + for (std::map::const_iterator i = labels.begin(); i != labels.end(); ++i) { + i->second->reset(); + } + Log.trace(" MDNS::writeResponses(): [11]"); + for (std::vector::const_iterator i = records.begin(); i != records.end(); ++i) { + (*i)->reset(); + } +} + +bool mdns::MDNS::isAlphaDigitHyphen(String string) { + bool result = true; + + uint8_t idx = 0; + + while (result && idx < string.length()) { + uint8_t c = string.charAt(idx++); + + result = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'; + } + + return result; +} + +bool mdns::MDNS::isNetUnicode(String string) { + bool result = true; + + uint8_t idx = 0; + + while (result && idx < string.length()) { + uint8_t c = string.charAt(idx++); + + result = c >= 0x1f && c != 0x7f; + } + + return result; +} diff --git a/lib/MDNS/src/MDNS.h b/lib/MDNS/src/MDNS.h new file mode 100644 index 0000000..e0651bf --- /dev/null +++ b/lib/MDNS/src/MDNS.h @@ -0,0 +1,68 @@ +#include "application.h" + +#ifndef _INCL_MDNS +#define _INCL_MDNS + +#include "Buffer.h" +#include "Label.h" +#include "Record.h" +#include +#include + +#define MDNS_ADDRESS IPAddress(224, 0, 0, 251) +#define MDNS_PORT 5353 + +#define BUFFER_SIZE 512 +#define HOSTNAME "" +#define META_SERVICE "_services._dns-sd._udp" + +namespace mdns { + + class MDNS { + public: + + bool setHostname(String hostname); + + bool addService(String protocol, String service, uint16_t port, String instance, std::vector subServices = std::vector()); + + void addTXTEntry(String key, String value = ""); + + bool begin(bool announce = false); + + bool processQueries(); + + private: + + struct QueryHeader { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + }; + + UDP * udp = new UDP(); + Buffer * buffer = new Buffer(BUFFER_SIZE); + + Label * ROOT = new Label(""); + Label * LOCAL = new Label("local", ROOT); + MetaLabel * META = new MetaLabel("_services", new Label("_dns-sd", new Label("_udp", LOCAL))); + Label::Matcher * matcher = new Label::Matcher(); + + ARecord * aRecord; + TXTRecord * txtRecord; + + std::map labels; + std::vector records; + String status = "Ok"; + + QueryHeader readHeader(Buffer * buffer); + void getResponses(); + void writeResponses(); + bool isAlphaDigitHyphen(String string); + bool isNetUnicode(String string); + }; + +} +#endif diff --git a/lib/MDNS/src/MDNS/Buffer.h b/lib/MDNS/src/MDNS/Buffer.h new file mode 100644 index 0000000..67d1997 --- /dev/null +++ b/lib/MDNS/src/MDNS/Buffer.h @@ -0,0 +1 @@ +#include "../Buffer.h" \ No newline at end of file diff --git a/lib/MDNS/src/MDNS/Label.h b/lib/MDNS/src/MDNS/Label.h new file mode 100644 index 0000000..e86c968 --- /dev/null +++ b/lib/MDNS/src/MDNS/Label.h @@ -0,0 +1 @@ +#include "../Label.h" \ No newline at end of file diff --git a/lib/MDNS/src/MDNS/MDNS.h b/lib/MDNS/src/MDNS/MDNS.h new file mode 100644 index 0000000..0817678 --- /dev/null +++ b/lib/MDNS/src/MDNS/MDNS.h @@ -0,0 +1 @@ +#include "../MDNS.h" \ No newline at end of file diff --git a/lib/MDNS/src/MDNS/Record.h b/lib/MDNS/src/MDNS/Record.h new file mode 100644 index 0000000..e92ec30 --- /dev/null +++ b/lib/MDNS/src/MDNS/Record.h @@ -0,0 +1 @@ +#include "../Record.h" \ No newline at end of file diff --git a/lib/MDNS/src/Record.cpp b/lib/MDNS/src/Record.cpp new file mode 100644 index 0000000..0d7c310 --- /dev/null +++ b/lib/MDNS/src/Record.cpp @@ -0,0 +1,165 @@ +#include "Record.h" + +#include "Label.h" + +mdns::Record::Record(uint16_t type, uint16_t cls, uint32_t ttl, bool announce) { + this->type = type; + this->cls = cls; + this->ttl = ttl; + this->announce = announce; +} + +void mdns::Record::setLabel(Label * label) { + this->label = label; +} + +void mdns::Record::announceRecord() { + if (this->announce) { + this->answerRecord = true; + } +} + +void mdns::Record::setAnswerRecord() { + this->answerRecord = true; +} + +bool mdns::Record::isAnswerRecord() { + return answerRecord && !knownRecord; +} + +void mdns::Record::setAdditionalRecord() { + this->additionalRecord = true; +} + +bool mdns::Record::isAdditionalRecord() { + return additionalRecord && !answerRecord && !knownRecord; +} + +void mdns::Record::setKnownRecord() { + this->knownRecord = true; +} + +void mdns::Record::write(Buffer * buffer) { + label->write(buffer); + buffer->writeUInt16(type); + buffer->writeUInt16(cls); + buffer->writeUInt32(ttl); + writeSpecific(buffer); +} + +void mdns::Record::reset() { + this->answerRecord = false; + this->additionalRecord = false; + this->knownRecord = false; +} + +mdns::Label * mdns::Record::getLabel() { + return label; +} + +mdns::ARecord::ARecord():Record(A_TYPE, IN_CLASS | CACHE_FLUSH, TTL_2MIN) { +} + +void mdns::ARecord::writeSpecific(Buffer * buffer) { + buffer->writeUInt16(4); + IPAddress ip = WiFi.localIP(); + for (int i = 0; i < IP_SIZE; i++) { + buffer->writeUInt8(ip[i]); + } +} + +mdns::NSECRecord::NSECRecord():Record(NSEC_TYPE, IN_CLASS | CACHE_FLUSH, TTL_2MIN) { +} + +mdns::HostNSECRecord::HostNSECRecord():NSECRecord() { +} + +void mdns::HostNSECRecord::writeSpecific(Buffer * buffer) { + buffer->writeUInt16(5); + getLabel()->write(buffer); + buffer->writeUInt8(0); + buffer->writeUInt8(1); + buffer->writeUInt8(0x40); +} + +mdns::InstanceNSECRecord::InstanceNSECRecord():NSECRecord() { +} + +void mdns::InstanceNSECRecord::writeSpecific(Buffer * buffer) { + buffer->writeUInt16(9); + getLabel()->write(buffer); + buffer->writeUInt8(0); + buffer->writeUInt8(5); + buffer->writeUInt8(0); + buffer->writeUInt8(0); + buffer->writeUInt8(0x80); + buffer->writeUInt8(0); + buffer->writeUInt8(0x40); +} + +mdns::PTRRecord::PTRRecord(bool meta):Record(PTR_TYPE, IN_CLASS, TTL_75MIN, !meta) { +} + +void mdns::PTRRecord::writeSpecific(Buffer * buffer) { + buffer->writeUInt16(targetLabel->getWriteSize()); + targetLabel->write(buffer); +} + +void mdns::PTRRecord::setTargetLabel(Label * label) { + targetLabel = label; +} + +mdns::SRVRecord::SRVRecord():Record(SRV_TYPE, IN_CLASS | CACHE_FLUSH, TTL_2MIN) { +} + +void mdns::SRVRecord::writeSpecific(Buffer * buffer) { + buffer->writeUInt16(6 + hostLabel->getWriteSize()); + buffer->writeUInt16(0); + buffer->writeUInt16(0); + buffer->writeUInt16(port); + hostLabel->write(buffer); +} + +void mdns::SRVRecord::setHostLabel(Label * label) { + hostLabel = label; +} + +void mdns::SRVRecord::setPort(uint16_t port) { + this->port = port; +} + +mdns::TXTRecord::TXTRecord():Record(TXT_TYPE, IN_CLASS | CACHE_FLUSH, TTL_75MIN) { +} + +void mdns::TXTRecord::addEntry(String key, String value) { + String entry = key; + + if (value == NULL || value.length() > 0) { + entry += '='; + entry += value; + } + + data.push_back(entry); +} + +void mdns::TXTRecord::writeSpecific(Buffer * buffer) { + uint16_t size = 0; + + std::vector::const_iterator i; + + for(i = data.begin(); i != data.end(); ++i) { + size += i->length() + 1; + } + + buffer->writeUInt16(size); + + for(i = data.begin(); i != data.end(); ++i) { + uint8_t length = i->length(); + + buffer->writeUInt8(length); + + for (uint8_t idx = 0; idx < length; idx++) { + buffer->writeUInt8(i->charAt(idx)); + } + } +} diff --git a/lib/MDNS/src/Record.h b/lib/MDNS/src/Record.h new file mode 100644 index 0000000..8ad424c --- /dev/null +++ b/lib/MDNS/src/Record.h @@ -0,0 +1,157 @@ +#include "application.h" + +#ifndef _INCL_RECORD +#define _INCL_RECORD + +#include "Buffer.h" +#include + +#define IN_CLASS 1 +#define CACHE_FLUSH 0x8000 + +#define A_TYPE 0x01 +#define PTR_TYPE 0x0c +#define TXT_TYPE 0x10 +#define AAAA_TYPE 0x1c +#define SRV_TYPE 0x21 +#define NSEC_TYPE 0x2f + +#define ANY_TYPE 0xFF + +#define TTL_2MIN 120 +#define TTL_75MIN 4500 + +#define IP_SIZE 4 + +namespace mdns { + class Label; + + class Record { + + public: + + void setLabel(Label * label); + + void announceRecord(); + + void setAnswerRecord(); + + bool isAnswerRecord(); + + void setAdditionalRecord(); + + bool isAdditionalRecord(); + + void setKnownRecord(); + + void write(Buffer * buffer); + + void reset(); + + protected: + + Record(uint16_t type, uint16_t cls, uint32_t ttl, bool announce = true); + + Label * getLabel(); + + virtual void writeSpecific(Buffer * buffer) = 0; + + private: + + Label * label; + uint16_t type; + uint16_t cls; + uint32_t ttl; + bool announce; + bool answerRecord = false; + bool additionalRecord = false; + bool knownRecord = false; + }; + + class ARecord : public Record { + + public: + + ARecord(); + + virtual void writeSpecific(Buffer * buffer); + }; + + class NSECRecord : public Record { + + public: + + NSECRecord(); + + virtual void writeSpecific(Buffer * buffer) = 0; + }; + + class HostNSECRecord : public NSECRecord { + + public: + + HostNSECRecord(); + + virtual void writeSpecific(Buffer * buffer); + }; + + class InstanceNSECRecord : public NSECRecord { + + public: + + InstanceNSECRecord(); + + virtual void writeSpecific(Buffer * buffer); + }; + + class PTRRecord : public Record { + + public: + + PTRRecord(bool meta = false); + + virtual void writeSpecific(Buffer * buffer); + + void setTargetLabel(Label * label); + + private: + + Label * targetLabel; + + }; + + class SRVRecord : public Record { + + public: + + SRVRecord(); + + virtual void writeSpecific(Buffer * buffer); + + void setHostLabel(Label * label); + + void setPort(uint16_t port); + + private: + + Label * hostLabel; + uint16_t port; + }; + + class TXTRecord : public Record { + + public: + + TXTRecord(); + + virtual void writeSpecific(Buffer * buffer); + + void addEntry(String key, String value = ""); + + private: + + std::vector data; + }; +} + +#endif diff --git a/library.properties b/library.properties index 4e79420..9273ef4 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=hackerpet_plus -version=0.1.110 +version=0.1.108 license=AGPL author=Csaba Petre maintainer=Csaba Petre @@ -9,4 +9,3 @@ url=http://hackerpet.com repository=https://github.com/CleverPet/hackerpet_plus/ architectures=particle-photon dependencies.hackerpet=0.2.6 -dependencies.MDNS=2.0.0 \ No newline at end of file diff --git a/project.properties b/project.properties index 62c9ae3..682c7f3 100644 --- a/project.properties +++ b/project.properties @@ -1,3 +1,2 @@ name=hackerpet_plus dependencies.hackerpet=0.2.6 -dependencies.MDNS=2.0.0 diff --git a/src/config-manager.cpp b/src/config-manager.cpp index 97086d3..a820f04 100644 --- a/src/config-manager.cpp +++ b/src/config-manager.cpp @@ -5,7 +5,7 @@ using namespace std; -#include +#include "../lib/MDNS/src/MDNS.h" using namespace mdns; @@ -21,7 +21,7 @@ ConfigManager::ConfigManager(HubInterface * hub, GameManager * gameMan) bool ConfigManager::_sched_char_to_string(char * char_tmp, String & str) { - + // utility to convert char to string, for scheduler String str2(char_tmp); str = str2; return true; @@ -29,6 +29,7 @@ bool ConfigManager::_sched_char_to_string(char * char_tmp, String & str) bool ConfigManager::_sched_string_to_char(char * char_tmp, String & str) { + // utility to convert string to char, for scheduler for (int k = 0; k < 5; k++) { char_tmp[k] = str[k]; @@ -40,7 +41,10 @@ bool ConfigManager::Initialize() { _system_ready = false; - bool ever_stored = false; // have we ever stored a game to eeprom? check value against "checksum" + bool ever_stored = false; // have we ever stored a game to eeprom? + + // we check against a hard coded value, and if there's no match, we assume we need to initialize all the EEPROM values to defaults for this hub + uint16_t ever_stored_check; EEPROM.get(_EVER_STORED_EEP_ADDRESS, ever_stored_check); @@ -50,11 +54,12 @@ bool ConfigManager::Initialize() } if (ever_stored) - { + { + // assume this hub's eeprom has been initialied at least once and all values can be read: + EEPROM.get(_GAME_EEP_ADDRESS, _game_to_play); _next_game_to_play = _game_to_play; _gameMan->Initialize(_next_game_to_play); - //_gameMan->set_next_game(_next_game_to_play); EEPROM.get(_TIME_ZONE_EEP_ADDRESS, _time_zone_offset); Time.zone(_time_zone_offset); @@ -96,17 +101,17 @@ bool ConfigManager::Initialize() } else { - // default values for a "new" hub: + // assume this hub's eeprom has not been initialized, or the hard coded value was changed to force a reset to defaults: + _game_to_play = 0; _next_game_to_play = 0; - _time_zone_offset = 0.0; // TIME_ZONE_OFFSET = 0.0; - _dst_on = false; // DST_ON = false; - _hub_mode = _HUB_MODE_STAY_ON; // HUB_MODE = HUB_MODE_STAY_ON; + _time_zone_offset = 0.0; + _dst_on = false; + _hub_mode = _HUB_MODE_STAY_ON; EEPROM.put(_GAME_EEP_ADDRESS, _next_game_to_play); _gameMan->Initialize(_next_game_to_play); - //_gameMan->set_next_game(_next_game_to_play); EEPROM.put(_TIME_ZONE_EEP_ADDRESS, _time_zone_offset); Time.zone(_time_zone_offset); @@ -148,6 +153,7 @@ bool ConfigManager::Initialize() // to force initialization to a valid _hub_state in Run() _hub_state = _HUB_STATE_INIT; + // mdns for http server mgschwan_mdns = new MDNS; return true; @@ -157,15 +163,14 @@ bool ConfigManager::Initialize() bool ConfigManager::Run() { - // interface + // this function is the interface: // of config / parameters from config manager above, to game manager and hub below // also other way around, from hub to config manager for webpage display - // TODO: set Hub mode based on _hub_mode !!! use _hub - - if (WiFi.ready() && _system_ready == false) { + // set up mdns for the local http server + delete mgschwan_mdns; mgschwan_mdns = new MDNS; @@ -183,14 +188,18 @@ bool ConfigManager::Run() if (_system_ready) { + // it's not clear if this can/should be called less or more often; + // the webserver appears more stable on some hubs with mdns being called every second, instead of every loop + // this should be investigated further... if ((millis() - _last_mdns_loop_time)>1000) { - //Serial.println(" >>>>>>>> Calling mgschwan_MDNS_loop ... >>>>>>>>"); mgschwan_MDNS_loop(mgschwan_mdns); - //Serial.println(" >>>>>>>> Done calling mgschwan_MDNS_loop. >>>>>>>>"); _last_mdns_loop_time = millis(); } + + // what message to display on the webpage for hub status + Log.trace(" [[ConfigManager::Run()]]: 1!"); _display_error_msg = "Your hub is working."; if (_hub->IsHubOutOfFood()) { @@ -209,12 +218,17 @@ bool ConfigManager::Run() _display_error_msg = "Dome is removed."; } - // get current game from gameMan + Log.trace(" [[ConfigManager::Run()]]: 2!"); + + // get current game from game manager + _game_to_play = _gameMan->get_current_game(); _new_game_selected = _game_to_play; + Log.trace(" [[ConfigManager::Run()]]: 3!"); _serve_webinterface(); + Log.trace(" [[ConfigManager::Run()]]: 4!"); if (_new_game_selected >= 0 && _new_game_selected != _game_to_play) { Log.info("New game selected %i", _new_game_selected); @@ -227,8 +241,11 @@ bool ConfigManager::Run() } - // test idea that we always attempt to reconnect every N seconds. - // could make this last webpage-request dependent, so if webpage is currently active, don't need to do this + // on some networks, mdns occasionally fails and the domain clevepet.local stops working; need to attempt reconnect + // since we don't have a conclusive way to determine if it is broken, we for now blindly attempt reconnect every 10 seconds if there's no request in that time + + Log.trace(" [[ConfigManager::Run()]]: 5!"); + bool _need_mdns_reconnect = (millis() - _last_request_time > 10000); if (_need_mdns_reconnect) @@ -240,9 +257,6 @@ bool ConfigManager::Run() Serial.print(Time.timeStr()); Serial.println(" ######## Attempting mdns reconnect... ########"); - //delete mgschwan_mdns; - //mgschwan_mdns = new MDNS; - _broadcastAddress = mgschwan_getBroadcastAddress(); mgschwan_setupNetwork(mgschwan_mdns, true); @@ -253,13 +267,11 @@ bool ConfigManager::Run() } - // TODO should _process_hub_mode be outside system_ready? things should function in general even if not on wifi??? - // also set to active by default? - // TODO it already is by default but should set back to HUB STAY ON mode if can't connect to wifi??? or at least not in HUB STAY OFF mode??? - // also... we might need to put this in its own class at some point + // process hub mode (stay on, stay off, scheduler) _process_hub_mode(); + Log.trace(" [[ConfigManager::Run()]]: 7!"); return true; } @@ -267,12 +279,9 @@ bool ConfigManager::Run() bool ConfigManager::_process_hub_mode() { - - // compare _hub_mode vs _last_hub_mode (set at the end of this function) - if (_hub_mode != _last_hub_mode) { - // there may not be anything to set here ... + // currently, there's no specific action to take when hub mode _changes_ ... } // determine hub state: active vs. standby, based on mode (and schedule, if mode is scheduler) @@ -289,17 +298,10 @@ bool ConfigManager::_process_hub_mode() } else if (_hub_mode == _HUB_MODE_SCHEDULED) { - // use: - - // _weekday_from: 06:30 - // _weekday_to - // _weekend_from - // _weekend_to - - // get: - // current time - // current day of the week -> weekday or weekend + // determine hub state based on schedule, current time, and current day of the week + // get current time and weekday vs. weekend + int hour_now = Time.hour(); int minute_now = Time.minute(); int weekday_now = Time.weekday(); @@ -309,6 +311,8 @@ bool ConfigManager::_process_hub_mode() String to_hour = " "; String to_minute = " "; + // get from/to hour and minute for hub to be on, from current scheduler settings (_weekend_from, _weekend_to, _weekday...) and current day of the week + if (weekday_now == 1 || weekday_now == 7) // it is a weekend { from_hour[0] = _weekend_from[0]; @@ -338,7 +342,6 @@ bool ConfigManager::_process_hub_mode() to_minute[1] = _weekday_to[4]; } - int day_minutes_now = hour_now * 60 + minute_now; int day_minutes_from = from_hour.toInt() * 60 + from_minute.toInt(); @@ -387,7 +390,7 @@ bool ConfigManager::_process_hub_mode() new_hub_state = _HUB_STATE_ACTIVE; } - // TODO have to count _kibbles_eaten_today !! + // count _kibbles_eaten_today (retrieve from game manager) _kibbles_eaten_today = _gameMan->get_kibbles_eaten(); // if kibbles are above limit, override new_hub_state to standby @@ -396,9 +399,9 @@ bool ConfigManager::_process_hub_mode() new_hub_state = _HUB_STATE_STANDBY; } - // TODO reset kibbles eaten when needed! (when it is after midnight and previous time was before) - // based on: Time.day, Time.last_day ? - this is day of the month! check not equals - // Time.day() + // reset kibbles eaten when needed (when it is after midnight and previous time was before) + // based on: Time.day, Time.last_day (day of the month) + int day_now = Time.day(); bool reset_kibbles = false; @@ -422,45 +425,39 @@ bool ConfigManager::_process_hub_mode() if (new_hub_state == _HUB_STATE_ACTIVE) { - - // for now, we are going to ignore the indicator light - - // _logger->Log("SI::OnDesiredStatus: setting _activity_state = ACTIVITY_STATE_ACTIVE", Logger::LOG_LEVEL_LIGHT_CONTEXT); - // IndicatorState = IL_SI_ACTIVE; - - - // this just set _active_mode flag in old firmware and that's all... - // _dli->SetActiveMode(true); - + // reference from cleverpet cloud-based firmware + // (for now, we are going to ignore the indicator light): + // _logger->Log("SI::OnDesiredStatus: setting _activity_state = ACTIVITY_STATE_ACTIVE", Logger::LOG_LEVEL_LIGHT_CONTEXT); + // IndicatorState = IL_SI_ACTIVE; + // this just set _active_mode flag + // _dli->SetActiveMode(true); + _hub->SetButtonAudioEnabled(true); _hub->SetLightEnabled(true); _hub->UpdateButtonAudioEnabled(); // inform game manager of hub state - // technically, only need to do this if it changed. - // should we just have it skip the game loop? _gameMan->set_game_enabled(true); + // set new hub state as the current state _hub_state = new_hub_state; - } else if (new_hub_state == _HUB_STATE_STANDBY) { - // for now, we are going to ignore the indicator light - - // _logger->Log("SI::OnDesiredStatus: setting _activity_state = ACTIVITY_STATE_STANDBY", Logger::LOG_LEVEL_LIGHT_CONTEXT); - // if (_max_kibbles_light_on) - // { - // IndicatorState = IL_SI_MAX_KIBBLES_DEPLETED; - // } - // else - // { - // IndicatorState = IL_SI_STANDBY; - // } - // + // reference from cleverpet cloud-based firmware + // (for now, we are going to ignore the indicator light): + // _logger->Log("SI::OnDesiredStatus: setting _activity_state = ACTIVITY_STATE_STANDBY", Logger::LOG_LEVEL_LIGHT_CONTEXT); + // if (_max_kibbles_light_on) + // { + // IndicatorState = IL_SI_MAX_KIBBLES_DEPLETED; + // } + // else + // { + // IndicatorState = IL_SI_STANDBY; + // } - // this just set _active_mode flag in old firmware and that's all... - // _dli->SetActiveMode(false); + // this just set _active_mode flag in old firmware and that's all... + // _dli->SetActiveMode(false); // only swap from ACTIVE to STANDBY if a game ended; but allow swap from INIT to STANDBY regardless if ((_hub_state == _HUB_STATE_ACTIVE && _gameMan->trial_just_done()) || (_hub_state == _HUB_STATE_INIT)) @@ -478,8 +475,6 @@ bool ConfigManager::_process_hub_mode() { Log.info("ERROR invalid hub state!"); } - - } _last_hub_mode = _hub_mode; @@ -490,9 +485,6 @@ bool ConfigManager::_process_hub_mode() bool ConfigManager::_serve_webinterface() { - //int new_game_selected = -1; - //int overrideable_next_game = _next_game_to_play; - _webclient = _webserver.available(); bool request_finished = false; if (_webclient.connected()) @@ -512,7 +504,7 @@ bool ConfigManager::_serve_webinterface() Log.info("--- SERVER FINISHED PROCESSING REQUEST ---"); } - delay (1); //That is a hack to allow the browser to receive the data + delay (1); //That is a hack to allow the browser to receive the data (this is from the original http server code library we are using) _webclient.stop(); return true; } @@ -544,10 +536,6 @@ bool ConfigManager::_read_from_client(bool & request_finished, String & response bool ConfigManager::_process_request(String req_str) { - - //Log.info("request string:"); - //Log.print(req_str); - // different types of requests bool req_get = req_str.substring(0, 3).equalsIgnoreCase("GET"); @@ -901,9 +889,7 @@ bool ConfigManager::_process_set_hub_mode_req(String req_str) int hub_mode_html_index = req_str.indexOf("hub_mode\""); // "hub_mode": Log.info("This is a HUB MODE post request."); - //Log.print("*******\n"); - //Log.print(thing + "\n"); - //Log.print("*-------------*\n"); + String tmp_2 = req_str.substring(hub_mode_html_index + 10); int index_stop = tmp_2.indexOf("}"); tmp_2 = tmp_2.substring(0, index_stop); @@ -1018,147 +1004,3 @@ bool ConfigManager::_process_get_req(String req_str) _webclient.print(bin2c_html_piece_39_tmp); return true; } - - -// // ////////////////////////////////////////////////////////////////////////////////////// DEPRECATED ////////////////////////////////////////////////////////////////////////////////////// - - -// bool ConfigManager::_write_response_html() -// { -// String content = ""; -// content += "\n"; -// content += "\n"; -// content += "\n\n"; -// content += _htmlMan->get_script_html(); -// content += "\n"; -// content += "\n"; - -// // enable for debugging full GET request: -// //content += "

"; -// //content += thing; -// //content += "

"; - -// // return the id from this function at the end; or, return what? -1? to indicate no new choice? - -// // print list of games and URL to go to - -// content += "
\n"; -// content += "select game:

\n"; - -// int next_game_to_disp = _next_game_to_play; -// if (_new_game_selected > 0) -// { -// // main loop has not set this yet, if it was just selected; so we need this check -// next_game_to_disp = _new_game_selected; -// } - -// content += _htmlMan->get_link_for_game(0, "0", "Eating the Food", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(1, "1", "Exploring the Touchpads", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(2, "2", "Engaging Consistently", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(3, "3", "Avoiding Unlit Touchpads", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(4, "4", "Learning the Lights", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(5, "5", "Mastering the Lights", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(6, "6", "Responding Quickly", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(7, "7", "Learning Brightness", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(8, "8", "Learning Double Sequences", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(9, "9", "Learning Longer Sequences", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(10, "10", "Matching Two Colors", _game_to_play, next_game_to_disp); -// content += _htmlMan->get_link_for_game(11, "11", "Matching More Colors", _game_to_play, next_game_to_disp); - -// content += "
\n"; -// //content += display_error_msg; -// //content += "
"; -// content += _htmlMan->get_async_html(); -// content += "
\n"; - -// // "
\n" -// //"Select Timezone:
\n" -// "
\n"; - -// String time_zone_str = _htmlMan->get_time_zone_string(_time_zone_offset); -// Log.info("time zone str length: " + int_to_string(time_zone_str.length())); - -// String content_2 = ""; - -// content_2 += "
\n"; - -// content_2 += "Current Date/Time:
\n"; -// content_2 += "" + Time.timeStr() + "
\n" + "
"; - -// content_2 += "
\n"; - -// String content_3 = ""; - -// // add scheduler to content_3 - -// Log.info("_weekday_from: <" + _weekday_from + ">"); -// Log.info("_weekday_to: <" + _weekday_to + ">"); -// Log.info("_weekend_from: <" + _weekend_from + ">"); -// Log.info("_weekend_to: <" + _weekend_to + ">"); - -// content_3 += _htmlMan->get_scheduler_html(_hub_mode, _weekday_from, _weekday_to, _weekend_from, _weekend_to); - - -// String content_4 = ""; - -// content_4 += "
\n"; - -// content_4 += "Hub state: "; - -// // this sets on init load - -// if (_hub_state == _HUB_STATE_ACTIVE) -// { -// content_4 += "Active"; -// } -// else if (_hub_state == _HUB_STATE_STANDBY) -// { -// content_4 += "Standby"; -// } -// else -// { -// content_4 += "Invalid"; -// } - -// content_4 += "
\n"; -// content_4 += "
\n"; - -// String content_5 = ""; -// content_5 += _htmlMan->get_kibbles_html(_kibbles_limit, _kibbles_eaten_today); - -// content_5 += "\n"; -// content_5 += ""; -// //Log.info("content length: " + int_to_string(content.length())); -// //Log.info("content_2 length: " + int_to_string(content_2.length())); -// _webclient.println("HTTP/1.0 200 OK"); -// _webclient.println("Content-type: text/html"); -// _webclient.print("Content-length: "); -// _webclient.println(content.length() + time_zone_str.length() + content_2.length() + content_3.length() + content_4.length() + content_5.length()); -// _webclient.println(""); -// _webclient.print(content); -// _webclient.print(time_zone_str); -// _webclient.print(content_2); -// _webclient.print(content_3); -// _webclient.print(content_4); -// _webclient.print(content_5); -// _webclient.println(); - -// return true; -// } - diff --git a/src/config-manager.h b/src/config-manager.h index 3c670a9..643b91e 100644 --- a/src/config-manager.h +++ b/src/config-manager.h @@ -2,7 +2,7 @@ #define CONFIG_MANAGER_H #include "Particle.h" -#include +#include "../lib/MDNS/src/MDNS.h" using namespace mdns; #include "http-server-util.h" @@ -11,11 +11,6 @@ using namespace mdns; #include "html-manager.h" -// TODO ADD THIS FILE TO GIT - -// TODO also need to commit hackerpet changes separately!!! - - class ConfigManager { public: @@ -23,22 +18,22 @@ class ConfigManager bool Initialize(); bool Run(); private: - bool _sched_char_to_string(char * char_tmp, String & str); - bool _sched_string_to_char(char * char_tmp, String & str); - bool _process_hub_mode(); - bool _serve_webinterface(); - bool _read_from_client(bool & request_finished, String & response_str); - bool _process_request(String req_str); - bool _process_api_get_req(String req_str); - bool _process_api_post_req(String req_str); - bool _process_set_game_req(String req_str); //, int dst_index); - bool _process_set_max_kibbles_req(String req_str); - bool _process_set_kibbles_thresh_req(String req_str); - bool _process_set_dst_req(String req_str); //, int dst_index); - bool _process_set_timezone_req(String req_str); // , int dst_index); - bool _process_set_hub_mode_req(String req_str); //, int dst_index); - bool _process_set_schedule_req(String req_str); - bool _process_get_req(String req_str); + bool _sched_char_to_string(char * char_tmp, String & str); // utility to convert char to string, for scheduler + bool _sched_string_to_char(char * char_tmp, String & str); // utility to convert string to char, for scheduler + bool _process_hub_mode(); // set new hub mode (on/off/scheduler), if needed, and then set hub state (on/standby) based on scheduler if applicable + bool _serve_webinterface(); // serves local http server + bool _read_from_client(bool & request_finished, String & response_str); // read a request + bool _process_request(String req_str); // process an http request + bool _process_api_get_req(String req_str); // process an API request from cleverpet.local + bool _process_api_post_req(String req_str); // post request + bool _process_set_game_req(String req_str); // a request to set a game to play + bool _process_set_max_kibbles_req(String req_str); // set max kibbles + bool _process_set_kibbles_thresh_req(String req_str); // set kibbles detection threshold + bool _process_set_dst_req(String req_str); // set daylight saving time on/off + bool _process_set_timezone_req(String req_str); // set the timezone + bool _process_set_hub_mode_req(String req_str); // set a new hub mode + bool _process_set_schedule_req(String req_str); // set scheduler settings + bool _process_get_req(String req_str); // http get request for showing cleverpet.local webpage HubInterface * _hub; GameManager * _gameMan; @@ -53,11 +48,11 @@ class ConfigManager // ***************** const EEPROM addresses ***************** - const int _EVER_STORED_EEP_ADDRESS = 10; // EVER_STORED_ADDRESS = 10; - const int _GAME_EEP_ADDRESS = 20; // GAME_ADDRESS = 20; - const int _TIME_ZONE_EEP_ADDRESS = 30; // TIME_ZONE_ADDRESS = 30; - const int _DST_EEP_ADDRESS = 40; // DST_ADDRESS = 40; - const int _HUB_MODE_EEP_ADDRESS = 50; // HUB_MODE_ADDRESS = 50; + const int _EVER_STORED_EEP_ADDRESS = 10; + const int _GAME_EEP_ADDRESS = 20; + const int _TIME_ZONE_EEP_ADDRESS = 30; + const int _DST_EEP_ADDRESS = 40; + const int _HUB_MODE_EEP_ADDRESS = 50; const int _SCHED_WEEKDAY_FROM_ADDRESS = 60; const int _SCHED_WEEKDAY_TO_ADDRESS = 160; const int _SCHED_WEEKEND_FROM_ADDRESS = 260; @@ -66,8 +61,9 @@ class ConfigManager // ***************** const other ***************** - // we can change this number to force an eeprom "reset" to defaults; and to avoid undefined state when adding new variables - const int _EVER_STORED_CHECK_VALUE = 12357; // EVER_STORED_CHECK_VALUE = 12346; + // if this number does not match the number stored at EEPROM address _EVER_STORED_EEP_ADDRESS, eeprom will be "reset" to defaults + // this number should be changed to avoid an undefined value at any new addresses added above + const int _EVER_STORED_CHECK_VALUE = 12357; const int _HUB_MODE_STAY_OFF = 0; const int _HUB_MODE_STAY_ON = 1; @@ -78,19 +74,21 @@ class ConfigManager const int _HUB_STATE_INIT = 2; // to force initialization // ***************** config vars ***************** - + + // these are variables set by the user via the cleverpet.local web page and local http server + int _hub_mode; // _HUB_MODE_STAY_OFF, _HUB_MODE_STAY_ON, _HUB_MODE_SCHEDULED int _last_hub_mode; - int _hub_state; // _HUB_STATE_STANDBY, _HUB_STATE_ACTIVE + int _hub_state; // _HUB_STATE_STANDBY, _HUB_STATE_ACTIVE, _HUB_STATE_INIT - int _game_to_play; // GAME_TO_PLAY; - int _next_game_to_play; // NEXT_GAME_TO_PLAY; - int _new_game_selected; // selected (clicked) via web interface + int _game_to_play; + int _next_game_to_play; + int _new_game_selected; - float _time_zone_offset; // TIME_ZONE_OFFSET = 0.0; - bool _dst_on; // DST_ON = false; + float _time_zone_offset; + bool _dst_on; String _weekday_from; String _weekday_to; @@ -103,7 +101,8 @@ class ConfigManager int _kibbles_eaten_today; int _last_day; - + + // mdns reconnect variables unsigned long _last_mdns_reconnect_attempt; unsigned long _last_request_time; MDNS * mgschwan_mdns; diff --git a/src/game-manager.cpp b/src/game-manager.cpp index 7ba790a..ce0c647 100644 --- a/src/game-manager.cpp +++ b/src/game-manager.cpp @@ -67,6 +67,7 @@ bool GameManager::Run() { if (_game_enabled) { + Log.trace(" [[GameManager::Run()]]: 1!"); bool trial_done = false; // TODO simplify this / move to a function when cleaning up @@ -141,6 +142,7 @@ bool GameManager::Run() } _trial_just_done = trial_done; + Log.trace(" [[GameManager::Run()]]: 2!"); } else { diff --git a/src/game_helper_functions.cpp b/src/game_helper_functions.cpp index 324ed92..d99ab85 100644 --- a/src/game_helper_functions.cpp +++ b/src/game_helper_functions.cpp @@ -4,15 +4,6 @@ #include // random_shuffle -// Use primary serial over USB interface for logging output (9600) -// Choose logging level here (ERROR, WARN, INFO) -SerialLogHandler logHandler(LOG_LEVEL_INFO, { // Logging level for all messages - { "app.hackerpet", LOG_LEVEL_ERROR }, // Logging level for library messages - { "app", LOG_LEVEL_INFO } // Logging level for application messages -}); - - - /** * Helper functions * ---------------- diff --git a/src/http-server-util.cpp b/src/http-server-util.cpp index db76510..6b2b16c 100644 --- a/src/http-server-util.cpp +++ b/src/http-server-util.cpp @@ -1,4 +1,5 @@ -#include +#include "../lib/MDNS/src/MDNS.h" + using namespace mdns; #include "http-server-util.h" diff --git a/src/http-server-util.h b/src/http-server-util.h index 1dc1fb1..88941b2 100644 --- a/src/http-server-util.h +++ b/src/http-server-util.h @@ -3,7 +3,7 @@ #include -#include +#include "../lib/MDNS/src/MDNS.h" using namespace mdns; using namespace std; diff --git a/webpage/assets/cleverpet-logo.png b/webpage/assets/cleverpet-logo.png new file mode 100644 index 0000000..1258e40 Binary files /dev/null and b/webpage/assets/cleverpet-logo.png differ diff --git a/webpage/assets/index.3556f265.css b/webpage/assets/index.3556f265.css new file mode 100644 index 0000000..28f903f --- /dev/null +++ b/webpage/assets/index.3556f265.css @@ -0,0 +1 @@ +*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:-webkit-sticky;position:sticky}.inset-0{top:0px;right:0px;bottom:0px;left:0px}.top-0{top:0px}.z-10{z-index:10}.m-0{margin:0}.-m-1{margin:-.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-10{margin-bottom:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mb-4{margin-bottom:1rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.-mb-2{margin-bottom:-.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-16{height:4rem}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-6{height:1.5rem}.h-5{height:1.25rem}.h-4{height:1rem}.max-h-72{max-height:18rem}.w-14{width:3.5rem}.w-full{width:100%}.w-11{width:2.75rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-4{width:1rem}.w-10{width:2.5rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.scroll-py-2{scroll-padding-top:.5rem;scroll-padding-bottom:.5rem}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-4{gap:1rem}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.overflow-y-auto{overflow-y:auto}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.rounded{border-radius:.25rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-0{border-width:0px}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-b{border-bottom-width:1px}.border-indigo-500{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(243 244 246 / var(--tw-border-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.bg-opacity-25{--tw-bg-opacity: .25}.p-4{padding:1rem}.p-2{padding:.5rem}.p-0{padding:0}.px-4{padding-left:1rem;padding-right:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pt-1{padding-top:.25rem}.pt-5{padding-top:1.25rem}.pr-4{padding-right:1rem}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-xs{font-size:.75rem;line-height:1rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-medium{font-weight:500}.font-bold{font-weight:700}.font-thin{font-weight:100}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity))}.placeholder-gray-500:-ms-input-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity))}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.transition-colors{transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition{transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.page-bg{background-color:#e5e5e5}.focus\:border-indigo-500:focus{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-0:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-green-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity))}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}@media (min-width: 640px){.sm\:ml-6{margin-left:1.5rem}.sm\:flex{display:flex}.sm\:w-1\/2{width:50%}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\:h-20{height:5rem}.md\:w-20{width:5rem}.md\:p-20{padding:5rem}.md\:py-5{padding-top:1.25rem;padding-bottom:1.25rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}.md\:text-7xl{font-size:4.5rem;line-height:1}.md\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}} diff --git a/webpage/assets/index.42fe4937.js b/webpage/assets/index.42fe4937.js new file mode 100644 index 0000000..c9883e1 --- /dev/null +++ b/webpage/assets/index.42fe4937.js @@ -0,0 +1 @@ +const s=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))n(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&n(o)}).observe(document,{childList:!0,subtree:!0});function i(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerpolicy&&(r.referrerPolicy=e.referrerpolicy),e.crossorigin==="use-credentials"?r.credentials="include":e.crossorigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(e){if(e.ep)return;e.ep=!0;const r=i(e);fetch(e.href,r)}};s(); diff --git a/webpage/assets/logo.d8ebc7ad.png b/webpage/assets/logo.d8ebc7ad.png new file mode 100644 index 0000000..ffade0d Binary files /dev/null and b/webpage/assets/logo.d8ebc7ad.png differ diff --git a/webpage/create_arrays.py b/webpage/create_arrays.py new file mode 100644 index 0000000..ab79699 --- /dev/null +++ b/webpage/create_arrays.py @@ -0,0 +1,96 @@ + + + +import sys +import os +import subprocess + + +def main(): + + N_arrays = 40 + # TODO THIS IS JUST A SIZE EXPERIMENT! + # THIS WON'T WORK ON PARTICLE + # IT REFERENCES .css, .js that are not included! + # testing whether size is good, how much remains, if those are assumed loaded from elsewhere + # we will need text version backup in this case + + #input_file = 'cleverpet_minified/index.html' + + input_file = 'index_combined.html' + + # use work folder + + whole_string = '' + #lines = [] + f = open(input_file, 'r') + + all_cpp_lines = '' + + done = False + while not done: + line = f.readline() + + # strip out whitespace at start of line + line = line.lstrip(' ') + + #print(int(line[-1])) + if line: + #lines.append(line) + whole_string += line + else: + done = True + + print(len(whole_string)) + + chars_per_array = int(len(whole_string) / N_arrays) + + for k in range(N_arrays): + # print this section to a tmp html file + tmp_html_file = 'work/html_piece_' + str(k) + '.tmp' + tmp_out_file = 'work/cpp_piece_' + str(k) + '.h' + f_out = open(tmp_html_file, 'w') + + l_start = k * chars_per_array + l_end = (k + 1) * chars_per_array + + if k == N_arrays - 1: + l_end = len(whole_string) + + print(l_start, l_end, l_end-l_start) + + lines_to_write = whole_string[l_start:l_end] + f_out.write(lines_to_write) + f_out.close() + + # run bin2c + subprocess.run(["bin2c", "-H", tmp_out_file, tmp_html_file]) + + # load line and append to all lines + f2 = open(tmp_out_file, 'r') + code_line = None + for line in f2.readlines(): + if line.startswith('static const unsigned char'): + code_line = line + code_line = code_line.replace('static const unsigned char', 'static const char') + + if code_line is not None: + all_cpp_lines += code_line + else: + print("ERROESJPOGJSIOPDGOSDHGIOHSDIGHSDUIGIZXSDJBGHSDUIHGJUISDHGFUIOSDBH!@!!!!!!") + assert False + + f.close() + + f = open('complete_html_code.h', 'w') + f.write(all_cpp_lines) + f.close() + + + for k in range(N_arrays): + #print('Log.info(bin2c_html_piece_' + str(k) + '_tmp);') + print('_webclient.print(bin2c_html_piece_' + str(k) + '_tmp);') + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/webpage/index_combined.html b/webpage/index_combined.html new file mode 100644 index 0000000..5d7866a --- /dev/null +++ b/webpage/index_combined.html @@ -0,0 +1,654 @@ + + + + + + + Clever Pet + + + + + + + +
+
+
+
+
+ + + +

+ You hub status is active +

+
+
+

30

+

+ Kibbles eaten today +

+
+
+
+ +
+ Date and Time settings +
+
+
+
+
+

Date and Time

+ Wed Mar 23 2022 23:40:11 +
+
+
+
+
+
+

Apply Daylight Savings

+ + +
+
+
+
+
+
+

Timezone

+

America/Los Angeles

+ +
+
+
+
+
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+
+
+
+ +
+ +
+ Kibbles settings +
+
+
+
+
+

Max Kibbles Per Day

+

40

+ +
+
+
+
+
+
+ +
+ Select Game +
+
+
+
    +
  • +
    + +
    +

    Eating the food

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Exploring the Touchpads

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Engaging Consistently

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Avoiding Unlit Touchpads

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Learning the Lights

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Mastering the Lights

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Responding Quickly

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Learning Brightness

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Learning Double Sequences

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Learning Longer Sequences

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Matching Two Colors

    +

    +
    +
    +
  • +
  • +
    + +
    +

    Matching More Colors

    +

    +
    +
    +
  • +
+
+
+
+
+ + + + + + + + + + + + +