Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
4255 lines (3745 sloc)
138 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
VoffCon is a system for controlling devices and appliances from anywhere. | |
It consists of two programs. A "node server" and a "device server". | |
Copyright (C) 2016 Gudjon Holm Sigurdsson | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, version 3 of the License. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <http://www.gnu.org/licenses/>. | |
You can contact the author by sending email to gudjonholm@gmail.com or | |
by regular post to the address Haseyla 27, 260 Reykjanesbar, Iceland. | |
Program for Board: NodeMCU 1.0 (ESP-12E Module) | |
*/ | |
#include <ESP8266WiFi.h> | |
#include <WiFiClient.h> | |
#include <ESP8266HTTPClient.h> | |
#include <ESP8266WebServer.h> | |
#include <ESP8266mDNS.h> | |
#define INT_FIRSTNODEMCU 123 | |
const int ERROR_NUMBER = -9999; | |
enum OBJECTTYPE { | |
OBJECTTYPE_KEYVALUE_STRING, | |
OBJECTTYPE_KEYVALUE_INT, | |
OBJECTTYPE_PINS_ARRAY, | |
OBJECTTYPE_PIN, | |
OBJECTTYPE_PINS, | |
OBJECTTYPE_DATE, | |
OBJECTTYPE_WHITELIST_ARRAY, | |
OBJECTTYPE_STATUS, | |
OBJECTTYPE_LOG_PINS, | |
OBJECTTYPE_INFORMATION, | |
OBJECTTYPE_WARNING, | |
OBJECTTYPE_ERROR, | |
/*add next type above this line*/ | |
OBJECTTYPE_COUNT | |
}; | |
enum JSONTYPEKEY { | |
KEYVALUE_STRING, | |
KEYVALUE_INT, | |
KEYVALUE_DOUBLE | |
}; | |
/// <summary> | |
/// Possible types of a pin are: | |
/// <para/>PINTYPE_INPUT_ANALOG : "Read method analogRead shall be used" | |
/// <para/>PINTYPE_INPUT_DIGITAL : "Read method digitalRead shall be used" | |
/// <para/>PINTYPE_OUTPUT_ANALOG : "Write method analogWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_DIGITAL : "Write method digitalWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_VIRTUAL : "A pin not connected to hardware, but can store values" | |
/// </summary> | |
enum PINTYPE { | |
PINTYPE_INPUT_ANALOG, | |
PINTYPE_INPUT_DIGITAL, | |
PINTYPE_OUTPUT_ANALOG, | |
PINTYPE_OUTPUT_DIGITAL, | |
PINTYPE_OUTPUT_VIRTUAL | |
}; | |
/// <summary> | |
/// Class for handling GPIO pins | |
/// Use it to read from or write to a pin | |
/// </summary> | |
class GPin { | |
private: | |
int mNumber; | |
int mValue; | |
PINTYPE mType; | |
char *mName; | |
#ifdef ESP32 | |
#define LEDC_TIMER_13_BIT 13 | |
#define LEDC_BASE_FREQ 8100 | |
int mChannel; | |
void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255); | |
void analogWriteEsp32(); | |
#endif | |
void init(const char*strPinName, PINTYPE pinType, int pinNumber, int pinValue); | |
void set(int number, int value); | |
String jsonKeyValue(String key, int value); | |
void destroy(); | |
public: | |
#ifdef ESP32 | |
GPin(const char*strPinName, PINTYPE pinType, int pinNumber, int pinValue, uint8_t pinChannel); | |
#else | |
GPin(const char *strPinName, PINTYPE pinType, int pinNumber, int pinValue); | |
#endif | |
~GPin(); | |
void setName(const char*strPinName); | |
String getName(); | |
void setValue(int value); | |
int getValue(bool readValueFromHardware = true); | |
int getNumber(); | |
int getType(); | |
String toJson(); | |
String toJsonKeyValue(); | |
}; | |
/// <summary> | |
/// This class stores GPin objects which connect the server to device hardware pins | |
///<para/> | |
/// </summary> | |
class GPins { | |
private: | |
int mLength = 0; | |
#ifdef ESP32 | |
int mChannelCount = 0; | |
#endif | |
GPin *mPins[30];//todo: I make this dynamic, instead of a fixed size | |
int indexOf(int pinNumber); | |
public: | |
int addPin(const char *strPinName, PINTYPE pinType, int pinNumber, int pinValue); | |
boolean setValue(int pinNumber, int newValue); | |
boolean exists(int pinNumber); | |
GPin *get(int pinNumber); | |
int getValue(int pinNumber); | |
int count(); | |
String toJson(); | |
String JsonPinout(); | |
GPins(); | |
}; | |
#ifndef CODE_BLOCK_LinkedList | |
//do not remove the comment below this line | |
//INSERT_FROM_HERE | |
// --------------------------------------------------------------- | |
// LinkedList.h - V1.1 - Generic LinkedList implementation | |
// For instructions, go to https://github.com/ivanseidel/LinkedList | |
// Created by Ivan Seidel Gomes, March, 2013. | |
// Released into the public domain. | |
// --------------------------------------------------------------- | |
template<class T> | |
struct ListNode | |
{ | |
T data; | |
ListNode<T> *next; | |
}; | |
/// <summary> | |
/// A general linked list ready to be used or Inherited | |
/// </summary> | |
template <typename T> | |
class LinkedList { | |
protected: | |
int _size; | |
ListNode<T> *root; | |
ListNode<T> *last; | |
// Helps "get" method, by saving last position | |
ListNode<T> *lastNodeGot; | |
int lastIndexGot; | |
// isCached should be set to FALSE | |
// everytime the list suffer changes | |
bool isCached; | |
ListNode<T>* getNode(int index); | |
public: | |
/// <summary> | |
/// The list constructor | |
/// </summary> | |
LinkedList(); | |
~LinkedList(); | |
/// <summary> | |
/// Returns current size of LinkedList | |
/// </summary> | |
/// <returns>a int number</returns> | |
virtual int size(); | |
/// <summary> | |
/// Adds a T object in the specified index; | |
/// Unlinkand link the LinkedList correcly; | |
/// Increment _size | |
/// </summary> | |
/// <param name="index">Where to add the object in the zero based index</param> | |
/// <param name="T">The object to be added</param> | |
/// <returns>Success: true. Fail: false.</returns> | |
virtual bool add(int index, T); | |
/// <summary> | |
/// Adds a T object in the end of the LinkedList; | |
/// Increment _size; | |
/// </summary> | |
/// <param name="T">The object</param> | |
virtual bool add(T); | |
/// <summary> | |
/// Adds a T object in the start of the LinkedList; | |
/// Increment _size; | |
/// </summary> | |
/// <param name="T">The object to be added at beginning of list</param> | |
/// <returns></returns> | |
virtual bool unshift(T); | |
/// <summary> | |
/// Set the object at index, with T; | |
/// Increment _size; | |
/// </summary> | |
/// <param name="index">Zero based index of where the object is</param> | |
/// <param name="T">The object which values will be overwritten</param> | |
/// <returns></returns> | |
virtual bool set(int index, T); | |
/// <summary> | |
/// Remove object at index; | |
/// If index is not reachable, returns false; | |
/// else, decrement _size | |
/// </summary> | |
/// <param name="index"></param> | |
/// <returns>Success: The object which was removed. Fail: an object created with the default constructor</returns> | |
virtual T remove(int index); | |
/// <summary> | |
/// Remove last object; | |
/// </summary> | |
/// <returns>The data of the removed object</returns> | |
virtual T pop(); | |
/// <summary> | |
/// Remove first object; | |
/// </summary> | |
virtual T shift(); | |
/// /// <summary> | |
/// Get the index'th element on the list; | |
/// Return Element if accessible, | |
/// else, return false; | |
/// </summary> | |
/// <param name="index">Zero based index if the object in the list</param> | |
/// <returns>Success: the object. Fail: A object created with a default constructor</returns> | |
virtual T get(int index); | |
/// <summary> | |
/// Clear the entire array | |
/// That is remove all objects from the list and delete them from memory | |
/// </summary> | |
virtual void clear(); | |
}; | |
// Initialize LinkedList with false values | |
template<typename T> | |
LinkedList<T>::LinkedList() | |
{ | |
root = NULL; | |
last = NULL; | |
_size = 0; | |
lastNodeGot = root; | |
lastIndexGot = 0; | |
isCached = false; | |
} | |
// Clear Nodes and free Memory | |
template<typename T> | |
LinkedList<T>::~LinkedList() | |
{ | |
ListNode<T>* tmp; | |
while (root != NULL) | |
{ | |
tmp = root; | |
root = root->next; | |
delete tmp; | |
} | |
last = NULL; | |
_size = 0; | |
isCached = false; | |
} | |
/* | |
Actualy "logic" coding | |
*/ | |
template<typename T> | |
ListNode<T>* LinkedList<T>::getNode(int index) { | |
int _pos = 0; | |
ListNode<T>* current = root; | |
// Check if the node trying to get is | |
// immediatly AFTER the previous got one | |
if (isCached && lastIndexGot <= index) { | |
_pos = lastIndexGot; | |
current = lastNodeGot; | |
} | |
while (_pos < index && current) { | |
current = current->next; | |
_pos++; | |
} | |
// Check if the object index got is the same as the required | |
if (_pos == index) { | |
isCached = true; | |
lastIndexGot = index; | |
lastNodeGot = current; | |
return current; | |
} | |
return NULL; | |
} | |
template<typename T> | |
int LinkedList<T>::size() { | |
return _size; | |
} | |
template<typename T> | |
bool LinkedList<T>::add(int index, T _t) { | |
if (index >= _size) | |
return add(_t); | |
if (index == 0) | |
return unshift(_t); | |
ListNode<T> *tmp = new ListNode<T>(), | |
*_prev = getNode(index - 1); | |
tmp->data = _t; | |
tmp->next = _prev->next; | |
_prev->next = tmp; | |
_size++; | |
isCached = false; | |
return true; | |
} | |
template<typename T> | |
bool LinkedList<T>::add(T _t) { | |
ListNode<T> *tmp = new ListNode<T>(); | |
tmp->data = _t; | |
tmp->next = NULL; | |
if (root) { | |
// Already have elements inserted | |
last->next = tmp; | |
last = tmp; | |
} | |
else { | |
// First element being inserted | |
root = tmp; | |
last = tmp; | |
} | |
_size++; | |
isCached = false; | |
return true; | |
} | |
template<typename T> | |
bool LinkedList<T>::unshift(T _t) { | |
if (_size == 0) | |
return add(_t); | |
ListNode<T> *tmp = new ListNode<T>(); | |
tmp->next = root; | |
tmp->data = _t; | |
root = tmp; | |
_size++; | |
isCached = false; | |
return true; | |
} | |
template<typename T> | |
bool LinkedList<T>::set(int index, T _t) { | |
// Check if index position is in bounds | |
if (index < 0 || index >= _size) | |
return false; | |
getNode(index)->data = _t; | |
return true; | |
} | |
template<typename T> | |
T LinkedList<T>::pop() { | |
if (_size <= 0) | |
return T(); | |
isCached = false; | |
if (_size >= 2) { | |
ListNode<T> *tmp = getNode(_size - 2); | |
T ret = tmp->next->data; | |
delete(tmp->next); | |
tmp->next = NULL; | |
last = tmp; | |
_size--; | |
return ret; | |
} | |
else { | |
// Only one element left on the list | |
T ret = root->data; | |
delete(root); | |
root = NULL; | |
last = NULL; | |
_size = 0; | |
return ret; | |
} | |
} | |
template<typename T> | |
T LinkedList<T>::shift() { | |
if (_size <= 0) | |
return T(); | |
if (_size > 1) { | |
ListNode<T> *_next = root->next; | |
T ret = root->data; | |
delete(root); | |
root = _next; | |
_size--; | |
isCached = false; | |
return ret; | |
} | |
else { | |
// Only one left, then pop() | |
return pop(); | |
} | |
} | |
template<typename T> | |
T LinkedList<T>::remove(int index) { | |
if (index < 0 || index >= _size) | |
{ | |
return T(); | |
} | |
if (index == 0) | |
return shift(); | |
if (index == _size - 1) | |
{ | |
return pop(); | |
} | |
ListNode<T> *tmp = getNode(index - 1); | |
ListNode<T> *toDelete = tmp->next; | |
T ret = toDelete->data; | |
tmp->next = tmp->next->next; | |
delete(toDelete); | |
_size--; | |
isCached = false; | |
return ret; | |
} | |
template<typename T> | |
T LinkedList<T>::get(int index) { | |
ListNode<T> *tmp = getNode(index); | |
return (tmp ? tmp->data : T()); | |
} | |
template<typename T> | |
void LinkedList<T>::clear() { | |
while (size() > 0) | |
shift(); | |
} | |
#endif //CODE_BLOCK_LinkedList | |
/// <summary> | |
/// Used to store only Number and value of a pin | |
/// </summary> | |
struct PinValue | |
{ | |
int pinNumber; | |
int pinValue; | |
}; | |
class GUrl { | |
private: | |
int mLength = 0; | |
public: | |
GUrl() { } | |
int toNumber(String str); | |
String jsonKeyValue(String key, String value); | |
String jsonKeyValue(String key, int value); | |
String jsonObjectType(unsigned int uiType); | |
String makeStatusResponceJson(String jsonPins, String jsonWhitelist, String jsonDate); | |
String makePostLogPinsJson(String deviceId, String jsonPins); | |
String makeHttpStatusCodeString(unsigned int uiStatusCode); | |
String jsonRoot(unsigned int uiType, String key, String value); | |
}; | |
class KeyValue { | |
private: | |
unsigned int mKeyValueType; | |
String mKey; | |
String mstrValue; | |
int miValue; | |
double mdValue; | |
void set(unsigned int keyValueType, String key, String strValue, int iValue, double dValue); | |
public: | |
KeyValue(String key, String value); | |
KeyValue(String key, int value); | |
KeyValue(String key, double value); | |
String getKey(); | |
String getValueString(boolean addDoubleQuotationIfString); | |
String toJson(); | |
}; | |
/// <summary> | |
/// The errornumber to return when a numberfunction fails; | |
/// Like: toInt(), toFloat(), toLong(), toULong() | |
/// </summary> | |
#define JSONDATA_ERRORNUMBER 999999999 | |
//See informanation on JSON on https://www.json.org (nice pictures) | |
//See more info here : https://www.crockford.com/mckeeman.html | |
//todo: Hexadecimal digits can be represented as u and then 4 hexadecimal digits. (\u hex hex hex hex) | |
// hex can be ('0'..'9' or 'A'..'F' or 'a'..'f') | |
//todo: Exponent numbers see second answser: https://stackoverflow.com/questions/19554972/json-standard-floating-point-numbers | |
// example1: 1e-005 example2: 2.99792458e8 Exponent can be ('E' sign digits) or ('e' sign digits) | |
/// <summary> | |
/// Enumeration for which type of json object the json data is. | |
/// </summary> | |
enum JSONTYPE { | |
JSONTYPE_INVALID, | |
JSONTYPE_ARRAY, | |
JSONTYPE_OBJECT, | |
JSONTYPE_KEY_VALUE, | |
JSONTYPE_STRING, | |
JSONTYPE_ULONG, | |
JSONTYPE_LONG, | |
JSONTYPE_FLOAT, | |
JSONTYPE_BOOL, | |
JSONTYPE_NULL, | |
}; | |
/// <summary> | |
/// Class for representing elements and objects in a json object. | |
/// You can use it to browse and get values and add and remove objects from a json. | |
/// </summary> | |
/// <example> | |
/// Example hello World: | |
/// @code{.xml} | |
/// // Create an json object with one key named "hello" | |
/// // and one value which is the string "world" | |
/// JsonData js("{\"hello\":\"world\"}"); | |
/// @endcode | |
/// </example> | |
class JsonData | |
{ | |
private: | |
String mValue; | |
JSONTYPE mType; | |
JSONTYPE mValueType; | |
JsonData *mFirstChild{}, | |
*mNext{}, | |
*mParent{}; | |
JsonData(JSONTYPE type, JsonData* parent); | |
JsonData(String jsonString, JsonData* parent); | |
JsonData(String value, JSONTYPE type, JSONTYPE valueType, JsonData* parent); | |
static bool isClosingToken(char c); | |
static char getClosingToken(char openingToken); | |
static int getIndexOfClosingToken(String* string, bool ignoreStrings); | |
static bool isDigit(char c); | |
static bool removeLast(JsonData* pNode); | |
static bool destroyIncludingChildren(JsonData* pNode); | |
static JSONTYPE getValueTypeFromChar(char firstCharInValue); | |
static JSONTYPE getType(String strValue); | |
static JsonData *getLastChild(JsonData* parent); | |
static JsonData *getLastNode(JsonData* previous); | |
static JsonData *getPointingNode(JsonData* findMe); | |
static JsonData *findPointingNode(JsonData* startFrom, JsonData* findMe); | |
static JsonData *getRootNode(JsonData* current); | |
void init(JSONTYPE type, JSONTYPE valueType, JsonData* parent); | |
void parse(const String jsonString, JsonData* parent); | |
JsonData *setRootInvalid(); | |
String valueToString(); | |
JsonData *array(String* elements, JsonData* parent, bool canBeMoreThanOne); | |
JsonData *object(String* members, JsonData* parent); | |
JsonData *elements(String* values, JsonData* parent); | |
bool getPairIndexes(String* pairs, bool& thereIsAnotherPair, | |
int &keyIndexOfFirstChar, int& keyLength, | |
int &valueIndexOfFirstChar, int& valueLength, | |
int &pairLength); | |
JsonData *members(String* pairs, JsonData* parent); | |
JsonData *pair(String* keyValues, JsonData* parent); | |
static bool validateValue(const JSONTYPE jsonvaluetype, String string); | |
JsonData *value(String* valuesString, JsonData* parent); | |
static String jsonTypeString(JSONTYPE type); | |
String toTree(JsonData* current, int level); | |
static bool isWhitespace(const char c); | |
public: | |
JsonData(const char* jsonString); | |
~JsonData(); | |
String toString(); | |
String toTree(); | |
static String trim(String jsonStringToTrim); | |
bool isValid() const; | |
/// <summary> | |
/// Checks if the current object has any child objects | |
/// </summary> | |
/// <returns> | |
/// true if this object has one ore more child object(s). Otherwize false | |
/// </returns> | |
bool hasChildren() { return this->mFirstChild != NULL; }; | |
JsonData *getChildAt(unsigned int index); | |
JsonData *getChild(String value); | |
JsonData *getNext(); | |
const String getValue(); | |
String getValueAsString(); | |
float getValueAsFloat(); | |
unsigned long getValueAsULong(); | |
long getValueAsLong(); | |
int getValueAsInt(); | |
JSONTYPE getType() const { return mType; } | |
JSONTYPE getValueType() const { return mValueType; } | |
}; | |
/// <summary> | |
/// A class for parsing json strings | |
/// </summary> | |
/// <example> | |
/// Example on how to create a json object from a string: | |
/// @code{.xml} | |
/// Json json("{\"hello\":\"world\",\"array\":[1,2,-4,-5.22,\"string in a array\"]}"); | |
/// @endcode | |
/// </example> | |
class Json | |
{ | |
JsonData *mData; | |
public: | |
Json(const char *jsonString); | |
~Json(); | |
String toString() const; | |
String toTree() const; | |
static String trim(String jsonStringToTrim); | |
bool isValid() const; | |
/// <summary> | |
/// Use this function access the root JsonData object | |
/// </summary> | |
/// <returns> | |
/// The root JsonData object. | |
/// If the object is invalid NULL is returned. | |
/// </returns> | |
JsonData *getRootObject() { return mData == NULL || !mData->isValid()? NULL: mData; }; | |
}; | |
/// <summary> | |
/// Used to store time. | |
/// Depended on which constuctor is used, values can be eather a "date and time" or a "counter" up to 49 days. | |
/// </summary> | |
class GTime { | |
/////////////////////////////////////////////////////////// | |
private: | |
int mYear = 0; | |
int mMonth = 0; | |
int mDay = 0; | |
int mHours = 0; | |
int mMinutes = 0; | |
int mSeconds = 0; | |
public: | |
GTime() { } | |
GTime(const GTime& gTime); | |
GTime(unsigned long milliSeconds); | |
void setTime(unsigned long milliSeconds); | |
boolean setTime(String strTime); | |
int strToMonth(String month); | |
static int toNumber(String str); | |
String toString(); | |
String toStreng(); | |
String toJson(); | |
int getYear(); | |
int getMonth(); | |
int getDay(); | |
int getHours(); | |
int getMinutes(); | |
int getSeconds(); | |
}; | |
/// <summary> | |
/// A list to store IP addresses | |
/// </summary> | |
class IPAddressList : public LinkedList<IPAddress*> { | |
private: | |
void destory(); | |
public: | |
bool add(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); | |
bool add(IPAddress address); | |
bool add(const char* strIpAddress); | |
bool exists(IPAddress address); | |
bool exists(String strIpaddress); | |
int indexOf(IPAddress address); | |
bool isEmpty(); | |
bool remove(const char *strIpAddress); | |
bool remove(IPAddress address); | |
String toJson(); | |
~IPAddressList(); | |
}; | |
/// <summary> | |
/// Used to monitor a pin. | |
/// Can monitor if a pin value changes more or less than something | |
/// and can monitor if a certain amount of time has passed. | |
/// </summary> | |
class PinWatch | |
{ | |
private: | |
GPin* pin; | |
unsigned long sampleSum = 0; // Sum of pin value samples | |
unsigned int pinValueLast = 0; // The pin value which was last logged. | |
unsigned int pinValueMargin = 0; // How much must a sampleSum / sampleCount change from pinValueLast to trigger a log. | |
int sampleCount = 0; // How many times has the pinValueSum been summerized. | |
int sampleTotalCount = 0; // How many samples before we can average sampleSum and compare with pinValueLast | |
unsigned long nextSampleTime; // When should we get the next sample | |
unsigned long sampleInterval; // How long between samples | |
unsigned long minLogInterval; // The minimum time allowed to pass between logs. Set to 0 to disable | |
unsigned long nextLogTime; // If minLogInterval is > 0 then this will be the time when we must log | |
// This time must be reset after each log. | |
void resetAllValues(); | |
void init(GPin* gPin, unsigned int pinValueMargin, int sampleTotalCount, unsigned long sampleInterval, unsigned long minLogInterval); | |
public: | |
PinWatch(GPin* gPin, unsigned int pinValueMargin, int sampleTotalCount, unsigned long sampleInterval, unsigned long minLogInterval); | |
PinWatch(const PinWatch& pinWatch); | |
void serialPrintValues(); | |
void serialPrintLnValues(); | |
bool check(unsigned long currentTimeInMillis); | |
void reset(unsigned long currentTimeInMillis, bool updateLastPinValue = true, bool updateMinLogInterval = true); | |
int getPinNumber(); | |
int getPinValue(); | |
int getPinType(); | |
bool isValidPin(); | |
String toJson(); | |
}; | |
/// <summary> | |
/// PinWatchList allows you to add one or more timers and/or monitor value changes on multiple pins | |
/// </summary> | |
/// <example> | |
/// Example: | |
/// @code{.xml} | |
/// PinWatchList watchList; | |
/// void setup(void) { | |
/// watchList.addPinValueMonitoring(devicePins.get(D0), 1, 1, 1000); | |
/// watchList.addPinValueMonitoringAndTimer(devicePins.get(D1), 2, 2, 1000, 1000 * 60 * 3); | |
/// watchList.addTimer(1000 * 30 * 9); | |
/// } | |
/// | |
/// void loop(void) { | |
/// if (watchList.isAnyPinWatchDo()) { | |
/// // One item did trigger so you could log | |
/// watchList.resetAllChecks(); | |
/// } | |
/// } | |
/// @endcode | |
/// </example> | |
class PinWatchList : public LinkedList<PinWatch*> { | |
private: | |
void destory(); | |
bool removeByIndex(int index); | |
bool add(GPin* gPin, int pinValueMargin, int sampleTotalCount, unsigned long sampleInterval, unsigned long minLogInterval); | |
bool addJsonPinWatchToList(JsonData* jsonObject, GPins* devicePins); | |
PinWatch* NewPinWatchFromJsonObject(JsonData* jsonObject, GPins* devicePins); | |
public: | |
bool add(PinWatch pinWatch); | |
bool add(PinWatch* ptrPinWatch); | |
bool update(int index, PinWatch pinWatch); | |
bool addOrUpdate(PinWatch pinWatch); | |
bool addTimer(unsigned long minLogInterval); | |
bool addPinValueMonitoring(GPin* gPin, int pinValueMargin, int sampleTotalCount, unsigned long sampleInterval); | |
bool addPinValueMonitoringAndTimer(GPin* gPin, int pinValueMargin, int sampleTotalCount, unsigned long sampleInterval, unsigned long minLogInterval); | |
bool exists(int pinNumber); | |
int indexOfPin(int pinNumber); | |
bool isEmpty(); | |
bool removePin(int pinNumber); | |
bool isAnyPinWatchDo(); | |
int getFirstPinWatchDo(); | |
int getNextPinWatchDo(int index); | |
void resetAllChecks(); | |
int updateMonitorFromJsonObject(JsonData* root, GPins *devicePins); | |
int deleteMonitorFromJsonObject(JsonData* root); | |
String toJson(); | |
~PinWatchList(); | |
}; | |
//INSERT_FROM_FILE_Json.h | |
GTime startTime; | |
GPins devicePins; | |
IPAddressList whiteList; | |
GUrl urlTool; | |
PinWatchList monitors; | |
////////////////////////////////////////////////////////////////////////////////////////////// | |
// // | |
// T H E V A L U E S T H A T M U S T B E C H A N G E D // | |
// // | |
////////////////////////////////////////////////////////////////////////////////////////////// | |
const char* deviceId = DEVICE_ID; | |
// Name of the wifi (accesspoint)network | |
// example: "guttisWiFi" | |
const char* ssid = WIFI_ACCESSPOINT; | |
// Wifi password | |
// example: "mypasswordToTheWifi" | |
const char* password = WIFI_PASSWORD; | |
// port number which this device will be operating on. | |
// example: 5100 | |
const int PORT = PORT_NUMBER; | |
// example: 6100 | |
const int voffconServerPort = VOFFCON_SERVER_PORT; | |
IPAddress | |
// ip address which this device will be operating on. | |
// Example: "192.168.1.158" | |
myIp(IPV4_IPADDRESS), | |
//the default gateway which this device will be operating on. | |
// example: "192.168.1.254" | |
gateway(IPV4_GATEWAY), | |
// the default gatway on this network. | |
// On windows goto command | |
// prompt and type "ipconfig" | |
// example: "255.255.255.0" | |
subnet(IPV4_SUBNET), | |
// example: "192.168.1.127" | |
voffconServerIp(VOFFCON_SERVER_IP); | |
// Additional information | |
// If the device is NOT connected, it's light is faint. | |
// If the device is connected it's light is bright. | |
// if you want a special address to be whiteListed you can add it here. | |
// remove "//" in the next line, if you want to allow requests(commands) from a specific ipaddress. | |
// #define WHITELIST_ADDRESS "89.17.157.231" | |
// boolean grantAccessToEverybody: | |
// Set to true if you want to allow all clients where the first 3 numbers | |
// in a client IP address are the same same as myIp (this server IP address). | |
boolean grantAccessToEverybody = true; | |
//boolean grantAccessToAllClientsOnSameSubnet: | |
//Set to true if you want to allow all clients where the first 3 numbers | |
//in a client IP address are the same same as myIp (this server IP address). | |
// | |
boolean grantAccessToAllClientsOnSameSubnet = true; | |
// boolean grantAccessToFirstCaller: | |
// set to true if you want to allow the first client to call the "/setup" method | |
// to be automaticly granted access. that is, client IP address will be whitelisted. | |
boolean grantAccessToFirstCaller = true; | |
ESP8266WebServer server(PORT); | |
//////////////////////// Global Functions /////////////////////////////// | |
String reportIn() { | |
Serial.println("Reporting in "); | |
String ret = "Fri, 1 Jan 1971 00:00:00 GMT"; | |
String data = "{" + | |
urlTool.jsonKeyValue("id", "\"" + String(deviceId) + "\",") + | |
urlTool.jsonKeyValue("ip", "\"" + WiFi.localIP().toString() + "\",") + | |
urlTool.jsonKeyValue("port", PORT) + | |
"}"; | |
HTTPClient http; | |
String host = voffconServerIp.toString() + ":" + String(voffconServerPort); | |
String url = "http://" + host + "/devices/reportin"; | |
http.begin(url); //Specify destination for HTTP request | |
http.addHeader("Content-Type", "application/json"); | |
http.addHeader("Connection", "close"); | |
Serial.println("sending"); | |
Serial.println(data); | |
int httpResponseCode = http.POST(data); //Send the actual POST request | |
if (httpResponseCode>0) { | |
String response = http.getString(); //Get the response to the request | |
Serial.println(httpResponseCode); //Print return code | |
Serial.println(response); //Print request answer | |
ret = response; //responce should contain date.toUTCString() | |
} | |
else { | |
Serial.print("Error on sending POST: "); | |
Serial.println(httpResponseCode); | |
} | |
http.end(); //Free resources | |
return ret; | |
} | |
void tellServerToSaveLog() { | |
Serial.println("Telling server to log the device pins"); | |
HTTPClient http; | |
String host = voffconServerIp.toString() + ":" + String(voffconServerPort); | |
String url = "http://" + host + "/logs/pins/" + deviceId; | |
http.begin(url); | |
http.addHeader("Connection", "close"); | |
Serial.print("Calling : "); Serial.println(url); | |
int httpResponseCode = http.GET(); //make the request to server | |
Serial.print("Responce code: "); Serial.println(httpResponseCode); //return code | |
Serial.println(http.getString()); //The response to the request | |
http.end(); //Free resources | |
} | |
void tellServerToSendMonitors() { | |
Serial.println("Telling server to send monitors"); | |
HTTPClient http; | |
String host = voffconServerIp.toString() + ":" + String(voffconServerPort); | |
String url = "http://" + host + "/monitors/update/" + deviceId; | |
http.begin(url); | |
http.addHeader("Connection", "close"); | |
Serial.print("Calling : "); Serial.println(url); | |
int httpResponseCode = http.GET(); //make the request to server | |
Serial.print("Responce code: "); Serial.println(httpResponseCode); //return code | |
Serial.println(http.getString()); //The response to the request | |
http.end(); //Free resources | |
} | |
void SerialPrint(String str, int value) { | |
Serial.print(str + " "); | |
Serial.print(value); | |
} | |
void SerialPrintLn(String str, String value, bool setMarkerAtBeginningAndEndOfValue = false) { | |
Serial.print(str + " "); | |
if (setMarkerAtBeginningAndEndOfValue) | |
value = "|" + value + "|"; | |
Serial.println(value); | |
} | |
void SerialPrintLn(String str, int value) { | |
SerialPrint(str, value); | |
Serial.println(); | |
} | |
/// <summary> | |
/// Parses Json text and extracts a key value from it. | |
/// The function assumes that the jsonText has only child objects of Json Key values. | |
/// Like so: {"key1":1,"key2":"string2"} | |
/// </summary> | |
/// <param name="jsonText"></param> | |
/// <param name="key"></param> The key of a Key value object; | |
/// <returns>Success: The value as a string. Fail: an empty string</returns> | |
String getKeyValueFromJsonAsString(String jsonText, String key) { | |
Json parser(jsonText.c_str()); | |
JsonData* root = parser.getRootObject(); | |
if (root == NULL || !root->isValid()) { | |
Serial.println("Invalid json root object"); | |
return ""; | |
}; | |
JsonData* current = root->getChild(key); | |
if (current == NULL) | |
return ""; | |
return current->getValueAsString(); | |
} | |
/// <summary> | |
/// Extracts pin numbers and values from the given string | |
/// and sets the pin values according to what was extracted. | |
/// </summary> | |
/// <param name="unParsedJson">A json string on the form { "3":220}</param> | |
/// <returns>true if successful otherwhise false</returns> | |
bool setPinValues(String jsonString, GPins* devicePins) { | |
Json parser(jsonString.c_str()); | |
JsonData* root = parser.getRootObject(); | |
if (root == NULL || !root->isValid()) { | |
Serial.println("Invalid json root object"); | |
return false; | |
}; | |
bool bRet = false; | |
JsonData* child, * current = root->getChildAt(0); | |
String name; | |
unsigned long ulValue; | |
while (current && current->getType() == JSONTYPE::JSONTYPE_KEY_VALUE) { | |
name = current->getValue(); | |
int pin = name.toInt(); | |
child = current->getChildAt(0); | |
if (child != NULL) { | |
ulValue = child->getValueAsULong(); | |
if (ulValue != JSONDATA_ERRORNUMBER) { | |
if (devicePins->setValue(pin, ulValue)) { | |
bRet = true; //set to true, if at least one value is changed | |
} | |
} | |
} | |
current = current->getNext(); | |
} | |
return bRet; | |
} | |
void handleRoot() { | |
sendText(200, "hello from esp8266!"); | |
} | |
void sendText(int statusCode, const char *strSend) { | |
server.send(statusCode, "text/plain", strSend); | |
} | |
void sendJson(int statusCode, const char *strJsonString) { | |
server.send(200, "application/json", strJsonString); | |
} | |
void handleNotFound() { | |
String message = "Invalid url\n\n"; | |
message += "URI: "; | |
message += server.uri(); | |
message += "\nMethod: "; | |
switch (server.method()) | |
{ | |
case HTTP_GET: message += "GET"; break; | |
case HTTP_POST: message += "POST"; break; | |
case HTTP_DELETE:message += "DELETE";break; | |
case HTTP_PUT: message += "PUT"; break; | |
default: message += "UNKNOWN"; | |
} | |
message += "\nBody: "; | |
if (server.hasArg("plain")) | |
{ | |
message +=server.arg("plain"); | |
} | |
else { | |
message += "No body found!"; | |
} | |
server.send(404, "text/plain", message); | |
} | |
void handlePins() { | |
Serial.println("handlePins client IP: " + server.client().remoteIP().toString()); | |
if (!isAuthorized()) return; | |
int pin; | |
int val; | |
if (server.method() == HTTP_POST) | |
{ | |
Serial.println("HTTP_POST"); | |
if (server.hasArg("plain")) | |
{ | |
Serial.println(server.arg("plain")); | |
bool b = setPinValues(server.arg("plain"), &devicePins); | |
} | |
} | |
if (server.method() == HTTP_POST) | |
{ | |
String name; | |
} | |
String itemJson = urlTool.jsonRoot(OBJECTTYPE_PINS_ARRAY, "pins", devicePins.toJson()); | |
server.send(200, "application/json", itemJson); | |
} | |
void handleStatus() { | |
Serial.println("handleStatus"); | |
if (!isAuthorized()) return; | |
int pin; | |
int val; | |
if (!server.method() == HTTP_GET) | |
{ | |
sendText(401, "Method not supported!"); | |
return; | |
} | |
String str = urlTool.makeStatusResponceJson(devicePins.toJson(), whiteList.toJson(), startTime.toJson()); | |
sendJson(200, str.c_str()); | |
Serial.println("Sending back"); | |
Serial.println(str); | |
} | |
void handleWhitelist() { | |
Serial.println("handleWhitelist "); | |
if (!isAuthorized()) return; | |
if (server.method() == HTTP_POST || server.method() == HTTP_DELETE) | |
{ | |
String key; | |
String value; | |
if (server.hasArg("plain")) | |
{ | |
// plain: {"ipaddress":"1.2.3.4"} | |
Serial.print("hasArgs:"); | |
Serial.println(server.arg("plain")); | |
value = getKeyValueFromJsonAsString(server.arg("plain"), "ipaddress"); | |
Serial.println("value: " + value); | |
if (server.method() == HTTP_POST && !whiteList.exists(value)) { | |
if (value.length() > 6 && whiteList.add(value.c_str())) | |
Serial.println("Whitelisting " + value); | |
} | |
else if (server.method() == HTTP_DELETE) { | |
if (whiteList.remove(value.c_str())) | |
Serial.println("Removing from whitelist: " + value); | |
} | |
} | |
}//if (server.method() == HTTP_POST || server.method() == HTTP_DELETE) | |
sendJson(200, whiteList.toJson().c_str()); | |
} | |
String getTime() { | |
WiFiClient client; | |
while (!!!client.connect("google.com", 80)) { | |
Serial.println("connection failed, retrying..."); | |
} | |
client.print("HEAD / HTTP/1.1\r\n\r\n"); | |
while (!!!client.available()) { | |
yield(); | |
} | |
while (client.available()) { | |
if (client.read() == '\n') { | |
if (client.read() == 'D') { | |
if (client.read() == 'a') { | |
if (client.read() == 't') { | |
if (client.read() == 'e') { | |
if (client.read() == ':') { | |
client.read(); | |
String theDate = client.readStringUntil('\r'); | |
client.stop(); | |
return theDate; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
return ""; | |
} | |
void handleStarted() { | |
Serial.println("client IP: " + server.client().remoteIP().toString()); | |
String itemJson = urlTool.jsonRoot(OBJECTTYPE_DATE, "date", startTime.toJson()); | |
server.send(200, "application/json", itemJson); | |
} | |
// show the mappings of the pins | |
void handlePinout() { | |
Serial.println("client IP: " + server.client().remoteIP().toString()); | |
server.send(200, "application/json", devicePins.JsonPinout()); | |
} | |
void handleMonitors() { | |
String uri = server.uri(); | |
Serial.println("Uri"); Serial.println(uri); | |
if ( ( server.method() == HTTP_POST || server.method() == HTTP_DELETE ) | |
&& server.hasArg("plain") ) | |
{ | |
Json parser( server.arg("plain").c_str() ); | |
if (parser.isValid()) { | |
if (server.method() == HTTP_POST) { | |
Serial.println("Valid post object parsed"); | |
int iCount = monitors.updateMonitorFromJsonObject(parser.getRootObject(), &devicePins); | |
Serial.print("updated "); Serial.print(iCount); Serial.println(iCount > 1 ? " PinWatches" : " PinWatch"); | |
} | |
else if (server.method() == HTTP_DELETE) { | |
Serial.println("Valid delete object parsed"); | |
int iCount = monitors.deleteMonitorFromJsonObject(parser.getRootObject()); | |
Serial.print("deleted "); Serial.print(iCount); Serial.println(iCount > 1 ? " PinWatches" : " PinWatch"); | |
} | |
} | |
else { | |
Serial.println("Invalid Json object"); | |
} | |
} | |
Serial.println("client IP: " + server.client().remoteIP().toString()); | |
server.send(200, "application/json", monitors.toJson()); | |
} | |
void handleCustom() { | |
String uri = server.uri(); | |
Serial.println("Uri"); Serial.println(uri); | |
if ((server.method() == HTTP_POST || server.method() == HTTP_DELETE) | |
&& server.hasArg("plain")) | |
{ | |
Serial.println(server.arg("plain")); | |
Json parser(server.arg("plain").c_str()); | |
if (parser.isValid()) { | |
if (server.method() == HTTP_POST) { | |
Serial.println("Valid post object parsed"); | |
} | |
else if (server.method() == HTTP_DELETE) { | |
Serial.println("Valid delete object parsed"); | |
} | |
} | |
else { | |
Serial.println("Invalid Json object"); | |
} | |
} | |
String strSend = "{\"custom\":\"Some responce for getting started\",\"val\":123}"; | |
Serial.println("Sending custom response-----"); | |
Serial.println("client IP: " + server.client().remoteIP().toString()); | |
server.send(200, "application/json", strSend); | |
} | |
//returns: | |
//true : if the calling client is autorized; | |
// false: if calling client is not autorized and send the text | |
// "You are not authorized to use this function" to the client | |
boolean isAuthorized() { | |
//todo: remove next line when you want everybody to be able to access the server | |
if (grantAccessToEverybody) return true; | |
//todo: remove next line when you want to make this save | |
if (grantAccessToAllClientsOnSameSubnet) { | |
Serial.println("client[0]" + server.client().remoteIP()[0]); | |
Serial.println("server[0]" + WiFi.localIP()[0]); | |
if (server.client().remoteIP()[0] == WiFi.localIP()[0] && | |
server.client().remoteIP()[1] == WiFi.localIP()[1] && | |
server.client().remoteIP()[2] == WiFi.localIP()[2]) | |
{ | |
Serial.println("First 3 ip digits match"); | |
return true; | |
} | |
} | |
String strIP = server.client().remoteIP().toString(); | |
Serial.println("Client IP: " + strIP); | |
delay(10); | |
if (whiteList.exists(strIP)) { | |
Serial.println("whitelisted ip: " + strIP); | |
return true; | |
} | |
sendText(401, "You are not authorized to use this function"); | |
return false; | |
} | |
void handleSetup() { | |
if (grantAccessToFirstCaller && whiteList.size() == 0) | |
whiteList.add(server.client().remoteIP().toString().c_str()); //first one who calls this method will be whitelisted | |
if (!isAuthorized()) return; | |
sendText(200, "Todo: this function, maybe allow add pins later"); | |
} | |
void setup(void) { | |
Serial.begin(115200); | |
Serial.println("Connecting to : " + String(ssid)); | |
//WiFi.begin(ssid); | |
//WiFi.config(myIp, gateway, subnet); //this line can be skipped. only use if you want a specific ip address | |
WiFi.mode(WIFI_STA); | |
WiFi.begin(ssid, password); | |
Serial.println(""); | |
// Wait for connectionc | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
Serial.print("."); | |
} | |
Serial.println(""); | |
Serial.print("Connected to "); | |
Serial.println(ssid); | |
Serial.print("IP address: "); Serial.println(WiFi.localIP()); | |
Serial.print("Mac address: "); Serial.println(WiFi.macAddress()); | |
/*if (MDNS.begin("esp8266")) { | |
Serial.println("MDNS responder started"); | |
}*/ | |
//SETTING_UP_WHITELIST_START | |
//Do not remove line, here whitelist ip's will be added by VoffCon Node server | |
//SETTING_UP_WHITELIST_END | |
startTime.setTime(reportIn()); | |
server.on("/", handleRoot); | |
server.on("/pins", handlePins); | |
server.on("/whitelist",handleWhitelist); | |
server.on("/started", handleStarted); | |
server.on("/status", handleStatus); | |
server.on("/setup", handleSetup); | |
server.on("/pinout", handlePinout); | |
server.on("/monitors", handleMonitors); | |
server.on("/custom", handleCustom); | |
//todo: spurning um hvort við bætum við /addpins eða /add | |
//todo: spurning um hvort við bætum við /removepins eða /remove | |
server.on("/inline", []() { | |
server.send(200, "text/plain", "this works as well"); | |
}); | |
server.onNotFound(handleNotFound); | |
server.begin(); | |
Serial.println("HTTP server started"); | |
int startState = 700; | |
//SETTING_UP_PINS_START | |
PINTYPE type = PINTYPE_OUTPUT_ANALOG; | |
devicePins.addPin("D0", type, D0, startState); | |
devicePins.addPin("D1", type, D1, startState); | |
devicePins.addPin("D2", PINTYPE_INPUT_ANALOG, D2, 512); | |
devicePins.addPin("D3", PINTYPE_INPUT_DIGITAL, D3, startState); | |
devicePins.addPin("D4", PINTYPE_OUTPUT_DIGITAL, D4, startState); | |
devicePins.addPin("D5", type, D5, 123); | |
devicePins.addPin("D6", type, D6, startState); | |
devicePins.addPin("D7", type, D7, startState); | |
devicePins.addPin("D8", PINTYPE_OUTPUT_VIRTUAL, D8, 456); | |
//SETTING_UP_PINS_END | |
Serial.println(devicePins.toJson()); | |
//monitors.addTimer(1000 * 60 * 60 * 24); //once per day | |
//monitors.addPinValueMonitoringAndTimer(devicePins.get(D1), 1, 2, 500, (1000 * 60 * 60 * 24 * 6));//the 6 day timer will never ve triggered because of the other 1 day timer | |
tellServerToSendMonitors(); | |
} | |
void loop(void) { | |
if (monitors.isAnyPinWatchDo()) { | |
// One item did trigger so you could log | |
tellServerToSaveLog(); | |
monitors.resetAllChecks(); | |
} | |
server.handleClient(); | |
} | |
#ifndef CODE_BLOCK_GPin_impl | |
#ifdef ESP32 | |
void GPin::ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax) { | |
// calculate duty | |
uint32_t duty = (LEDC_BASE_FREQ / valueMax) * ((value) < (valueMax) ? (value) : (valueMax)); | |
//todo: remove all Serial.prints in this class | |
Serial.println("Analog Writing duty " + String(duty) + " to channel " + String(channel)); | |
// write duty to LEDC | |
ledcWrite(channel, duty); | |
} | |
// writes the current value of the pin to the GPO | |
void GPin::analogWriteEsp32() { | |
Serial.println("ledcAnalogWrite " + String(mValue) +" to pin " + String(mNumber) + " on channel " + String(mChannel)); | |
if (mValue == 0) { | |
//sometimes ledcAnalogWrite duty = 0 will not have any effect pins D0 - D7 seem to be ok, but others will have no effect | |
ledcAnalogWrite(mChannel, 1, 255); | |
} | |
ledcAnalogWrite(mChannel, mValue, 255); | |
//sigmaDeltaWrite(mChannel, mValue); | |
} | |
/// <summary> | |
/// Constructor for the GPin object | |
/// You will need to porovide name, type, number and a starting value of the pin | |
/// </summary> | |
/// <param name="strPinName">Name of the pin</param> | |
/// <param name="pinType">Type of the pin | |
/// <para/>Possible types of a pin are: | |
/// <para/>PINTYPE_INPUT_ANALOG : "Read method analogRead shall be used" | |
/// <para/>PINTYPE_INPUT_DIGITAL : "Read method digitalRead shall be used" | |
/// <para/>PINTYPE_OUTPUT_ANALOG : "Write method analogWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_DIGITAL : "Write method digitalWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_VIRTUAL : "A pin not connected to hardware, but can store values" | |
/// </param> | |
/// <param name="pinNumber">Number of the pin. That is the GPIO number</param> | |
/// <param name="pinValue">The value to set the pin to</param> | |
/// <param name="pinChannel">Pin channel is needed only for the esp32. todo: provide more decription</param> | |
GPin::GPin(const char*strPinName, PINTYPE pinType, int pinNumber, int pinValue, uint8_t pinChannel) { | |
mChannel = pinChannel; | |
init(strPinName, pinType, pinNumber, pinValue); | |
} | |
#else | |
/// <summary> | |
/// Constructor for the GPin object | |
/// You will need to porovide name, type, number and a starting value of the pin | |
/// </summary> | |
/// <param name="strPinName">Name of the pin</param> | |
/// <param name="pinType">Type of the pin | |
/// <para/>Possible pin types are: | |
/// <para/>PINTYPE_INPUT_ANALOG : "Read method analogRead shall be used" | |
/// <para/>PINTYPE_INPUT_DIGITAL : "Read method digitalRead shall be used" | |
/// <para/>PINTYPE_OUTPUT_ANALOG : "Write method analogWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_DIGITAL : "Write method digitalWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_VIRTUAL : "A pin not connected to hardware, but can store values" | |
/// </param> | |
/// <param name="pinNumber">Number of the pin. That is the GPIO number</param> | |
/// <param name="pinValue">The value to set the pin to</param> | |
GPin::GPin(const char*strPinName, PINTYPE pinType, int pinNumber, int pinValue) { | |
init(strPinName, pinType, pinNumber, pinValue); | |
} | |
#endif | |
void GPin::init(const char*strPinName, PINTYPE pinType, int pinNumber, int pinValue) { | |
mName = NULL; | |
setName(strPinName); | |
mType = pinType; | |
if (mType == PINTYPE_INPUT_ANALOG || mType == PINTYPE_INPUT_DIGITAL){ | |
pinMode(pinNumber, INPUT); | |
mNumber = pinNumber; | |
getValue();//to set the member mValue | |
} | |
#ifdef ESP32 | |
else if (pinType == PINTYPE_OUTPUT_ANALOG) { | |
ledcSetup(mChannel, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT); //setup the LEDC_CHANNEL which is index | |
ledcAttachPin(pinNumber, mChannel); //connect the pin and the LEDC_CHANNEL | |
/*sigmaDeltaSetup(mChannel, 312500); | |
sigmaDeltaAttachPin(pinNumber, mChannel);*/ | |
set(pinNumber, pinValue); | |
} | |
#endif | |
else { //TYPE IS PINTYPE_OUTPUT_DIGITAL or it is (ESP8266 && PINTYPE_OUTPUT_ANALOG) or PINTYPE_OUTPUT_VIRTUAL | |
if (mType != PINTYPE_OUTPUT_VIRTUAL) | |
pinMode(pinNumber, OUTPUT); | |
set(pinNumber, pinValue); | |
} | |
} | |
/// <summary> | |
/// Deconstructor for the pin | |
/// </summary> | |
GPin::~GPin() { | |
this->destroy(); | |
}; | |
void GPin::set(int number, int value) { | |
mNumber = number; | |
setValue(value); | |
} | |
/// <summary> | |
/// Assigns a name to a specific pin | |
/// </summary> | |
/// <param name="strPinName">Name of the pin</param> | |
void GPin::setName(const char * strPinName) { | |
if (strPinName == NULL) | |
return; | |
if (mName != NULL) | |
delete[] mName; | |
mName = new char[strlen(strPinName) + 1]; | |
strcpy(mName, strPinName); | |
} | |
void GPin::destroy() { | |
if (mName != NULL) { | |
delete[] mName; | |
mName = NULL; //no need but feels good :) | |
} | |
} | |
/// <summary> | |
/// Gets a pin name | |
/// </summary> | |
/// <returns>A String containing the pin name</returns> | |
String GPin::getName() { | |
return String(this->mName); | |
} | |
/// <summary> | |
/// Sets a new value to a pin | |
/// If the pin is of the type PINTYPE_OUTPUT_ANALOG or PINTYPE_OUTPUT_DIGITAL | |
/// Then the GPIO pin of the device will be changed to this value. | |
/// </summary> | |
/// <param name="value">The new value to set</param> | |
void GPin::setValue(int value) { | |
if (mType == PINTYPE_INPUT_ANALOG || mType == PINTYPE_INPUT_DIGITAL) { | |
getValue(); //we cannot set value of a input pin, so we will read instead to set the value | |
return; | |
} | |
if (mType == PINTYPE_OUTPUT_DIGITAL) { | |
value = (value > 0) ? HIGH : LOW; | |
mValue = value; | |
digitalWrite(mNumber, mValue); | |
return; | |
} | |
mValue = value; | |
if (mType == PINTYPE_OUTPUT_VIRTUAL) | |
return; | |
#ifdef ESP32 | |
analogWriteEsp32(); | |
#else | |
analogWrite(mNumber, mValue); | |
#endif // ESP8266 | |
} | |
/// <summary> | |
/// Read a pin value. | |
/// If input type is PINTYPE_INPUT_DIGITAL or PINTYPE_INPUT_ANALOG and readValueFromHardware is true | |
/// then a read will be maid directly to the hardwarepin. otherwise the old member value will be returned. | |
/// </summary> | |
/// <param name="readValueFromHardware"></param> | |
/// <returns>The value as an int</returns> | |
int GPin::getValue(bool readValueFromHardware) { | |
if (readValueFromHardware && mType != PINTYPE_OUTPUT_VIRTUAL) { | |
if (mType == PINTYPE_INPUT_DIGITAL) | |
mValue = digitalRead(mNumber); | |
else if (mType == PINTYPE_INPUT_ANALOG) | |
mValue = analogRead(mNumber); | |
} | |
return mValue; | |
} | |
/// <summary> | |
/// Get the number of a pin. | |
/// <para>You can then use this number to search for the index of the pin in the GPins object</para> | |
/// </summary> | |
/// <returns>The number of the pin. ps. this is not the Index of a pin.</returns> | |
int GPin::getNumber() { | |
return mNumber; | |
} | |
/// <summary> | |
/// Gets the type of the pin | |
/// <para>Possible pin types are:</para> | |
/// <para>PINTYPE_INPUT_ANALOG : "Read method analogRead shall be used"</para> | |
/// <para>PINTYPE_INPUT_DIGITAL : "Read method digitalRead shall be used"</para> | |
/// <para>PINTYPE_OUTPUT_ANALOG : "Write method analogWrite shall be used"</para> | |
/// <para>PINTYPE_OUTPUT_DIGITAL : "Write method digitalWrite shall be used"</para> | |
/// <para>PINTYPE_OUTPUT_VIRTUAL : "A pin not connected to hardware, but can store values"</para> | |
/// </summary> | |
/// <returns>The type of the pin</returns> | |
int GPin::getType() { | |
return mType; | |
} | |
/// <summary> | |
/// Creates a string with a key and a value, which can be used when populating a JSON object. | |
/// </summary> | |
/// <param name="name">Name of the key</param> | |
/// <param name="value">The integer value</param> | |
/// <returns> | |
/// A string with a key and a value. | |
/// For example: | |
/// "args":12 | |
/// </returns> | |
String GPin::jsonKeyValue(String key, int value) { | |
String str = "\"" + key + "\":" + String(value); | |
return str; | |
} | |
/// <summary> | |
/// Returns pin number and value as a Key value pair with no surrounding curly brackets | |
/// </summary> | |
/// <returns>A key value string which can be added to a Json object string</returns> | |
String GPin::toJsonKeyValue() { | |
return jsonKeyValue(getName(), getNumber()); | |
} | |
/// <summary> | |
/// Converts the pin -name, -number, -type and -value to a valid json object string | |
/// </summary> | |
/// <returns>A json object string with information about pin members</returns> | |
String GPin::toJson() { | |
//todo: use jsonKeyValue function here | |
String str = "{\"pin\":" + String(mNumber) + "," + | |
"\"val\":" + String(getValue()) + "," + | |
"\"m\":" + String(mType) + "," + | |
"\"name\":\"" + getName() + "\"}"; | |
return str; | |
} | |
#endif //#ifndef CODE_BLOCK_GPin_impl | |
#ifndef CODE_BLOCK_GPins_impl | |
/// <summary> | |
/// Constructor for the GPins Object | |
/// </summary> | |
GPins::GPins() { | |
mLength = 0; | |
} | |
/// <summary> | |
/// Sets the value of a pin with the given number. | |
/// </summary> | |
/// <param name="pinNumber">The number of the pin to search for</param> | |
/// <param name="newValue">The new value to be set</param> | |
/// <returns>True if value was set. False if value was not set because no pin with given pin number was not found.</returns> | |
boolean GPins::setValue(int pinNumber, int newValue) { | |
int i = indexOf(pinNumber); | |
if (i < 0) return false; | |
Serial.println("setting value of pin " + String(pinNumber) + " to " + String(newValue)); | |
mPins[i]->setValue(newValue); | |
return true; | |
} | |
// todo: will we need a removePIn function? | |
#ifdef ESP32 | |
/// <summary> | |
/// Adds a pin | |
/// </summary> | |
/// <param name="strPinName">The name of the pin</param> | |
/// <param name="pinType">Type of the pin | |
/// <para/>Possible types of a pin are: | |
/// <para/>PINTYPE_INPUT_ANALOG : "Read method analogRead shall be used" | |
/// <para/>PINTYPE_INPUT_DIGITAL : "Read method digitalRead shall be used" | |
/// <para/>PINTYPE_OUTPUT_ANALOG : "Write method analogWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_DIGITAL : "Write method digitalWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_VIRTUAL : "A pin not connected to hardware, but can store values" | |
/// </param> | |
/// <param name="pinNumber">Number of the pin (GPIO number)</param> | |
/// <param name="pinValue">Starting value of the pin. If the pin type is an input pin then the value will be read from the hardware and this value ignored.</param> | |
/// <returns>The number of pins after the pin was added.</returns> | |
int GPins::addPin(const char *strPinName, PINTYPE pinType, int pinNumber, int pinValue) { | |
if (pinType == PINTYPE_OUTPUT_ANALOG) { | |
mChannelCount++; //mChannel is only used when pin is of type PINTYPE_OUTPUT_ANALOG | |
} | |
mPins[mLength] = new GPin(strPinName, pinType, pinNumber, pinValue, mChannelCount - 1); | |
mLength++; | |
return mLength - 1; | |
} | |
#else | |
/// <summary> | |
/// Adds a pin | |
/// </summary> | |
/// <param name="strPinName">The name of the pin</param> | |
/// <param name="pinType">Type of the pin | |
/// <para/>Possible types of a pin are: | |
/// <para/>PINTYPE_INPUT_ANALOG : "Read method analogRead shall be used" | |
/// <para/>PINTYPE_INPUT_DIGITAL : "Read method digitalRead shall be used" | |
/// <para/>PINTYPE_OUTPUT_ANALOG : "Write method analogWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_DIGITAL : "Write method digitalWrite shall be used" | |
/// <para/>PINTYPE_OUTPUT_VIRTUAL : "A pin not connected to hardware, but can store values" | |
/// </param> | |
/// <param name="pinNumber">Number of the pin (GPIO number)</param> | |
/// <param name="pinValue">Starting value of the pin. If the pin type is an input pin then the value will be read from the hardware and this value ignored.</param> | |
/// <returns>The number of pins after the pin was added.</returns> | |
int GPins::addPin(const char *strPinName, PINTYPE pinType, int pinNumber, int pinValue) { | |
mPins[mLength] = new GPin(strPinName, pinType, pinNumber, pinValue); | |
mLength++; | |
return mLength - 1; | |
} | |
#endif | |
/// <summary> | |
/// Searches for a pin with a specific number and returns it's index in the array | |
/// </summary> | |
/// <param name="pinNumber">The pin number to search for</param> | |
/// <returns>If nothing is found the function returns -1</returns> | |
int GPins::indexOf(int pinNumber) { | |
if (pinNumber < 0) return -1; | |
for (int i = 0; i<mLength; i++) { | |
if (pinNumber == mPins[i]->getNumber()) { | |
return i; | |
} | |
} | |
Serial.println("indexOf pin not found!"); | |
return -1; | |
} | |
/// <summary> | |
/// Checks if a pin with a given number exits. | |
/// </summary> | |
/// <param name="pinNumber"></param> | |
/// <returns>true if a pin was found otherwise false</returns> | |
boolean GPins::exists(int pinNumber) { | |
return (indexOf(pinNumber) > -1); | |
} | |
/// <summary> | |
/// Gets a pointer to a GPin | |
/// </summary> | |
/// <param name="pinNumber">The pin number to search for</param> | |
/// <returns>A pointer to the GPin ojbect. Returns NULL if pin is not found</returns> | |
GPin *GPins::get(int pinNumber) { | |
int i = indexOf(pinNumber); | |
if (i < 0) return NULL; | |
return mPins[i]; | |
} | |
/// <summary> | |
/// Gets a value of a pin | |
/// </summary> | |
/// <param name="pinNumber">The pin to get the value from</param> | |
/// <returns>The value of the given pin.<para/> Returns -1 if pinNumber was not found</returns> | |
int GPins::getValue(int pinNumber) { | |
GPin *pin = this->get(pinNumber); | |
if (pin == NULL) return -1; | |
return pin->getValue(); | |
} | |
/// <summary> | |
/// Gets the number of pins. | |
/// </summary> | |
/// <returns>Number of GPin objects in the GPins object</returns> | |
int GPins::count() { | |
return mLength; | |
} | |
/// <summary> | |
/// Returns all pin values in a json array | |
/// a key-value Json object with the '{' and '}' around it. | |
/// where first key is the first in the index with the key as the GPO key | |
/// and the value is the last value set to that key. | |
/// </summary> | |
/// <returns>A Json object string containing status of all pins in the GPins object</returns> | |
String GPins::toJson() { | |
String str = "["; | |
int i; | |
for (i = 0; i < mLength; i++) { | |
if (i > 0) { | |
str = str + ","; | |
} | |
str = str + mPins[i]->toJson(); | |
} | |
return str + "]"; | |
} | |
/// <summary> | |
/// Creates a JSON object containg all pins name and their number. | |
/// </summary> | |
/// <returns>A string formatted as a JSON object which contains all pin names and number. </returns> | |
String GPins::JsonPinout() { | |
String str = "["; | |
int i; | |
for (i = 0; i < mLength; i++) { | |
if (i > 0) { | |
str = str + ","; | |
} | |
str = str + mPins[i]->toJsonKeyValue(); | |
} | |
return str + "]"; | |
} | |
#endif //CODE_BLOCK_GPins_impl | |
#ifndef CODE_BLOCK_GUrl_impl | |
//return -9999 if a string is not a number | |
int GUrl::toNumber(String str) { | |
const int ERROR_NUMBER = -9999; | |
int iLen = str.length(); | |
if (iLen < 1) return ERROR_NUMBER; | |
for (int i = 0; i < iLen; i++) { | |
if (!isDigit(str[i])) | |
return ERROR_NUMBER; | |
} | |
return str.toInt(); | |
} | |
String GUrl::jsonKeyValue(String key, String value) { | |
String str = "\"" + key + "\":" + value; | |
return str; | |
} | |
/// <summary> | |
/// Creates a string with a key and a value, which can be used when populating a JSON object. | |
/// </summary> | |
/// <param name="key">Name of the key</param> | |
/// <param name="value">The integer value</param> | |
/// <returns> | |
/// A string with a key and a value. | |
/// For example: | |
/// "args":12 | |
/// </returns> | |
String GUrl::jsonKeyValue(String key, int value) { | |
String str = "\"" + key + "\":" + String(value); | |
return str; | |
} | |
String GUrl::jsonObjectType(unsigned int uiType) { | |
String str; | |
if (uiType < OBJECTTYPE_COUNT) | |
str = String(uiType); | |
else | |
str = "-1"; | |
return jsonKeyValue("type", str); | |
} | |
String GUrl::makeStatusResponceJson(String jsonPins, String jsonWhitelist, String jsonDate) { | |
String str = "{" + | |
jsonObjectType(OBJECTTYPE_STATUS) + "," + | |
jsonKeyValue("pins", jsonPins) + "," + | |
jsonKeyValue("whitelist", jsonWhitelist) + "," + | |
jsonKeyValue("date", jsonDate) + | |
"}"; | |
return str; | |
} | |
String GUrl::makePostLogPinsJson(String deviceId, String jsonPins) { | |
String str = "{" + | |
jsonObjectType(OBJECTTYPE_LOG_PINS) + "," + | |
jsonKeyValue("pins", jsonPins) + "," + | |
jsonKeyValue("deviceId", "\""+deviceId+ "\"") + | |
"}"; | |
return str; | |
} | |
/// <summary> | |
/// Formats a http status code | |
/// </summary> | |
/// <param name="uiStatusCode">Number of the http status code to format</param> | |
/// <returns>A string with the http statuscode number and the status text.</returns> | |
String GUrl::makeHttpStatusCodeString(unsigned int uiStatusCode) { | |
String strCode; | |
switch (uiStatusCode) { | |
case 200: strCode = String(uiStatusCode) + " OK"; | |
break; | |
case 400: strCode = String(uiStatusCode) + " Bad request"; | |
break; | |
case 416: strCode = String(uiStatusCode) + " Range Not Satisfiable"; | |
break; | |
default: strCode = ""; | |
break; | |
} | |
return strCode; | |
} | |
String GUrl::jsonRoot(unsigned int uiType, String key, String value) { | |
if (uiType == OBJECTTYPE_KEYVALUE_STRING) | |
value = "\"" + value + "\""; | |
String str = "{" + | |
jsonObjectType(uiType) + | |
"," + | |
jsonKeyValue(key, value) + | |
"}"; | |
return str; | |
} | |
#endif //#ifndef CODE_BLOCK_GUrl_impl | |
#ifndef CODE_BLOCK_KeyValue_impl | |
KeyValue::KeyValue(String key, String value) { set(KEYVALUE_STRING, key, value, 0, 0); } | |
KeyValue::KeyValue(String key, int value) { set(KEYVALUE_INT, key, "", value, 0); } | |
KeyValue::KeyValue(String key, double value) { set(KEYVALUE_DOUBLE, key, "", 0, value); } | |
String KeyValue::getKey() { return mKey; } | |
void KeyValue::set(unsigned int keyValueType, String key, String strValue, int iValue, double dValue) { | |
mKeyValueType = keyValueType; | |
mKey = key; | |
mstrValue = strValue; | |
miValue = iValue; | |
mdValue = dValue; | |
} | |
String KeyValue::getValueString(boolean addDoubleQuotationIfString) { | |
switch (mKeyValueType) { | |
case KEYVALUE_STRING: | |
if (addDoubleQuotationIfString) | |
return "\"" + mstrValue + "\""; | |
else | |
return mstrValue; | |
break; | |
case KEYVALUE_INT: | |
return String(miValue); | |
break; | |
case KEYVALUE_DOUBLE: return String(mdValue, 3); | |
break; | |
} | |
return ""; | |
} | |
/// <summary> | |
// Get the key and value as a json pair string. | |
//on error "" is returned. | |
/// </summary> | |
/// <returns>Success: returns the key value pair as a String. Fail: returns ""</returns> | |
String KeyValue::toJson() { | |
String val; | |
return "\"" + mKey + "\":" + getValueString(true); | |
} | |
#endif //CODE_BLOCK_KeyValue_impl | |
#ifndef CODE_BLOCK_Gtime | |
/// <summary> | |
/// Assignment constructor | |
/// </summary> | |
/// <example> | |
/// @code{.xml} | |
/// GTime x, y; | |
/// x = y; | |
/// @endcode | |
/// </example> | |
GTime::GTime(const GTime& gTime) { | |
mYear = gTime.mYear; | |
mMonth = gTime.mMonth; | |
mDay = gTime.mDay; | |
mHours = gTime.mHours; | |
mMinutes = gTime.mMinutes; | |
mSeconds = gTime.mSeconds; | |
} | |
/// <summary> | |
/// Sets a new GTime by converting milliseconds to GTime | |
/// Largest possible unsigned long is 4294967295, which is 49 days, 17:02:47 | |
/// If there are more than 28 days then the month is not be increased so days can grow up to 49 | |
/// But note, this is not a real GTime because this is more like a counter, | |
/// so if milliseconds are less than one day year, month and day will all be 0 | |
/// </summary> | |
/// <param name="milliSeconds"> | |
/// There are 1000 milliSeconds in a second | |
/// </param> | |
void GTime::setTime(unsigned long milliSeconds) { | |
unsigned long d=0, y, s, m, h, mi; | |
y = ((unsigned long)60 * (unsigned long)60 * (unsigned long)1000); | |
h = milliSeconds / y; | |
m = (milliSeconds - (h * y)) / (y / 60); | |
s = (milliSeconds - (h * y) - (m * (y / 60))) / 1000; | |
mi = milliSeconds - (h * y) - (m * (y / 60)) - (s * 1000); | |
if (h > 23) | |
{ | |
d = h / 24; | |
h -= (d * 24); | |
} | |
mYear = 0; | |
mMonth = 0; | |
mDay = d; | |
mHours = h; | |
mMinutes = m; | |
mSeconds = s; | |
} | |
/// <summary> | |
/// Constructor for the GTime object | |
/// Creates new GTime by converting milliseconds to GTime | |
/// Largest possible unsigned long is 4294967295, which is 49 days, 17:02:47 | |
/// If there are more than 28 days then the month is not be increased so days can grow up to 49 | |
/// But note, this is not a real GTime because this is more like a counter, | |
/// so if milliseconds are less than one day year, month and day will all be 0 | |
/// </summary> | |
/// <param name="milliSeconds"> | |
/// There are 1000 milliSeconds in a second | |
/// </param> | |
GTime::GTime(unsigned long milliSeconds) { | |
setTime(milliSeconds); | |
} | |
/// <summary> | |
/// Sets time values from a given string | |
/// </summary> | |
/// <param name="strTime"> | |
/// A string with date and time. | |
/// The string needs to be formatted like this: | |
/// Fri, 15 Jul 2016 11:08:12 GMT | |
/// </param> | |
/// <returns>Success: returns true if time was set successfully. Fail: returns false.</returns> | |
boolean GTime::setTime(String strTime) { | |
String str; | |
String num; | |
int i; | |
Serial.println("strTime: " + strTime); | |
i = strTime.indexOf(", "); if (i < 0) return false; else i += 2; | |
str = strTime.substring(i); | |
//todo: make following into a function always the | |
//get the day | |
i = str.indexOf(' '); if (i < 1) return false; i += 1; | |
num = str.substring(0, i - 1); | |
mDay = toNumber(num); | |
str = str.substring(i); | |
//get the month | |
i = str.indexOf(' '); if (i < 1) return false; i += 1; | |
num = str.substring(0, i - 1); | |
mMonth = strToMonth(num); | |
str = str.substring(i); | |
//get the year | |
i = str.indexOf(' '); if (i < 1) return false; i += 1; | |
num = str.substring(0, i - 1); | |
mYear = toNumber(num); | |
str = str.substring(i); | |
//get the hour | |
i = str.indexOf(':'); if (i < 1) return false; i += 1; | |
num = str.substring(0, i - 1); | |
mHours = toNumber(num); | |
str = str.substring(i); | |
//get the minute | |
i = str.indexOf(':'); if (i < 1) return false; i += 1; | |
num = str.substring(0, i - 1); | |
mMinutes = toNumber(num); | |
str = str.substring(i); | |
//get the second | |
i = str.indexOf(' '); if (i < 1) return false; i += 1; | |
num = str.substring(0, i - 1); | |
mSeconds = toNumber(num); | |
str = str.substring(i); | |
} | |
/// <summary> | |
/// Converts a String to a number positive number. | |
/// Negative numbers are considered invalid. | |
/// </summary> | |
/// <param name="str">The string to be converted to a number.</param> | |
/// <returns> | |
/// Success: The converted number. | |
/// Fail : -9999 | |
/// </returns> | |
int GTime::toNumber(String str) { | |
const int ERROR_NUMBER = -9999; | |
int iLen = str.length(); | |
if (iLen < 1) return ERROR_NUMBER; | |
for (int i = 0; i < iLen; i++) { | |
if (!isDigit(str[i])) | |
return ERROR_NUMBER; | |
} | |
return str.toInt(); | |
} | |
/// <summary> | |
/// Converts a 3 letter english month name to the number of the month in the year. | |
/// </summary> | |
/// <param name="month"> | |
/// A string of length 3, which represents the month. | |
/// For example "JAN" for january.</param> | |
/// <returns> | |
/// success : the number of the month, where 1 is january. | |
/// fail : -1 | |
/// </returns> | |
int GTime::strToMonth(String month) { | |
if (month.length() != 3) return -1; | |
month.toLowerCase(); | |
if (month == "jan") return 1; | |
if (month == "feb") return 2; | |
if (month == "mar") return 3; | |
if (month == "apr") return 4; | |
if (month == "may") return 5; | |
if (month == "jun") return 6; | |
if (month == "jul") return 7; | |
if (month == "aug") return 8; | |
if (month == "sep") return 9; | |
if (month == "oct") return 10; | |
if (month == "nov") return 11; | |
if (month == "dec") return 12; | |
return -1; | |
} | |
/// <summary> | |
/// Returns date and time values as a english date time string. | |
/// </summary> | |
/// <returns>Date and time string on the format "MM/DD/YY hh:mm:ss"</returns> | |
String GTime::toString() { | |
return String(mMonth) + "/" + | |
String(mDay) + "/" + | |
String(mYear) + " " + | |
String(mHours) + ":" + | |
String(mMinutes) + ":" + | |
String(mSeconds); | |
} | |
/// <summary> | |
/// Returns date and time values as a icelandic date time string. | |
/// </summary> | |
/// <returns>Date and time string on the format "DD.MM.YY hh:mm:ss"</returns> | |
String GTime::toStreng() { | |
return String(mDay) + "." + | |
String(mMonth) + "." + | |
String(mYear) + " " + | |
String(mHours) + ":" + | |
String(mMinutes) + ":" + | |
String(mSeconds); | |
} | |
/// <summary> | |
/// Returns the date and time values as an JSON object. | |
/// </summary> | |
/// <returns>A String formatted as an JSON object</returns> | |
String GTime::toJson() { | |
String str = "{\"year\":" + String(mYear) + "," + | |
"\"month\":" + String(mMonth) + "," + | |
"\"day\":" + String(mDay) + "," + | |
"\"hours\":" + String(mHours) + "," + | |
"\"minutes\":" + String(mMinutes) + "," + | |
"\"seconds\":" + String(mSeconds) + "}"; | |
return str; | |
} | |
/// <summary> | |
/// Get the year part of the GTime | |
/// </summary> | |
/// <returns>The year as a integer number</returns> | |
int GTime::getYear() { return mYear; } | |
/// <summary> | |
/// Get the month part of the GTime | |
/// </summary> | |
/// <returns>The month as a integer number</returns> | |
int GTime::getMonth() { return mMonth; } | |
/// <summary> | |
/// Get the day part of the GTime | |
/// </summary> | |
/// <returns>The day as a integer number</returns> | |
int GTime::getDay() { return mDay; } | |
/// <summary> | |
/// Get the hours part of the GTime | |
/// </summary> | |
/// <returns>The hours as a integer number</returns> | |
int GTime::getHours() { return mHours; } | |
/// <summary> | |
/// Get the minutes part of the GTime | |
/// </summary> | |
/// <returns>The minutes as a integer number</returns> | |
int GTime::getMinutes() { return mMinutes; } | |
/// <summary> | |
/// Get the seconds part of the GTime | |
/// </summary> | |
/// <returns>The seconds as a integer number</returns> | |
int GTime::getSeconds() { return mSeconds; } | |
#endif //CODE_BLOCK_Gtime | |
/// <summary> | |
/// Parses the json_string into a new JsonData object | |
/// </summary> | |
/// <example> | |
/// // Create an empty object | |
/// @code{.xml} | |
/// // Create an empty json object | |
/// JsonData jsEmptyObject("{}"); | |
/// | |
/// // Create an empty json array | |
/// JsonData jsEmptyArray("[]"); | |
/// @endcode | |
/// </example> | |
/// <param name="jsonString">Must be a valid JSON string</param> | |
Json::Json(const char *jsonString) | |
{ | |
mData = new JsonData(jsonString); | |
} | |
/// <summary> | |
/// A deconstructor for the Json object. It will free the object from memory. | |
/// This function fires automatically when this object is no longer needed. | |
/// </summary> | |
Json::~Json() | |
{ | |
if (mData) { | |
//cout << "Deleting " << mData->getKey().c_str() << ":" << mData->getValue().c_str() << endl; | |
delete mData; | |
mData = NULL; | |
} | |
} | |
/// <summary> | |
/// Returns the object as a JSON string. | |
/// This string should be a valid JSON string, ready to be sent over the wire. | |
/// </summary> | |
/// <returns>The current object returned as an JSON String.</returns> | |
String Json::toString() const | |
{ | |
return mData->toString(); | |
} | |
/// <summary> | |
/// Showing objects and sub-objects In a treeview. | |
/// That is child objects have additional tabs relative to parent objects. | |
/// </summary> | |
/// <returns>The json object as a tree view string.</returns> | |
String Json::toTree() const | |
{ | |
return mData->toTree(); | |
} | |
/// <summary> | |
/// Removes all unnecessary white spaces like tab end line and carriage return | |
/// </summary> | |
/// <param name="jsonStringToTrim">The input string that will be unchanged</param> | |
/// <returns>Copy of the input string without all white spaces</returns> | |
String Json::trim(String jsonStringToTrim) | |
{ | |
return JsonData::trim(jsonStringToTrim); | |
} | |
/// <summary> | |
/// Will tell you if the current object is invalid | |
/// </summary> | |
bool Json::isValid() const | |
{ | |
return mData->isValid(); | |
} | |
/// <summary> | |
/// Parses the json_string into a new JsonData object | |
/// </summary> | |
/// <example> | |
/// Example one: | |
/// @code{.xml} | |
/// // Create an empty json object | |
/// JsonData jsEmptyObject("{}"); | |
/// @endcode | |
/// Example two: | |
/// @code{.xml} | |
/// // Create an empty json array | |
/// JsonData jsEmptyArray("[]"); | |
/// @endcode | |
/// </example> | |
/// <param name="jsonString">Must be a valid JSON string</param> | |
JsonData::JsonData(const char* jsonString) // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) | |
{ | |
//parse calls init which will initialize members | |
parse(jsonString, NULL); | |
} | |
/// <summary> | |
/// Parses a json string into a new JsonData object | |
/// </summary> | |
/// <param name="jsonString">A valid json string for the current object</param> | |
/// <param name="parent">Parent of this json object if no parent pass NULL</param> | |
JsonData::JsonData(const String jsonString, JsonData* parent) // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) | |
{ | |
//parse calls init which will initialize members | |
parse(jsonString, parent); | |
} | |
/// <summary> | |
/// Creates a new Json object with a invalid valueType | |
/// </summary> | |
/// <param name="type">The type of object this is</param> | |
/// <param name="parent">Parent of this json object if no parent pass NULL</param> | |
JsonData::JsonData(JSONTYPE type, JsonData* parent) { // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) | |
//init will initialize member values | |
init(type, JSONTYPE_INVALID, parent); | |
} | |
/// <summary> | |
/// Initializes all member variables | |
/// </summary> | |
/// <param name="type">The type of this object</param> | |
/// <param name="valueType">The value type of this object</param> | |
/// <param name="parent">Parent of this json object if no parent pass NULL</param> | |
void JsonData::init(JSONTYPE type, JSONTYPE valueType, JsonData* parent) { | |
this->mParent = parent; | |
mType = type; | |
mValueType = valueType; | |
mFirstChild = NULL; | |
mNext = NULL; | |
if (parent) { | |
if (type != parent->mValueType) { | |
parent->mValueType = type; | |
} | |
JsonData* lastChild = getLastChild(parent); | |
if (lastChild) | |
lastChild->mNext = this; | |
else | |
parent->mFirstChild = this; | |
} | |
} | |
/// <summary> | |
/// Initializes all member variables and parses the jsonString | |
/// </summary> | |
/// <param name="jsonString">Json string object</param> | |
/// <param name="parent">Parent, pass NULL if this is the root object (no parent)</param> | |
void JsonData::parse(const String jsonString, JsonData* parent) { | |
// | |
String strJson(trim(jsonString)); | |
init(JSONTYPE_INVALID, JSONTYPE_INVALID, parent); | |
const int len = strJson.length(); | |
if (len < 2) | |
return; | |
const char firstChar = strJson.charAt(0); | |
if (strJson.charAt(len - 1) != getClosingToken(firstChar)) | |
return;//invalid json | |
if (firstChar == '[') | |
mType = JSONTYPE_ARRAY; | |
else if (firstChar == '{') | |
mType = JSONTYPE_OBJECT; | |
else | |
return; //invalid json | |
if (len == 2) | |
return; //an empty json | |
if (mType == JSONTYPE_ARRAY) | |
array(&strJson, this, false); | |
else if (mType == JSONTYPE_OBJECT) | |
object(&strJson, this); | |
} | |
/// <summary> | |
/// Searches for children of a given parent. | |
/// </summary> | |
/// <param name="parent">The parent to search for last child in. Must not be NULL.</param> | |
/// <returns>A pointer to the last child of the parent object. Return NULL if parent has not children</returns> | |
JsonData* JsonData::getLastChild(JsonData* parent) { | |
if (parent->mFirstChild == NULL) | |
return NULL; | |
JsonData* child = parent->mFirstChild; | |
while (child->mNext) { | |
child = child->mNext; | |
} | |
return child; | |
} | |
/// <summary> | |
/// Searches for the last sibling of a node; | |
/// </summary> | |
/// <param name="previous">Will search for the last sibling after this node. </param> | |
/// <returns>Pointer to the last sibling after previous node. Returns NULL if previous is the last node.</returns> | |
JsonData* JsonData::getLastNode(JsonData* previous) { | |
if (!previous) | |
return NULL; | |
JsonData* pNode = previous->mNext; | |
if (!pNode) | |
return NULL; | |
while (pNode->mNext) { | |
pNode = pNode->mNext; | |
} | |
return pNode; | |
} | |
//key and val must have a parent | |
/// <summary> | |
/// Will create a new Object and set it's values according to the given parameters. | |
/// No parsing will take place. Makes the last child of parent point to this one. | |
/// </summary> | |
/// <param name="value"></param> | |
/// <param name="type"></param> | |
/// <param name="valueType"></param> | |
/// <param name="parent">Parent of this json object if no parent pass NULL</param> | |
JsonData::JsonData(String value, JSONTYPE type, JSONTYPE valueType, JsonData* parent) { // NOLINT | |
init(type, valueType, parent); | |
mValue = value; | |
} | |
/// <summary> | |
/// Checks if a character is any of the json closing tokens | |
/// note quotes " are tricky, it can also be a starting token but we return true for that | |
/// </summary> | |
/// <param name="c">The Character to check</param> | |
/// <returns></returns> | |
bool JsonData::isClosingToken(const char c) { | |
switch (c) { | |
case ']': | |
case '}': | |
case '"': | |
case ',': return true; | |
default: return false; | |
} | |
} | |
/// <summary> | |
/// Searches for an closing token of the given opening token | |
/// </summary> | |
/// <param name="openingToken">The opening token to find a closing token for</param> | |
/// <returns>The closing token for an opening token. if '+' is returned then any of the closing tokens can be a closing token. If no closing token was found then ' ' is returned.</returns> | |
char JsonData::getClosingToken(const char openingToken) { | |
switch (openingToken) { | |
case '[': return ']'; | |
case '{': return '}'; | |
case '"': return '"'; | |
case ':': return '+'; | |
case '-':/*Fyrir integer or float*/ | |
case '1': | |
case '2': | |
case '3': | |
case '4': | |
case '5': | |
case '6': | |
case '7': | |
case '8': | |
case '9': | |
//todo hvað með | |
case '0': /*Fyrir floating point*/ | |
case 'f': /*Fyrir bool*/ | |
case 't': | |
case 'n': /*Fyrir null*/ | |
return '+'; | |
default: return ' '; | |
} | |
} | |
/// <summary> | |
/// Searches for end of string. | |
/// </summary> | |
/// <param name="searchMe">a pointer to the string to search</param> | |
/// <param name="startAt">index of the first carater in the string. That is first character afthe the "</param> | |
/// <param name="length">length of the searchMe.</param> | |
/// <returns>Index of where the string ends (index of the ")</returns> | |
int getIndexOfClosingString(String* searchMe, int startAt, int length) { | |
int index = startAt; | |
if (length < 2 || index < 1 || index >= length) | |
return -1; | |
char before, | |
check = searchMe->charAt(index - 1); | |
while (index < length) { | |
before = check; | |
check = searchMe->charAt(index); | |
if (check == '\"' && before != '\\') { | |
return index; | |
} | |
index++; | |
} | |
return -1; | |
} | |
/// <summary> | |
/// If one of these three start tokens [ { " are found at the beginning of | |
/// the string the function returns the position of it's closing token. | |
/// </summary> | |
/// <param name="string">First char of this string will be used to search the same string for it's closing token</param> | |
/// <param name="ignoreStrings">If you are searching for a closing string token (first char is ") then set this parameter to false.</param> | |
/// <returns> | |
/// Success: Index of the closing token. Fail: Returns -1 if startToken is not found. | |
/// Returns -2 if startToken is found but no end token. | |
/// </returns> | |
int JsonData::getIndexOfClosingToken(String* string, bool ignoreStrings = true) { | |
int length = string->length(); | |
if (length < 1) | |
return -1; //maybe this should be 2 | |
const char cStart = string->charAt(0); | |
const char cEnd = getClosingToken(cStart); | |
if (cEnd == ' ') return -1; | |
char before, check; | |
int index = 1, | |
level = 1; | |
char startToken = cStart, | |
endToken = cEnd; | |
if (!ignoreStrings && string->charAt(0) == '\"') { | |
//read untill end of string | |
return getIndexOfClosingString(string, 1, length); | |
}; | |
while (index < length) { | |
before = string->charAt(index - 1); | |
check = string->charAt(index); | |
if (check == startToken && startToken != endToken && endToken != '+') { | |
if (!ignoreStrings && startToken == '\"' && before == '\\') // if \" is within a string it should be ignored | |
{/*Ignore this case*/ | |
} | |
else | |
level++; | |
} | |
else if (check == endToken || (endToken == '+' && isClosingToken(check))) { | |
if (!ignoreStrings && endToken == '\"' && before == '\\') // if \" is within a string it should be ignored | |
{/*ignore this case*/ | |
} | |
else | |
level--; | |
} | |
if (level == 0) | |
return index; | |
index++; | |
} | |
return -1; | |
} | |
/// <summary> | |
/// Checks if the character is a digit. | |
/// </summary> | |
/// <param name="c">The character to check.</param> | |
/// <returns>True if it is a digit. False it is not.</returns> | |
bool JsonData::isDigit(const char c) { | |
return (c >= '0' && c <= '9'); | |
} | |
/// <summary> | |
/// Searches for a value in the string, creates an object and removes that value from the string | |
/// Lexer: | |
/// value = [ string | number | object | array | "true" | "false" | "null" ] | |
/// number = [ @ignore("null") [int frac? exp?] ] | |
/// object = [ '{' members? '}' ] | |
/// array = [ '[' elements? ']' ] | |
/// string = [ '"' text '"' ] | |
/// PS. | |
/// We are skipping "true" | "false" | "null" | frac | exp for now | |
/// </summary> | |
/// <param name="valuesString">A json string to search for the first value</param> | |
/// <param name="parent">There must be a parent of a value.</param> | |
/// <returns>A pointer to the object created. If value was found, the function returns NULL</returns> | |
JsonData* JsonData::value(String* valuesString, JsonData* parent) | |
{ | |
if (!parent) | |
return NULL; | |
const int len = valuesString->length(); | |
if (len < 1) | |
return NULL; | |
String strKey, strValue; | |
const char firstChr = valuesString->charAt(0); | |
JSONTYPE valType = getValueTypeFromChar(firstChr); | |
int endOfVal = getIndexOfClosingToken(valuesString); | |
JsonData* pNewObject; | |
switch (valType) | |
{ | |
case JSONTYPE_OBJECT: | |
return object(valuesString, parent); | |
case JSONTYPE_ARRAY: | |
return array(valuesString, parent, true); | |
case JSONTYPE_STRING: | |
pNewObject = pair(valuesString, parent); | |
if (!pNewObject) | |
{ | |
//this is a string | |
endOfVal = getIndexOfClosingToken(valuesString, false); | |
if (endOfVal < 1) return NULL; | |
strValue = valuesString->substring(1, endOfVal); | |
int delLen = endOfVal + 1; | |
if (delLen + 1 < valuesString->length()) { | |
delLen += valuesString->charAt(delLen) == ','; | |
} | |
*valuesString = valuesString->substring(delLen); | |
if (!validateValue(valType, strValue)) | |
return setRootInvalid(); | |
pNewObject = new JsonData(strValue, valType, valType, parent); | |
} | |
return pNewObject; | |
case JSONTYPE_ULONG: | |
case JSONTYPE_LONG: | |
if (endOfVal == -1) | |
{ | |
JSONTYPE tempType = getType(*valuesString); | |
if (tempType != valType && tempType != JSONTYPE_FLOAT) { | |
return setRootInvalid(); | |
} | |
else | |
endOfVal = len; | |
} | |
strValue = valuesString->substring(0, endOfVal); | |
if (endOfVal < valuesString->length()) { | |
endOfVal += valuesString->charAt(endOfVal) == ','; | |
} | |
*valuesString = valuesString->substring(endOfVal); | |
if (getType(strValue) == JSONTYPE_FLOAT) { | |
valType = JSONTYPE_FLOAT; | |
} | |
if (!validateValue(valType, strValue)) | |
return setRootInvalid(); | |
pNewObject = new JsonData(strValue, valType, valType, parent); | |
return pNewObject; | |
case JSONTYPE_BOOL: | |
case JSONTYPE_NULL: | |
if (endOfVal == -1) | |
{ | |
JSONTYPE tempType = getType(*valuesString); | |
if (tempType != valType) { | |
return setRootInvalid(); | |
} | |
else | |
endOfVal = len; | |
} | |
strValue = valuesString->substring(0, endOfVal); | |
if (endOfVal < valuesString->length()) { | |
endOfVal += valuesString->charAt(endOfVal) == ','; | |
} | |
*valuesString = valuesString->substring(endOfVal); | |
if (!validateValue(valType, strValue)) | |
return setRootInvalid(); | |
pNewObject = new JsonData(strValue, valType, valType, parent); | |
return pNewObject; | |
default: | |
return NULL; | |
} | |
} | |
/// <summary> | |
/// Extracts the first value found in the string | |
/// Lexer: elements = [ value [',' value]* ] | |
/// </summary> | |
/// <param name="strValues">Json string with one or more values</param> | |
/// <param name="parent">parent must not be NULL</param> | |
/// <returns></returns> | |
JsonData* JsonData::elements(String* strValues, JsonData* parent) | |
{ | |
// all values must be children of parent | |
const int len = strValues->length(); | |
if (parent == NULL) | |
return setRootInvalid(); | |
if (len < 1) | |
return NULL; | |
JsonData* pLast, * p = NULL; | |
do { | |
pLast = p; | |
p = value(strValues, parent); | |
} while (p); | |
return pLast; | |
} | |
/// <summary> | |
/// Processes one ore more pairs and adds them to parent. | |
/// Lexer: members = [ pair [',' pair]* ] | |
/// </summary> | |
/// <param name="pairs">One ore move Pairs</param> | |
/// <param name="parent">Parent of all pairs. Must not be NULL</param> | |
/// <returns>The last Pair object added to parent</returns> | |
JsonData* JsonData::members(String* pairs, JsonData* parent) | |
{ | |
const int len = pairs->length(); | |
if (parent == NULL || | |
len < 5 || // "x":1 | |
pairs->charAt(0) != '\"' | |
) | |
return NULL; | |
JsonData* pLast; | |
//hér þarf að finna út hvort object fari í child eða next | |
int keyIndexOfFirstChar, keyLength, valueIndexOfFirstChar, | |
valueLength, pairLength; | |
bool thereIsAnotherPair; | |
String firstPairString = ""; | |
JsonData* p = NULL; | |
do { | |
pLast = p; | |
if (!getPairIndexes(pairs, thereIsAnotherPair, keyIndexOfFirstChar, keyLength, valueIndexOfFirstChar, valueLength, pairLength) | |
|| valueLength == 0) | |
{ | |
break; | |
} | |
firstPairString = pairs->substring(0, pairLength); | |
*pairs = pairs->substring(pairLength + thereIsAnotherPair); | |
p = pair(&firstPairString, parent); | |
} while (p); | |
return pLast; | |
} | |
/// <summary> | |
/// Parses members of an object from a string. | |
/// Lexer: members = [ pair [',' pair]* ] | |
/// pair = [ string ':' value ] | |
/// </summary> | |
/// <param name="members">Json string which will be parsed.</param> | |
/// <param name="parent">Parent of all members. Must not be NULL.</param> | |
/// <returns></returns> | |
JsonData* JsonData::object(String* members, JsonData* parent) | |
{ | |
const int len = members->length(); | |
if (parent == NULL || len < 2 || members->charAt(0) != '{') | |
return NULL; | |
//remove brackets around first object | |
int endOf = getIndexOfClosingToken(members); | |
if (endOf < 1) | |
return NULL; | |
String strArr = members->substring(1, endOf); | |
endOf++; | |
const bool moreItems = (endOf < len) && (members->charAt(endOf) == ','); | |
if (moreItems) | |
endOf++; | |
*members = members->substring(endOf); | |
JsonData* current = parent; | |
if (parent != NULL) | |
{ | |
if (strArr.length() == 0) { | |
return new JsonData(JSONTYPE_OBJECT, parent); | |
} | |
if (parent->mType == JSONTYPE_ARRAY) | |
current = new JsonData(JSONTYPE_OBJECT, parent); | |
else if (strArr.length() > 0) | |
{ | |
JSONTYPE objType = getValueTypeFromChar(strArr.charAt(0)); | |
if (parent->mType == JSONTYPE_KEY_VALUE) | |
current = new JsonData(JSONTYPE_OBJECT, parent); | |
} | |
else | |
return current; | |
} | |
return this->members(&strArr, current); | |
} | |
/// <summary> | |
/// Gets information about a pair, if it is a valid pair | |
/// </summary> | |
/// <param name="pairs">A string with one or more pairs with NO surrounding {}</param> | |
/// <param name="thereIsAnotherPair">true if there are more pairs in this string</param> | |
/// <param name="keyIndexOfFirstChar">Position of where the first char in the key</param> | |
/// <param name="keyLength">length of the key</param> | |
/// <param name="valueIndexOfFirstChar">Position of where the first char in the value</param> | |
/// <param name="valueLength">length of the value</param> | |
/// <param name="pairLength">Length of the pair</param> | |
/// <returns>true: All information about a pair was found and stored in parameters | |
/// False:Pair invalid no parameter value is unchanged</returns> | |
bool JsonData::getPairIndexes(String* pairs, bool& thereIsAnotherPair, | |
int& keyIndexOfFirstChar, int& keyLength, | |
int& valueIndexOfFirstChar, int& valueLength, | |
int& pairLength) | |
{ | |
///////////// start ///////////////// | |
const int len = pairs->length(); | |
if (len < 5 || // "x":1 | |
pairs->charAt(0) != '\"') | |
return false; | |
//we need to find end of value to find endOfPair | |
const int indexOfKeyEnd = getIndexOfClosingToken(pairs, false); | |
if (len < indexOfKeyEnd + 2 || indexOfKeyEnd < 1) return false; | |
//got end of key | |
int endOfPair = pairs->indexOf(':', indexOfKeyEnd + 1); | |
int startOfValue = endOfPair + 1; | |
if (startOfValue - 1 != indexOfKeyEnd + 1 && endOfPair + 2 < len) return false; | |
//got start of value; | |
String strValue = pairs->substring(startOfValue); | |
const int isStringValue = getValueTypeFromChar(strValue.charAt(0)) == JSONTYPE_STRING; | |
int endOfValue = getIndexOfClosingToken(&strValue, !isStringValue); | |
int iComma = strValue.indexOf(","); | |
if (endOfValue == -1) | |
{ | |
if (iComma > -1) | |
endOfValue = iComma - 1; | |
else | |
endOfValue = strValue.length() - 1; //-1 hér er munur á parse | |
JSONTYPE type = getType(strValue.substring(0, endOfValue + 1)); | |
if (type == JSONTYPE_INVALID) { | |
return false; | |
} | |
} | |
else { | |
if (endOfValue + 1 < strValue.length()) { | |
if (strValue.charAt(endOfValue) == ',') | |
endOfValue--; | |
} | |
} | |
endOfPair = startOfValue + endOfValue; | |
bool morePares = false; | |
if (endOfPair + 1 < len) { | |
morePares = pairs->charAt(endOfPair + 1) == ','; | |
} | |
///////////// endir ///////////////// | |
keyIndexOfFirstChar = 1; | |
keyLength = indexOfKeyEnd - keyIndexOfFirstChar; | |
valueIndexOfFirstChar = startOfValue; | |
valueLength = endOfValue + 1; | |
pairLength = endOfPair + 1; | |
thereIsAnotherPair = morePares; | |
return true; | |
} | |
/// <summary> | |
/// Extracts the first key value pair found in the members string and removes it from the string members | |
/// </summary> | |
/// <param name="keyValues">One or more pair. Pairs must start with key like this "key ":</param> | |
/// <param name="parent">Parent to one or more pairs in members. parent must not be NULL</param> | |
/// <returns>Returns a newly added pair to parent. If a pair is not found the return values is NULL;</returns> | |
JsonData* JsonData::pair(String* keyValues, JsonData* parent) | |
{ | |
if (!parent) | |
return setRootInvalid(); | |
bool thereIsAnotherPair; | |
int keyIndexOfFirstChar, keyLength, | |
valueIndexOfFirstChar, valueLength, | |
pairLength; | |
if (!getPairIndexes(keyValues, thereIsAnotherPair, | |
keyIndexOfFirstChar, keyLength, | |
valueIndexOfFirstChar, valueLength, | |
pairLength)) | |
{ | |
return NULL; | |
} | |
String strKeyX = keyValues->substring(keyIndexOfFirstChar, keyIndexOfFirstChar + keyLength); | |
String strValX = keyValues->substring(valueIndexOfFirstChar, valueIndexOfFirstChar + valueLength); | |
JSONTYPE valType = getValueTypeFromChar(strValX.charAt(0)); | |
bool isStringValue = valType == JSONTYPE_STRING; | |
if (valType == JSONTYPE_INVALID) | |
return setRootInvalid(); | |
JsonData* p = new JsonData(strKeyX, JSONTYPE_KEY_VALUE, valType, parent); | |
value(&strValX, p); | |
*keyValues = keyValues->substring(pairLength); | |
return p; | |
} | |
/// <summary> | |
/// Check if a value is a valid JSONTYPE | |
/// </summary> | |
/// <param name="jsonValueType">The type of the value to check</param> | |
/// <param name="stringValue">The value</param> | |
/// <returns></returns> | |
bool JsonData::validateValue(const JSONTYPE jsonValueType, String stringValue) | |
{ | |
if (jsonValueType == JSONTYPE_STRING) | |
return true; //all strings are valid | |
//todo:: use JSONTYPE instead | |
switch (jsonValueType) | |
{ | |
case JSONTYPE_ARRAY: return JSONTYPE_ARRAY == getType(stringValue); | |
case JSONTYPE_OBJECT: return JSONTYPE_OBJECT == getType(stringValue); | |
case JSONTYPE_ULONG: return JSONTYPE_ULONG == getType(stringValue); | |
case JSONTYPE_LONG: return JSONTYPE_LONG == getType(stringValue); | |
case JSONTYPE_FLOAT: return JSONTYPE_FLOAT == getType(stringValue); | |
case JSONTYPE_BOOL: return JSONTYPE_BOOL == getType(stringValue); | |
case JSONTYPE_NULL: return JSONTYPE_NULL == getType(stringValue); | |
case JSONTYPE_KEY_VALUE: return true; | |
} | |
return false; | |
} | |
/// <summary> | |
/// Parses elements of an array from a string, and creates objects from them. | |
/// Lexer: elements = [ value [',' value]* ] | |
/// </summary> | |
/// <param name="strElements">Json string of elements which are one or more values</param> | |
/// <param name="parent">Parent of all values. Must not be NULL.</param> | |
/// <param name="canBeMoreThanOne">todo: do I need this variable.</param> | |
/// <returns>A pointer to the last value created. If nothing was created NULL will be returned.</returns> | |
JsonData* JsonData::array(String* strElements, JsonData* parent, bool canBeMoreThanOne) | |
{ | |
// all elements must be children of parent | |
const int len = strElements->length(); | |
if (parent == NULL || len < 2 || strElements->charAt(0) != '[') | |
return setRootInvalid(); | |
if (!canBeMoreThanOne) { | |
if (strElements->charAt(len - 1) != ']') | |
return setRootInvalid(); | |
*strElements = strElements->substring(1, len - 1); | |
return elements(strElements, parent); | |
} | |
//there can be more than array | |
int endOf = getIndexOfClosingToken(strElements); | |
if (endOf < 1) | |
return setRootInvalid(); | |
String strArr = strElements->substring(1, endOf); | |
endOf++; | |
if (endOf < len) | |
endOf += strElements->charAt(endOf) == ','; | |
*strElements = strElements->substring(endOf); | |
JsonData* pNew = new JsonData(JSONTYPE_ARRAY, parent); | |
return elements(&strArr, pNew); | |
} | |
/// <summary> | |
/// Will tell you if the current object is invalid | |
/// </summary> | |
bool JsonData::isValid() const | |
{ | |
return (mType == JSONTYPE_KEY_VALUE && mValueType != JSONTYPE_INVALID) || | |
(mType != JSONTYPE_INVALID); | |
} | |
/// <summary> | |
/// Will return the value of the current object. | |
/// </summary> | |
/// <returns>A string showing the value. If value type is a string then quotation at the beginning and end of the returned string.</returns> | |
String JsonData::valueToString() { | |
String strRet; | |
if (mType == JSONTYPE_STRING || mValueType == JSONTYPE_STRING) | |
{ | |
strRet = "\""; | |
strRet += mValue.c_str(); | |
strRet += "\""; | |
return strRet; | |
} | |