Skip to content

Instantly share code, notes, and snippets.

@gabonator
Created March 31, 2018 19:31
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gabonator/48bad5e70ac3926113fc7c1d095000cb to your computer and use it in GitHub Desktop.
Save gabonator/48bad5e70ac3926113fc7c1d095000cb to your computer and use it in GitHub Desktop.
ESP8266 based Wifi CNC controller: http server, websocket server, embedded html resources
#include "wifi.h"
#define _ASSERT(x)
class HX711
{
enum {
PD_SCK=D7,
DOUT=D6,
};
unsigned char GAIN=1;
public:
void begin(byte gain = 128) {
pinMode(PD_SCK, OUTPUT);
pinMode(DOUT, INPUT);
set_gain(gain);
}
bool available() {
return digitalRead(DOUT) == LOW;
}
void set_gain(byte gain) {
switch (gain) {
case 128: // channel A, gain factor 128
GAIN = 1;
break;
case 64: // channel A, gain factor 64
GAIN = 3;
break;
case 32: // channel B, gain factor 32
GAIN = 2;
break;
}
digitalWrite(PD_SCK, LOW);
read();
}
long read() {
/*
// wait for the chip to become ready
while (!is_ready()) {
// Will do nothing on Arduino but prevent resets of ESP8266 (Watchdog Issue)
yield();
}*/
unsigned long value = 0;
uint8_t data[3] = { 0 };
uint8_t filler = 0x00;
// pulse the clock pin 24 times to read the data
data[2] = shiftIn(DOUT, PD_SCK, MSBFIRST);
data[1] = shiftIn(DOUT, PD_SCK, MSBFIRST);
data[0] = shiftIn(DOUT, PD_SCK, MSBFIRST);
// set the channel and the gain factor for the next reading using the clock pin
for (unsigned int i = 0; i < GAIN; i++) {
digitalWrite(PD_SCK, HIGH);
digitalWrite(PD_SCK, LOW);
}
// Replicate the most significant bit to pad out a 32-bit signed integer
if (data[2] & 0x80) {
filler = 0xFF;
} else {
filler = 0x00;
}
// Construct a 32-bit signed integer
value = ( static_cast<unsigned long>(filler) << 24
| static_cast<unsigned long>(data[2]) << 16
| static_cast<unsigned long>(data[1]) << 8
| static_cast<unsigned long>(data[0]) );
return static_cast<long>(value);
}
void power_down() {
digitalWrite(PD_SCK, LOW);
digitalWrite(PD_SCK, HIGH);
}
void power_up() {
digitalWrite(PD_SCK, LOW);
}
};
class CCncAxis
{
#define function void
#define var long
private:
long maxSpeed = 4000; // < fixed
public:
long fixed = 4000;
long ticksPerMm = 800;
long currentStep = 0;
long current = 0;
long target = 0;
long speed = 0;
bool userLeft = false;
bool userRight = false;
bool enableFollow = false;
long maxUserSpeed = maxSpeed;
public:
byte pinDirection = false;
byte pinStep = false;
public:
function safe()
{
userLeft = false;
userRight = false;
enableFollow = false;
speed = 0;
}
function update()
{
if (enableFollow)
follow();
else
userMove();
current += speed;
updateDriver();
}
function userMove()
{
if (userRight)
{
if (speed < maxUserSpeed)
speed++;
} else
if (userLeft)
{
if (speed > -maxUserSpeed)
speed--;
} else
{
if (speed > 0)
speed--;
if (speed < 0)
speed++;
}
}
function updateDriver()
{
pinDirection = speed < 0;
if (current >= currentStep+fixed)
{
pinStep = !pinStep;
currentStep += fixed;
} else
if (current < currentStep)
{
pinStep = !pinStep;
currentStep -= fixed;
}
}
function follow()
{
if (current == target)
{
if (abs(speed) < 3)
{
speed = 0;
return;
}
}
// tolkoto preleti keby zacal okamzite brzdit
var speedSq = speed*speed/2;
if (current < target)
{
if (speed <= 0)
{
incSpeed();
//updateSpeed(+1);
}
else
{
if (current + speedSq + 20*speed < target) // daleko aj keby zacal o 20it spomalovat
{
incSpeed();
//updateSpeed(+1);
}
else if (speed > 1 && current + speedSq > target) //keby nespomaloval, preleti
{
decSpeed();
//updateSpeed(-1);
}
}
}
else if (current > target)
{
if (speed >= 0)
{
decSpeed();
//updateSpeed(-1);
}
else
{
if (current - speedSq + 20*speed > target) // daleko aj keby zacal o 20it spomalovat
{
decSpeed();
//updateSpeed(-1);
}
else if (speed < -1 && current - speedSq < target) //keby nespomaloval, preleti
{
incSpeed();
//updateSpeed(+1);
}
}
}
}
inline void incSpeed()
{
if (speed < maxSpeed)
speed++;
}
inline void decSpeed()
{
if (speed > -maxSpeed)
speed--;
}
inline function updateSpeed(int d)
{
speed += d;
if (speed >= maxSpeed)
speed = maxSpeed;
if (speed <= -maxSpeed)
speed = -maxSpeed;
}
};
class CTimer
{
typedef void (*THandler)();
public:
void setup(long frequency, THandler handler)
{
timer1_isr_init();
timer1_attachInterrupt(handler);
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
set(frequency);
}
void set(long frequency)
{
const long intervalUs = 1000000UL / frequency;
timer1_write((clockCyclesPerMicrosecond() / 16) * intervalUs); // 80/16
}
};
class CCncHw
{
public:
CCncHw()
{
// D5 - led
/*
pinMode(D12, OUTPUT);
pinMode(D13, OUTPUT);
digitalWrite(D12, LOW);
digitalWrite(D13, LOW);
*/
pinMode(D13, OUTPUT);
pinMode(D14, OUTPUT);
pinMode(D15, OUTPUT);
digitalWrite(D13, LOW);
digitalWrite(D14, LOW);
digitalWrite(D15, LOW);
}
void update(bool dir, bool step)
{
/*
digitalWrite(D12, step);
digitalWrite(D13, dir);
*/
digitalWrite(D15, step);
digitalWrite(D14, dir);
}
};
class CComm
{
public:
CComm()
{
Serial.begin(115200);
//Serial.begin(250000);
}
operator bool()
{
return Serial.available();
}
operator char()
{
return Serial.read();
}
};
class CTokenizer
{
enum {
bufLen = 32
};
public:
char buffer[bufLen];
int offset = 0;
bool available = false;
public:
char* operator<<(char c)
{
if (c==0x0d || c==0x0a)
{
if (!overflow())
{
if (offset>0)
{
buffer[offset] = 0;
available = true;
offset = 0;
return buffer;
}
offset = 0;
return NULL;
} else
{
offset = 0;
_ASSERT(0);
return NULL;
}
}
available = false;
if (!overflow())
buffer[offset++] = c;
return NULL;
}
bool overflow()
{
return offset >= bufLen-2;
}
operator bool()
{
return available;
}
operator char*()
{
_ASSERT(available);
available = false;
return buffer;
}
};
class CInterface
{
friend class CApp;
protected:
bool bLeft;
bool bRight;
bool bPanic;
unsigned long lMovementTill = 0;
bool bStartFollow = false;
bool bFun = false;
bool bFollow = false;
long target = 0;
bool targetSet = false;
long newSpeed = 0;
bool requestReset = false;
bool requestForceReset = false;
bool reportGlitch = false;
bool requestConfig = false;
long current = 0;
bool currentSet = false;
long newFrequency = 0;
int relative = 0;
float functionK = PI*2.0f/4000.0f; // 2*pi / T
float functionA = 20000.0f * 4000.0f;
float functionO = 0.0f * 4000.0f;
const long cnc_fixed = 4000;
public:
CInterface()
{
}
bool operator<<(char* str)
{
if (!str)
return false;
if (strcmp(str, "fun")==0)
return onFun(), true;
if (strcmp(str, "left")==0 || strcmp(str, "l")==0)
return onLeft(), true;
if (strcmp(str, "right")==0 || strcmp(str, "r")==0)
return onRight(), true;
if (strcmp(str, "stop")==0 || strcmp(str, "s")==0)
return onStop(), true;
if (strcmp(str, "panic")==0 || strcmp(str, "p")==0)
return onPanic(), true;
if (strcmp(str, "reset")==0)
return onReset(), true;
if (strcmp(str, "freset")==0)
return onForceReset(), true;
if (strncmp(str, "go=", 3)==0)
return onGo(atoi(str+3)), true;
if (strncmp(str, "set=", 4)==0)
return onSet(atoi(str+4)), true;
if (strncmp(str, "rel=", 4)==0)
return onRelative(atoi(str+4)), true;
if (strncmp(str, "speed=", 6)==0)
return onSpeed(atoi(str+6)), true;
if (strcmp(str, "glitch")==0)
return onGlitch(), true;
if (strcmp(str, "config")==0)
return onConfig(), true;
if (strncmp(str, "freq=", 5)==0)
return onFreq(atoi(str+5)), true;
if (strncmp(str, "funt=", 5)==0)
return functionK = PI*2.0f/(atof(str+5)*1000.0f), true;
if (strncmp(str, "funa=", 5)==0)
return functionA = atof(str+5)*cnc_fixed, true;
if (strncmp(str, "funo=", 5)==0)
return functionO = atof(str+5)*cnc_fixed, true;
if (strcmp(str, "fget")==0)
{
Serial.print("_f(");
Serial.print(trajectory(millis())/cnc_fixed);
Serial.print(")\n");
return true;
}
Serial.print("Unknown command='");
Serial.print(str);
Serial.print("'\n");
return false;
}
void onFreq(long f)
{
newFrequency = f;
}
void onConfig()
{
requestConfig = true;
}
void onGlitch()
{
reportGlitch = !reportGlitch;
}
void onGo(int pos)
{
if (!bFollow)
bStartFollow = true;
bFun = false;
target = pos;
targetSet = true;
}
void onSet(int pos)
{
current = pos;
currentSet = true;
}
void onRelative(int delta)
{
if (!bFollow)
bStartFollow = true;
relative += delta;
}
void onSpeed(int speed)
{
newSpeed = speed;
}
void onFun()
{
if (bFun)
{
bFun = false;
return;
}
if (bFollow)
{
bFun = true;
return;
}
bStartFollow = true;
bFun = true;
}
void onLeft()
{
//Serial.print("left");
lMovementTill = millis() + 1000;
bFollow = false;
bLeft = true;
bRight = false;
}
void onRight()
{
//Serial.print("right");
lMovementTill = millis() + 1000;
bFollow = false;
bLeft = false;
bRight = true;
}
void onStop()
{
bFollow = false;
bLeft = false;
bRight = false;
}
void onPanic()
{
bPanic = true;
}
void onReset()
{
requestReset = true;
}
void onForceReset()
{
requestForceReset = true;
}
int trajectory(long tick)
{
return (int)(sin(tick*functionK)*functionA+functionO);
}
void recalculate(CCncAxis& cnc)
{
if (bFollow && bFun)
{
static long lLastTick = 0;
long lTick = millis();
if (lLastTick != lTick)
{
cnc.target = trajectory(lTick);
lLastTick = lTick;
}
}
}
void apply(CCncAxis& cnc)
{
unsigned long tick = millis();
if (requestReset)
{
requestReset = false;
bLeft = false;
bRight = false;
bFollow = false;
cnc.current = 0;
cnc.target = 0;
cnc.speed = 0;
}
if (bPanic)
{
bLeft = false;
bRight = false;
bPanic = false;
bFollow = false;
cnc.safe();
}
if (newSpeed != 0)
{
cnc.maxUserSpeed = newSpeed;
newSpeed = 0;
}
if (bStartFollow)
{
bStartFollow = false;
bFollow = true;
//cnc.current = 0;
//cnc.target = 0;
}
if (currentSet)
{
currentSet = false;
cnc.currentStep = current;
cnc.current = current;
cnc.target = current;
}
cnc.enableFollow = bFollow;
if (bFollow && !bFun)
{
if (targetSet)
{
targetSet = false;
cnc.target = target * cnc.fixed;
}
if (relative != 0)
{
cnc.target += relative;
relative = 0;
}
}
else if (bFollow && bFun)
{
// moved to recalculate()
recalculate(cnc); // 7..127
} else
{
bLeft = bLeft && tick < lMovementTill;
bRight = bRight && tick < lMovementTill;
cnc.userLeft = bLeft;
cnc.userRight = bRight;
}
}
};
class CTicker
{
typedef void (*THandler)();
long mInterval{0};
long mNext{0};
public:
CTicker(int interval) : mInterval(interval)
{
}
void operator()( void (*handler)() )
{
long now = millis();
if (now > mNext)
{
mNext += mInterval;
handler();
}
}
};
class CGlitchMonitor
{
long mLastTick{0};
public:
long mMin{0};
long mMax{0};
long mCount{0};
long mLoadCount{0};
public:
void operator()()
{
mCount++;
long now = micros();
if (mLastTick==0)
{
mLastTick = now;
return;
}
long passed = now - mLastTick;
mLastTick = now;
if (mMin==0)
mMin = mMax = passed;
else
{
if (passed < mMin)
mMin = passed;
if (passed > mMax)
mMax = passed;
}
}
void reset()
{
mMin = 0;
mCount = 0;
mLoadCount = 0;
}
};
class CApp
{
static CApp* mSingleton;
CTimer mTimer;
CCncAxis mCnc;
CCncHw mHw;
CComm mComm;
CTokenizer mTokenizer;
CInterface mInterface;
CTicker mTickerFast{50};
CTicker mTickerSlow{1000};
CGlitchMonitor mMonitor;
CWifi wifi;
unsigned long frequency{30000};
long mLoadRef{0};
long mLoadValue{0};
HX711 mLoadSensor;
public:
CApp()
{
mSingleton = this;
mLoadSensor.begin();
}
void startMainTimer()
{
mTimer.setup(frequency, []() {
CApp::mSingleton->timer();
});
}
void connect()
{
wifi.connect();
}
void timer()
{
mMonitor();
//mInterface.recalculate(mCnc);
mCnc.update();
mHw.update(mCnc.pinDirection, mCnc.pinStep);
}
void operator()()
{
if (mLoadSensor.available())
{
long lLoad = mLoadSensor.read();
if (mLoadRef==0 || mInterface.requestForceReset)
{
mLoadRef = lLoad;
mInterface.requestForceReset = false;
}
mLoadValue = (lLoad - mLoadRef)>>7; // /128
if (mLoadValue > 32767)
mLoadValue = 32767;
if (mLoadValue < -32767)
mLoadValue = -32767;
static bool wasSafety = false;
if (abs(mLoadValue) > 16000 && !wasSafety)
{
wasSafety = true;
// > 200g safety limit
mInterface.onPanic();
Serial.print("load cell safety!\n");
}
if (wasSafety && abs(mLoadValue) < 200)
{
wasSafety = false;
Serial.print("load cell ok\n");
}
mMonitor.mLoadCount++;
}
wifi();
while (mComm)
mInterface << (mTokenizer << mComm);
char *pdata = wifi.wsGetData();
if (pdata)
{
Serial.print(pdata);
while (*pdata)
mInterface << (mTokenizer << *pdata++);
}
mInterface.apply(mCnc);
if (mInterface.newFrequency)
{
mTimer.set(mInterface.newFrequency);
mInterface.newFrequency = 0;
}
if (mInterface.requestConfig)
{
mInterface.requestConfig = false;
Serial.print("_config({freq:");
Serial.print(frequency);
Serial.print(",fixed:");
Serial.print(mCnc.fixed);
Serial.print(",permm:");
Serial.print(mCnc.ticksPerMm);
Serial.print(",per100g:");
Serial.print(8300);
Serial.print("});\n");
}
mTickerFast([]()
{
CApp& _this = *CApp::mSingleton;
char message[64];
sprintf(message, "_([%d,%d,%d])\n", _this.mCnc.current/_this.mCnc.fixed, _this.mLoadValue, _this.mCnc.speed);
_this.wifi.wsSend(message);
//Serial.print(message);
});
mTickerSlow([]()
{
CApp* _this = CApp::mSingleton;
if (_this->mInterface.reportGlitch)
{
char msg[64];
sprintf(msg, "_g({min:%d,max:%d,cnt:%d,ldc:%d});\n",
_this->mMonitor.mMin,
_this->mMonitor.mMax,
_this->mMonitor.mCount,
_this->mMonitor.mLoadCount);
_this->mMonitor.reset();
_this->wifi.wsSend(msg);
}
Serial.print("running...\n");
});
}
};
/*static*/ CApp* CApp::mSingleton(NULL);
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
CApp app;
app.connect();
app.startMainTimer();
while (1)
{
app();
yield();
}
}
void loop()
{
}
/*
* sha1.c
*
* Description:
* This file implements the Secure Hashing Algorithm 1 as
* defined in FIPS PUB 180-1 published April 17, 1995.
*
* The SHA-1, produces a 160-bit message digest for a given
* data stream. It should take about 2**n steps to find a
* message with the same digest as a given message and
* 2**(n/2) to find any two messages with the same digest,
* when n is the digest size in bits. Therefore, this
* algorithm can serve as a means of providing a
* "fingerprint" for a message.
*
* Portability Issues:
* SHA-1 is defined in terms of 32-bit "words". This code
* uses <stdint.h> (included via "sha1.h" to define 32 and 8
* bit unsigned integer types. If your C compiler does not
* support 32 bit unsigned integers, this code is not
* appropriate.
*
* Caveats:
* SHA-1 is designed to work with messages less than 2^64 bits
* long. Although SHA-1 allows a message digest to be generated
* for messages of any number of bits less than 2^64, this
* implementation only works with messages with a length that is
* a multiple of the size of an 8-bit character.
*
*/
#include "crypto.h"
/*
* Define the SHA1 circular left shift macro
*/
#define SHA1CircularShift(bits,word) \
(((word) << (bits)) | ((word) >> (32-(bits))))
/* Local Function Prototyptes */
void SHA1PadMessage(SHA1Context *);
void SHA1ProcessMessageBlock(SHA1Context *);
/*
* SHA1Reset
*
* Description:
* This function will initialize the SHA1Context in preparation
* for computing a new SHA1 message digest.
*
* Parameters:
* context: [in/out]
* The context to reset.
*
* Returns:
* sha Error Code.
*
*/
int SHA1Reset(SHA1Context *context)
{
if (!context)
{
return shaNull;
}
context->Length_Low = 0;
context->Length_High = 0;
context->Message_Block_Index = 0;
context->Intermediate_Hash[0] = 0x67452301;
context->Intermediate_Hash[1] = 0xEFCDAB89;
context->Intermediate_Hash[2] = 0x98BADCFE;
context->Intermediate_Hash[3] = 0x10325476;
context->Intermediate_Hash[4] = 0xC3D2E1F0;
context->Computed = 0;
context->Corrupted = 0;
return shaSuccess;
}
/*
* SHA1Result
*
* Description:
* This function will return the 160-bit message digest into the
* Message_Digest array provided by the caller.
* NOTE: The first octet of hash is stored in the 0th element,
* the last octet of hash in the 19th element.
*
* Parameters:
* context: [in/out]
* The context to use to calculate the SHA-1 hash.
* Message_Digest: [out]
* Where the digest is returned.
*
* Returns:
* sha Error Code.
*
*/
int SHA1Result( SHA1Context *context,
uint8_t Message_Digest[SHA1HashSize])
{
int i;
if (!context || !Message_Digest)
{
return shaNull;
}
if (context->Corrupted)
{
return context->Corrupted;
}
if (!context->Computed)
{
SHA1PadMessage(context);
for(i=0; i<64; ++i)
{
/* message may be sensitive, clear it out */
context->Message_Block[i] = 0;
}
context->Length_Low = 0; /* and clear length */
context->Length_High = 0;
context->Computed = 1;
}
for(i = 0; i < SHA1HashSize; ++i)
{
Message_Digest[i] = context->Intermediate_Hash[i>>2]
>> 8 * ( 3 - ( i & 0x03 ) );
}
return shaSuccess;
}
/*
* SHA1Input
*
* Description:
* This function accepts an array of octets as the next portion
* of the message.
*
* Parameters:
* context: [in/out]
* The SHA context to update
* message_array: [in]
* An array of characters representing the next portion of
* the message.
* length: [in]
* The length of the message in message_array
*
* Returns:
* sha Error Code.
*
*/
int SHA1Input( SHA1Context *context,
const uint8_t *message_array,
unsigned length)
{
if (!length)
{
return shaSuccess;
}
if (!context || !message_array)
{
return shaNull;
}
if (context->Computed)
{
context->Corrupted = shaStateError;
return shaStateError;
}
if (context->Corrupted)
{
return context->Corrupted;
}
while(length-- && !context->Corrupted)
{
context->Message_Block[context->Message_Block_Index++] =
(*message_array & 0xFF);
context->Length_Low += 8;
if (context->Length_Low == 0)
{
context->Length_High++;
if (context->Length_High == 0)
{
/* Message is too long */
context->Corrupted = 1;
}
}
if (context->Message_Block_Index == 64)
{
SHA1ProcessMessageBlock(context);
}
message_array++;
}
return shaSuccess;
}
/*
* SHA1ProcessMessageBlock
*
* Description:
* This function will process the next 512 bits of the message
* stored in the Message_Block array.
*
* Parameters:
* None.
*
* Returns:
* Nothing.
*
* Comments:
* Many of the variable names in this code, especially the
* single character names, were used because those were the
* names used in the publication.
*
*
*/
void SHA1ProcessMessageBlock(SHA1Context *context)
{
const uint32_t K[] = { /* Constants defined in SHA-1 */
0x5A827999,
0x6ED9EBA1,
0x8F1BBCDC,
0xCA62C1D6
};
int t; /* Loop counter */
uint32_t temp; /* Temporary word value */
uint32_t W[80]; /* Word sequence */
uint32_t A, B, C, D, E; /* Word buffers */
/*
* Initialize the first 16 words in the array W
*/
for(t = 0; t < 16; t++)
{
W[t] = context->Message_Block[t * 4] << 24;
W[t] |= context->Message_Block[t * 4 + 1] << 16;
W[t] |= context->Message_Block[t * 4 + 2] << 8;
W[t] |= context->Message_Block[t * 4 + 3];
}
for(t = 16; t < 80; t++)
{
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
}
A = context->Intermediate_Hash[0];
B = context->Intermediate_Hash[1];
C = context->Intermediate_Hash[2];
D = context->Intermediate_Hash[3];
E = context->Intermediate_Hash[4];
for(t = 0; t < 20; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | ((~B) & D)) + E + W[t] + K[0];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 20; t < 40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 40; t < 60; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 60; t < 80; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
context->Intermediate_Hash[0] += A;
context->Intermediate_Hash[1] += B;
context->Intermediate_Hash[2] += C;
context->Intermediate_Hash[3] += D;
context->Intermediate_Hash[4] += E;
context->Message_Block_Index = 0;
}
/*
* SHA1PadMessage
*
* Description:
* According to the standard, the message must be padded to an even
* 512 bits. The first padding bit must be a '1'. The last 64
* bits represent the length of the original message. All bits in
* between should be 0. This function will pad the message
* according to those rules by filling the Message_Block array
* accordingly. It will also call the ProcessMessageBlock function
* provided appropriately. When it returns, it can be assumed that
* the message digest has been computed.
*
* Parameters:
* context: [in/out]
* The context to pad
* ProcessMessageBlock: [in]
* The appropriate SHA*ProcessMessageBlock function
* Returns:
* Nothing.
*
*/
void SHA1PadMessage(SHA1Context *context)
{
/*
* Check to see if the current message block is too small to hold
* the initial padding bits and length. If so, we will pad the
* block, process it, and then continue padding into a second
* block.
*/
if (context->Message_Block_Index > 55)
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 64)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
SHA1ProcessMessageBlock(context);
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
else
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
/*
* Store the message length as the last 8 octets
*/
context->Message_Block[56] = context->Length_High >> 24;
context->Message_Block[57] = context->Length_High >> 16;
context->Message_Block[58] = context->Length_High >> 8;
context->Message_Block[59] = context->Length_High;
context->Message_Block[60] = context->Length_Low >> 24;
context->Message_Block[61] = context->Length_Low >> 16;
context->Message_Block[62] = context->Length_Low >> 8;
context->Message_Block[63] = context->Length_Low;
SHA1ProcessMessageBlock(context);
}
const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
/* 'Private' declarations */
inline void a3_to_a4(unsigned char * a4, unsigned char * a3);
inline void a4_to_a3(unsigned char * a3, unsigned char * a4);
inline unsigned char b64_lookup(char c);
int base64_encode(char *output, char *input, int inputLen) {
int i = 0, j = 0;
int encLen = 0;
unsigned char a3[3];
unsigned char a4[4];
while(inputLen--) {
a3[i++] = *(input++);
if(i == 3) {
a3_to_a4(a4, a3);
for(i = 0; i < 4; i++) {
output[encLen++] = b64_alphabet[a4[i]];
}
i = 0;
}
}
if(i) {
for(j = i; j < 3; j++) {
a3[j] = '\0';
}
a3_to_a4(a4, a3);
for(j = 0; j < i + 1; j++) {
output[encLen++] = b64_alphabet[a4[j]];
}
while((i++ < 3)) {
output[encLen++] = '=';
}
}
output[encLen] = '\0';
return encLen;
}
int base64_decode(char * output, char * input, int inputLen) {
int i = 0, j = 0;
int decLen = 0;
unsigned char a3[3];
unsigned char a4[4];
while (inputLen--) {
if(*input == '=') {
break;
}
a4[i++] = *(input++);
if (i == 4) {
for (i = 0; i <4; i++) {
a4[i] = b64_lookup(a4[i]);
}
a4_to_a3(a3,a4);
for (i = 0; i < 3; i++) {
output[decLen++] = a3[i];
}
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++) {
a4[j] = '\0';
}
for (j = 0; j <4; j++) {
a4[j] = b64_lookup(a4[j]);
}
a4_to_a3(a3,a4);
for (j = 0; j < i - 1; j++) {
output[decLen++] = a3[j];
}
}
output[decLen] = '\0';
return decLen;
}
int base64_enc_len(int plainLen) {
int n = plainLen;
return (n + 2 - ((n + 2) % 3)) / 3 * 4;
}
int base64_dec_len(char * input, int inputLen) {
int i = 0;
int numEq = 0;
for(i = inputLen - 1; input[i] == '='; i--) {
numEq++;
}
return ((6 * inputLen) / 8) - numEq;
}
inline void a3_to_a4(unsigned char * a4, unsigned char * a3) {
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = (a3[2] & 0x3f);
}
inline void a4_to_a3(unsigned char * a3, unsigned char * a4) {
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
}
inline unsigned char b64_lookup(char c) {
int i;
for(i = 0; i < 64; i++) {
if(b64_alphabet[i] == c) {
return i;
}
}
return -1;
}
/*
* sha1.h
*
* Description:
* This is the header file for code which implements the Secure
* Hashing Algorithm 1 as defined in FIPS PUB 180-1 published
* April 17, 1995.
*
* Many of the variable names in this code, especially the
* single character names, were used because those were the names
* used in the publication.
*
* Please read the file sha1.c for more information.
*
*/
#ifndef _SHA1_H_
#define _SHA1_H_
#include <stdint.h>
/*
* If you do not have the ISO standard stdint.h header file, then you
* must typdef the following:
* name meaning
* uint32_t unsigned 32 bit integer
* uint8_t unsigned 8 bit integer (i.e., unsigned char)
* int_least16_t integer of >= 16 bits
*
*/
#ifndef _SHA_enum_
#define _SHA_enum_
enum
{
shaSuccess = 0,
shaNull, /* Null pointer parameter */
shaInputTooLong, /* input data too long */
shaStateError /* called Input after Result */
};
#endif
#define SHA1HashSize 20
/*
* This structure will hold context information for the SHA-1
* hashing operation
*/
typedef struct SHA1Context
{
uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
uint32_t Length_Low; /* Message length in bits */
uint32_t Length_High; /* Message length in bits */
/* Index into message block array */
int_least16_t Message_Block_Index;
uint8_t Message_Block[64]; /* 512-bit message blocks */
int Computed; /* Is the digest computed? */
int Corrupted; /* Is the message digest corrupted? */
} SHA1Context;
/*
* Function Prototypes
*/
int SHA1Reset( SHA1Context *);
int SHA1Input( SHA1Context *,
const uint8_t *,
unsigned int);
int SHA1Result( SHA1Context *,
uint8_t Message_Digest[SHA1HashSize]);
/* base64_encode:
* Description:
* Encode a string of characters as base64
* Parameters:
* output: the output buffer for the encoding, stores the encoded string
* input: the input buffer for the encoding, stores the binary to be encoded
* inputLen: the length of the input buffer, in bytes
* Return value:
* Returns the length of the encoded string
* Requirements:
* 1. output must not be null or empty
* 2. input must not be null
* 3. inputLen must be greater than or equal to 0
*/
int base64_encode(char *output, char *input, int inputLen);
/* base64_decode:
* Description:
* Decode a base64 encoded string into bytes
* Parameters:
* output: the output buffer for the decoding,
* stores the decoded binary
* input: the input buffer for the decoding,
* stores the base64 string to be decoded
* inputLen: the length of the input buffer, in bytes
* Return value:
* Returns the length of the decoded string
* Requirements:
* 1. output must not be null or empty
* 2. input must not be null
* 3. inputLen must be greater than or equal to 0
*/
int base64_decode(char *output, char *input, int inputLen);
#endif
const /*PROGMEM*/ char _index_html[] = R"---(
<html>
<head>
<link rel="icon" type="image/png" href="/favicon.png"/>
</head>
<body>
<input type="button" value="<<<" onMouseDown="ui.onLeft(3)" onMouseUp="ui.onStop()">
<input type="button" value="<<" onMouseDown="ui.onLeft(2)" onMouseUp="ui.onStop()">
<input type="button" value="<" onMouseDown="ui.onLeft(1)" onMouseUp="ui.onStop()">
<input type="button" value="[panic]" onMouseDown="ui.onPanic()">
<input type="button" value=">" onMouseDown="ui.onRight(1)" onMouseUp="ui.onStop()">
<input type="button" value=">>" onMouseDown="ui.onRight(2)" onMouseUp="ui.onStop()">
<input type="button" value=">>>" onMouseDown="ui.onRight(3)" onMouseUp="ui.onStop()">
<input type="button" value="fun" onMouseDown="ui.onFun()">
<br>
<input type="button" value="manual" onMouseDown="ui.onManual()">
<input type="button" value="go(0)" onMouseDown="ui.goZero()">
<input type="button" value="set(0)" onMouseDown="ui.setZero()">
<input type="button" value="en/dis glitch" onMouseDown="ui.toggleGlitch()">
<br>
<input type="button" value="clear" onMouseDown="ui.clear()">
<input type="button" value="force reset" onMouseDown="ui.forceReset()">
<input type="button" value="set min" onMouseDown="ui.setMin()">
<input type="button" value="set max" onMouseDown="ui.setMax()">
&nbsp;Period[s]:<input type="text" size="8" value="4.0" onChange="ui.setPeriod(this.value)">
<input type="button" value="calculate" onMouseDown="ui.calcFunction()">
<div id="info"></div>
<div id="extra"></div>
<div style="width:400px; height:400px">
<canvas id="canBlend" width="400" height="400" style="position:absolute; border:#000 solid 1px;"></canvas>
<canvas id="canHold" width="400" height="400" style="position:absolute; border:#000 solid 1px;"></canvas>
<canvas id="canCurrent" width="400" height="400" style="position:absolute; border:#000 solid 1px;"></canvas>
<canvas id="canGrid" width="400" height="400" style="position:absolute; border:#000 solid 1px;"></canvas>
</div>
</body>
<script>
Canvas = function(id)
{
this.can = document.getElementById(id);
this.ctx = this.can.getContext("2d");
this.clear();
}
Canvas.prototype.dot = function(x, y, clr, size)
{
size = size || 4;
this.ctx.fillStyle = clr; //"rgba(128, 0, 0, 0.8)";
this.ctx.fillRect(x-size/2, y-size/2, size, size);
}
Canvas.prototype.line = function(x0, y0, x1, y1, clr)
{
this.ctx.strokeStyle = clr;
this.ctx.beginPath();
this.ctx.moveTo(x0, y0);
this.ctx.lineTo(x1, y1);
this.ctx.stroke();
}
Canvas.prototype.clear = function()
{
this.ctx.clearRect(0, 0, this.can.width, this.can.height);
}
Canvas.prototype.blend = function()
{
this.ctx.fillStyle = "rgba(255, 255, 255, 0.08)";
this.ctx.fillRect(0, 0, this.can.width, this.can.height);
}
Graph = function()
{
this.hold = new Canvas("canHold");
// this.hold.ctx.globalAlpha = 0.3;
this.hold.can.style.opacity = 0.1;
this.canvas = new Canvas("canBlend");
this.canvas.ctx.lineWidth = 5;
this.range = {xmin:-24000, xmax:24000, ymin:-5000, ymax:5000}; //spd 4000
this.grid = new Canvas("canGrid");
this.width = this.canvas.can.width;
this.height = this.canvas.can.height;
this.grid.ctx.globalAlpha = 0.5;
this.current = new Canvas("canCurrent");
this.init();
}
Graph.prototype.init = function()
{
this.lastPos = false;
this.line(this.range.xmin, 0, this.range.xmax, 0, "#000000", this.grid);
this.line(0, this.range.ymin, 0, this.range.ymax, "#000000", this.grid);
}
Graph.prototype.clear = function()
{
this.canvas.clear();
this.hold.clear();
this.grid.clear();
this.init();
}
Graph.prototype.dot = function(x, y, clr)
{
var pos = this.project(x, y);
this.hold.dot(pos.x, pos.y, clr);
//this.canvas.dot(pos.x, pos.y, clr);
if (this.lastPos)
{
this.canvas.line(this.lastPos.x, this.lastPos.y, pos.x, pos.y, clr);
}
this.lastPos = pos;
this.canvas.blend();
this.current.clear();
this.current.dot(pos.x, pos.y, "#000000", 8);
}
Graph.prototype.debugDot = function(x, y, clr)
{
var pos = this.project(x, y);
this.current.dot(pos.x, pos.y, clr);
}
Graph.prototype.project = function(x, y)
{
return {
x:(x-this.range.xmin)*this.width/(this.range.xmax-this.range.xmin),
y:(y-this.range.ymin)*this.height/(this.range.ymax-this.range.ymin)};
}
Graph.prototype.line = function(x0, y0, x1, y1, clr, target)
{
var p0 = this.project(x0, y0);
var p1 = this.project(x1, y1);
target = target || this.canvas;
target.line(p0.x, p0.y, p1.x, p1.y, clr);
}
Comm = function()
{
this.buffer = "";
var base = location.hash ? location.hash.substr(1) : window.location.hostname;
var wsUri = "ws://" + base + ":88/";
//wsUri = "ws://localhost:2222";
this.socket = new WebSocket(wsUri);
this.socket.onopen = () => console.log("Connected!");
this.socket.onclose = () => console.log("Connection lost!");
this.socket.onerror = () => console.log("Connection lost!");
this.socket.onmessage = (function(msg)
{
this.buffer += new String(msg.data);
var lines = this.buffer.split("\r").join("\n").split("\n");
while ( lines.length > 1 )
comm.onData(lines.shift());
this.buffer = lines.join("\n");
}).bind(this);
}
Comm.prototype.send = function(msg)
{
console.log("<"+msg);
this.socket.send(msg+"\n");
}
Comm.prototype.onData = function(msg)
{
if (msg.charAt(0) == "_" && msg.charAt(1) == "(")
;
else
console.log(">"+msg);
if (msg.charAt(0) == '_')
eval("receiver."+msg);
}
Ui = function(comm)
{
this.comm = comm;
this.repeat = "";
this.speed = 0;
this.manual = false;
this.mouse = {x:0, y:0};
var _this = this;
this._timer = setInterval((function()
{
this.mtimer();
}).bind(this), 400);
document.onmousemove = (function(e)
{
this.mouse = {x:e.clientX, y:e.clientY};
}).bind(this);
}
Ui.prototype.mtimer = function()
{
if (this.repeat != "")
this.comm.send(this.repeat);
}
Ui.prototype.setSpeed = function(speed)
{
if (this.speed == speed)
return;
this.speed = speed;
this.comm.send("speed="+this.speed); // 4000 max?
}
Ui.prototype.onLeft = function(speed)
{
this.setSpeed(Math.floor(4000/3*speed)); // 4000 max?
this.repeat = "left";
this.comm.send("left");
}
Ui.prototype.onRight = function(speed)
{
this.setSpeed(Math.floor(4000/3*speed)); // 4000 max?
this.repeat = "right";
this.comm.send("right");
}
Ui.prototype.onStop = function()
{
this.repeat = "";
this.comm.send("stop");
}
Ui.prototype.onPanic = function()
{
this.repeat = "";
this.comm.send("panic");
}
Ui.prototype.onFun = function()
{
this.repeat = "";
this.comm.send("fun");
}
Ui.prototype.onManual = function()
{
this.manual = !this.manual;
}
Ui.prototype.goZero = function()
{
this.repeat = "";
this.comm.send("go=0");
}
Ui.prototype.setZero = function()
{
this.repeat = "";
this.comm.send("set=0");
}
Ui.prototype.toggleGlitch = function()
{
this.repeat = "";
this.comm.send("glitch");
}
Ui.prototype.clear = function()
{
graph.clear();
}
Ui.prototype.forceReset = function()
{
this.comm.send("freset");
}
Ui.prototype.setMin = function()
{
console.log("set min="+lastValue.pos);
funct.min = lastValue.pos;
}
Ui.prototype.setMax = function()
{
console.log("set max="+lastValue.pos);
funct.max = lastValue.pos;
}
Ui.prototype.setPeriod = function(period)
{
console.log("set period="+period);
funct.period = period;
}
Ui.prototype.calcFunction = function(period)
{
var offset = Math.floor((funct.min + funct.max)/2);
var amplitude = Math.floor((funct.max - funct.min)/2);
var period = funct.period;
var harmonic = ["funt="+period, "funa="+amplitude, "funo="+offset];
console.log(harmonic);
while (harmonic.length>0)
this.comm.send(harmonic.shift());
}
// clear, setMin, setMax, setPeriod, calcFunction
Receiver = function()
{
}
Receiver.prototype.calcPosition = (raw) => (raw/8000).toFixed(2) + "cm";
Receiver.prototype.calcForce = (raw) => (raw/8300*100).toFixed(2) + "g";
// speed==4000 - every interrupt tick send move signal to stepper
// isr runs 30k times per second
// 800 calls are required for 1mm step
// speed/4000*30000/8000 cm per second
Receiver.prototype.calcSpeed = (raw) => (raw/4000*30000/8000).toFixed(2) + "cm/s";
Receiver.prototype._ = function(json)
{
json = {pos:json[0], force:json[1], spd:json[2]};
lastValue = {pos:json.pos, force:json.force, spd:json.spd};
graph.dot(json.pos, json.force, "#f00");
document.getElementById("info").innerHTML = "position: " + this.calcPosition(json.pos) +
" force: " + this.calcForce(json.force) + " speed: " + this.calcSpeed(json.spd);
if (ui.manual)
{
var millis = (new Date()).getTime();
var pos = Math.floor(Math.sin(millis/2000.0)*20000);
var alpha10 = millis/10000.0;
alpha10 -= Math.floor(alpha10);
var alpha2 = millis/5000.0;
alpha2 -= Math.floor(alpha2);
// var pos = (-1+alpha10*2)*10000 - Math.sin(alpha2*Math.PI*2)*10000;
graph.debugDot(pos, json.spd, "#00f");
comm.send("go=" + Math.floor(pos));
}
graph.current.dot(ui.mouse.x - graph.current.can.offsetLeft, ui.mouse.y - graph.current.can.offsetTop, "#0b0");
}
Receiver.prototype._g = function(json)
{
document.getElementById("extra").innerHTML = JSON.stringify(json);
}
Receiver.prototype._f = function(json)
{
document.getElementById("extra").innerHTML = "synthesized function target="+JSON.stringify(json);
}
Receiver.prototype._config = function(json)
{
document.getElementById("extra").innerHTML = "config="+JSON.stringify(json);
}
var lastValue = {};
var funct = {min:-20000, max:+20000, period:4.0};
var comm = new Comm();
var ui = new Ui(comm);
var receiver = new Receiver();
var graph = new Graph();
</script>
</html>
)---";
const /*PROGMEM*/ char _favicon_png[] = {
0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x10,0x06,0x00,0x00,0x00,0xfa,0xf9,0xad,
0x9d,0x00,0x00,0x00,0x06,0x62,0x4b,0x47,0x44,0xff,0xff,0xff,0xff,0xff,0xff,0x09,0x58,0xf7,0xdc,0x00,0x00,0x00,0x09,0x70,0x48,0x59,0x73,0x00,0x00,0x00,0x48,0x00,
0x00,0x00,0x48,0x00,0x46,0xc9,0x6b,0x3e,0x00,0x00,0x0c,0x26,0x49,0x44,0x41,0x54,0x78,0xda,0xed,0xdd,0x6d,0x94,0x55,0xd5,0x7d,0xc7,0xf1,0xcf,0xb9,0xf7,0xce,0x03,
0x03,0xc3,0x0c,0xcf,0x6a,0x04,0x04,0x31,0x5a,0x45,0x51,0x5a,0x13,0x56,0x94,0xf8,0xc0,0xc4,0x56,0x6b,0x62,0xad,0x32,0x54,0xeb,0x73,0xd3,0xa6,0xf5,0xa1,0x26,0xc4,
0xae,0xd5,0x18,0xb5,0x8d,0x09,0x2b,0x31,0x76,0xa5,0x8d,0x31,0xda,0xa8,0x75,0x25,0xc6,0x36,0x32,0x36,0x5a,0x35,0x3e,0x33,0x60,0xac,0x56,0x03,0x88,0x88,0x28,0xa8,
0x80,0x08,0x0a,0x32,0x30,0x30,0x30,0xcc,0x9d,0xa7,0x7b,0xcf,0xe9,0x8b,0x73,0x2e,0x2a,0x4b,0x17,0x0a,0x63,0xce,0xc0,0xcc,0xf7,0xcd,0x5e,0xe7,0xee,0x73,0xf6,0xfe,
0x9f,0xbd,0x7f,0xfb,0x79,0x9f,0xbb,0x83,0xba,0x0b,0x1b,0x1a,0x58,0x16,0x59,0x05,0xd9,0x26,0xdd,0x90,0x0f,0xc5,0x14,0xf4,0xb3,0x2f,0x91,0x53,0x06,0x55,0x81,0xf1,
0x50,0x1c,0x95,0xdb,0x91,0xf1,0x2b,0xe1,0x47,0x23,0x34,0xc1,0xcb,0x97,0x0a,0xc0,0xb6,0xb4,0x2d,0xee,0xa7,0x87,0x88,0xc0,0x60,0x23,0xe1,0xc8,0x5b,0xe2,0x1f,0x67,
0x6e,0xc8,0xed,0x28,0xf1,0x49,0xc6,0x17,0x4e,0x80,0x67,0xe7,0x28,0x82,0xcd,0x69,0xdb,0xdd,0x4f,0x0f,0x91,0x05,0x43,0x73,0xbf,0x05,0x97,0x1a,0x03,0xf9,0x7f,0xca,
0x25,0xde,0x85,0x1d,0x25,0x3e,0xc9,0xf8,0xa7,0x9e,0xaa,0xaf,0xd7,0x2f,0x80,0x7d,0x86,0x13,0x4f,0x6c,0x68,0x80,0x9d,0x6a,0xf6,0x42,0x26,0x6d,0xc3,0xfa,0x49,0x97,
0x7e,0x01,0xf4,0x71,0xfa,0x05,0xd0,0xc7,0xc9,0xed,0x79,0x10,0xbf,0x1f,0xa6,0xe5,0x1b,0xe6,0xa1,0xc2,0xeb,0x36,0xe1,0x30,0x83,0xe4,0x51,0x1b,0xfc,0x83,0x31,0x98,
0x3f,0xe7,0xfe,0xfa,0x93,0xd0,0xfe,0x69,0xc5,0x5f,0xf7,0xc3,0x86,0x2b,0x91,0x0d,0xae,0xb6,0x1f,0xc6,0x99,0x61,0x11,0xc6,0x9a,0xe3,0x7c,0xbc,0x18,0xdd,0x60,0x01,
0xb6,0xcc,0xb9,0xa8,0xfe,0xbb,0x4a,0x7d,0xee,0xbd,0x80,0x5e,0x5f,0x03,0x9c,0x34,0xf1,0x81,0x53,0x91,0x0d,0x3e,0x27,0xcb,0x80,0x2b,0x82,0xf3,0x65,0x19,0x7f,0x72,
0x70,0xa1,0x32,0x0e,0xbf,0x46,0xb9,0x0e,0x06,0x2d,0xaa,0xfb,0xe7,0x86,0xdb,0x31,0xb7,0xa7,0xe3,0x9f,0xb6,0xbe,0xe1,0x65,0x4c,0xf6,0x6b,0x53,0x28,0x3b,0x20,0xaa,
0xf7,0x59,0x0e,0x9c,0x1c,0x0d,0x71,0x36,0x13,0x4f,0x8e,0xce,0x57,0x64,0xd8,0x02,0x77,0x68,0x25,0x93,0xab,0x9b,0xdc,0x70,0x01,0xf1,0x68,0x7b,0x6f,0xa0,0xd7,0x0b,
0x20,0x73,0x7f,0xf7,0xff,0xe0,0x46,0x23,0xe4,0x99,0x9a,0x33,0x54,0xc4,0x0d,0x2d,0x2a,0xe5,0xf8,0xd9,0x72,0xed,0x3a,0x38,0xf3,0x97,0x16,0x1b,0x41,0xd5,0xb4,0xa9,
0x8b,0x1e,0x1c,0xa1,0xd4,0xd7,0xed,0x09,0xca,0x74,0x91,0xb9,0x44,0x8b,0x41,0x1c,0xf2,0x8a,0x0d,0x22,0xbe,0x75,0xb6,0xd5,0x32,0xdc,0x36,0xc5,0x4a,0x6d,0x5c,0x7a,
0x3f,0x0e,0x64,0xd8,0x17,0x54,0xf9,0x2c,0xfe,0x22,0xed,0x74,0xfb,0xb8,0xf4,0xfa,0x26,0x20,0xb8,0x31,0x7c,0x9e,0x6c,0x9b,0x0a,0x5b,0xf9,0xe2,0x4f,0x14,0x05,0x4c,
0xdb,0x18,0x57,0xb2,0x83,0xaf,0xd4,0x2a,0xe4,0xed,0xeb,0xbd,0x64,0x3f,0xe6,0x4e,0xc9,0x1c,0xd4,0xb5,0x81,0xfc,0x79,0x62,0x71,0xef,0xf9,0x44,0xd6,0x16,0xeb,0x29,
0xdb,0x64,0x9b,0x02,0x47,0x96,0xeb,0x12,0x51,0x77,0x84,0x40,0xc4,0xc1,0x74,0xaa,0xa4,0x6c,0xa8,0x0a,0x47,0xf1,0xc8,0x22,0x0c,0x65,0xd3,0x36,0x5c,0xb3,0x37,0xb4,
0x04,0xbd,0x5e,0x00,0xda,0xa2,0x88,0xec,0x4a,0xc5,0xa0,0x95,0x9a,0xcb,0x45,0x06,0x51,0x79,0x0b,0x32,0x64,0xbb,0xe4,0x85,0x0c,0xf9,0xc7,0xe8,0x15,0xcd,0x54,0x3e,
0x19,0xfc,0x4b,0xd8,0x85,0x21,0xc9,0xd3,0x7b,0x2e,0x80,0xe7,0xe4,0xc9,0x7c,0x39,0x7a,0x0b,0x06,0x3f,0x1a,0x4c,0x85,0xaa,0xaf,0xa9,0x80,0xec,0x61,0xb1,0x3d,0x83,
0x3b,0x05,0x26,0x53,0xd5,0x44,0x30,0x09,0x77,0x25,0x4f,0x7f,0x2d,0xed,0xe4,0xdb,0x15,0xbd,0xbe,0x09,0xb0,0xc1,0x06,0x0c,0x14,0x89,0x50,0x9b,0xfc,0x5a,0x12,0x6e,
0x20,0x2e,0x66,0xd5,0x3a,0x75,0x60,0x88,0xff,0xf3,0xa0,0x9e,0x6c,0x02,0xb6,0x24,0xf1,0x75,0x80,0x61,0xe2,0x55,0x92,0xca,0xa4,0x70,0x07,0xe2,0x39,0xb6,0x6a,0xd4,
0x10,0x0c,0xc1,0xfe,0x69,0x27,0xd9,0x27,0xa1,0xf7,0x0b,0xa0,0x9f,0x4f,0x95,0x7e,0x01,0xf4,0x71,0xfa,0x05,0xd0,0xc7,0xe9,0x17,0x40,0x1f,0x27,0xb5,0x51,0xc0,0x97,
0x46,0xcd,0xde,0x82,0x20,0xba,0xd8,0x02,0x54,0x5a,0x68,0x8b,0x8f,0x9e,0x40,0x19,0x9c,0xb8,0xe5,0xbb,0x78,0x8f,0xea,0x60,0xaa,0x07,0x90,0xad,0xcb,0xcd,0x9e,0xfd,
0xbe,0xe7,0x76,0x9b,0x28,0x10,0x25,0xe1,0x04,0xe8,0x4a,0x7e,0xde,0xb9,0xe0,0x94,0xae,0x07,0xa2,0x15,0x35,0x75,0x75,0x3b,0xc5,0x5f,0xda,0x5a,0xb3,0x0e,0x74,0x47,
0x2f,0x83,0x8e,0xc6,0x8a,0x19,0x33,0xa4,0x38,0x5e,0x4c,0x4d,0x00,0xd1,0xa5,0xc1,0xfe,0xb8,0xc4,0x12,0x35,0xe4,0x86,0xa3,0x9c,0xec,0xf3,0x1f,0x72,0xe7,0x12,0xca,
0xb3,0x68,0x20,0x3b,0x28,0xf9,0xf1,0xb8,0xc4,0x5d,0x8a,0x88,0x4c,0xd6,0x50,0x5b,0x28,0xff,0xc3,0xec,0x23,0x9e,0xa5,0xa2,0xd5,0x90,0xe0,0x69,0xef,0x0d,0x07,0x77,
0x9f,0x2f,0x3b,0x8b,0x8a,0x26,0xe5,0x8e,0xa5,0x6c,0x86,0x58,0xa4,0x2f,0x26,0xbe,0xd3,0xe3,0xf9,0x80,0x60,0xa4,0x8c,0x66,0xca,0x66,0x0a,0xa3,0xc3,0xa8,0x38,0x44,
0x14,0x5c,0x45,0x74,0xcc,0x8e,0x70,0xe2,0x71,0xc9,0x33,0xb1,0x24,0x8a,0x3f,0x0f,0x0e,0x12,0x51,0x18,0x37,0xed,0x89,0x86,0x83,0x30,0xb5,0xf1,0x94,0xfa,0xd5,0x52,
0x10,0x42,0x7a,0xf3,0x00,0x6f,0x1b,0x41,0xae,0x42,0xa7,0x13,0x38,0xfa,0x34,0x0c,0x63,0xfc,0x87,0x0c,0xa1,0x82,0x6b,0xc9,0x9d,0x80,0x23,0x19,0xff,0x55,0x04,0x64,
0x0f,0x4d,0x3c,0x27,0x2b,0x53,0x60,0x64,0x65,0xf0,0x45,0x2d,0xd4,0x7d,0xde,0x30,0x7f,0xc6,0x86,0xc5,0xe2,0xc4,0xdc,0xb8,0xa7,0x66,0x06,0xe7,0x28,0xa7,0xe2,0x8f,
0x35,0xd9,0xc6,0xa4,0xe5,0xf6,0x37,0x84,0x01,0xdb,0x04,0x32,0x38,0x5e,0x46,0x07,0x83,0xde,0x50,0x66,0x1e,0xc7,0x8f,0xd2,0xe9,0x60,0x86,0xbf,0x8c,0x83,0x88,0xce,
0xdc,0x11,0x50,0x56,0x44,0xf1,0x62,0xb5,0xda,0x58,0x7b,0x9c,0x1a,0xcd,0x2c,0x19,0x1e,0xcc,0xf2,0x10,0x1d,0xa5,0x9a,0xaf,0x6b,0x37,0x4c,0xdc,0x23,0xd2,0x13,0xc0,
0x48,0xc3,0x28,0x9f,0x6b,0xa3,0x09,0x9c,0xde,0x8e,0x91,0xd4,0x7f,0x48,0x0d,0x20,0x20,0xf8,0x4f,0x54,0x31,0x6a,0x48,0x7c,0x5d,0xf6,0x9b,0xc4,0xef,0x1b,0x2a,0x64,
0x98,0xf0,0x37,0xca,0x0d,0xe7,0xef,0xff,0x44,0x60,0x30,0xdd,0x37,0x24,0xfe,0xdd,0x7b,0x6c,0xe7,0x40,0x01,0xc1,0x43,0xc6,0x18,0x40,0xcd,0x5b,0x32,0x02,0x6a,0xae,
0x4d,0x7c,0x7f,0x86,0x0e,0x86,0xcd,0x57,0xd0,0xc9,0xc5,0xbf,0x10,0x99,0x40,0x7e,0x7c,0xe2,0xdf,0xf8,0xbe,0xb7,0x08,0xe9,0xa8,0x57,0x66,0x3b,0x8f,0x9e,0x6c,0x80,
0x6a,0xde,0xfc,0x6b,0x47,0x44,0xdd,0x74,0x54,0xfa,0xad,0x8c,0x3e,0x25,0x80,0xbc,0xd3,0x50,0xa7,0x68,0x2c,0x43,0x6f,0xc2,0x00,0x0e,0x5d,0x95,0xa4,0xd6,0x37,0x76,
0xba,0x7b,0xa0,0x58,0x08,0x5b,0x92,0xeb,0x6f,0x27,0x6e,0x8d,0x40,0x96,0x81,0x5f,0x10,0xa8,0xe6,0xe0,0xbb,0xc4,0x25,0x7f,0x6c,0x8f,0xd9,0x19,0x57,0xdd,0xb7,0xcb,
0x09,0x70,0x7b,0x62,0xc7,0x0f,0x12,0xdf,0xb1,0xf1,0x0e,0xaa,0xf2,0x55,0xb1,0xfd,0x63,0x2e,0x47,0x25,0x6e,0x4b,0xfc,0x27,0xec,0x08,0x27,0x12,0x90,0xbf,0x47,0xa7,
0xfd,0x58,0xf8,0x63,0xed,0x8e,0x20,0x73,0x94,0x87,0x83,0xb5,0x24,0xfb,0xb0,0x52,0x20,0x3d,0x01,0xbc,0xeb,0x48,0xa2,0xb5,0x22,0x19,0xba,0xb7,0x0a,0x04,0x04,0x67,
0xc4,0xad,0x60,0xb0,0xf4,0x13,0x86,0x76,0xb5,0x38,0x63,0xe8,0xc9,0x59,0xc0,0x8f,0xe6,0x98,0x9d,0xae,0x4f,0xde,0x29,0xfe,0x53,0x3f,0xe2,0xb9,0x5b,0x85,0xb2,0x74,
0x37,0xeb,0x50,0x46,0xf8,0xfd,0xe8,0xce,0xe0,0x5c,0x74,0x9b,0xf4,0x7b,0xb0,0xfa,0x43,0x48,0x6f,0x18,0x38,0xd2,0x76,0xa2,0xfd,0x0d,0xd2,0x45,0xd7,0xb7,0xe3,0x36,
0x32,0x6c,0x4b,0xcd,0x9e,0x4f,0x9b,0x0c,0x44,0xb3,0xd4,0xe8,0xa2,0x6b,0x95,0x09,0x9a,0x29,0xce,0x6f,0x9c,0x34,0xfd,0x59,0x3d,0xd1,0x54,0xed,0x26,0xe9,0xd5,0x00,
0x9b,0x94,0x11,0x6d,0x97,0x97,0xa1,0xe3,0xf4,0x78,0x8e,0xbd,0x78,0x62,0xec,0x99,0x4d,0xcd,0xac,0x4f,0x8d,0x50,0x40,0xf1,0x25,0xad,0x72,0x74,0x2c,0xd0,0xaa,0x92,
0x30,0xf5,0xc5,0xb8,0xf4,0x0c,0x78,0x43,0x17,0x51,0x18,0xaf,0xeb,0x77,0xce,0x8a,0x67,0x00,0xc2,0xd1,0x49,0x05,0x3e,0x3d,0xed,0x84,0xe9,0x71,0x8a,0x02,0xc2,0xe7,
0xe3,0x7d,0x05,0x9d,0x33,0x35,0xab,0x25,0x5e,0x63,0x4c,0x93,0xf4,0x9a,0x80,0x32,0x93,0x08,0x6f,0x93,0x55,0x4b,0xdb,0x99,0xf1,0xb0,0xaa,0xf0,0xf5,0xc4,0x77,0x5f,
0xfb,0x22,0x29,0x8a,0xe7,0x0b,0x8a,0xf3,0x54,0xe8,0xa4,0x6d,0x92,0x2a,0x6d,0x14,0x2f,0x4f,0xdb,0xb0,0xf4,0x04,0x50,0xae,0x9a,0xe8,0xb6,0xb8,0x17,0xdf,0x7a,0x62,
0xdc,0x4b,0xee,0x7e,0x3c,0xf1,0x7d,0x27,0xed,0x84,0xe9,0x61,0xd6,0xc7,0xcb,0xd9,0xdd,0xdb,0x15,0x75,0xd3,0x3a,0x41,0x87,0x36,0x8a,0x9f,0x4f,0xdb,0xb0,0xf4,0x04,
0xf0,0xba,0x37,0x88,0xee,0xb3,0x5d,0x1b,0x5b,0x87,0x08,0x85,0x74,0xcc,0x4a,0x7c,0xff,0x37,0xed,0x84,0xe9,0x61,0xee,0x8b,0x6b,0x80,0xf6,0x1f,0xa9,0xd0,0xcd,0xb6,
0x45,0x86,0xc9,0x13,0xde,0x99,0xb6,0x61,0xa9,0x09,0x20,0x5a,0xe3,0x69,0xa2,0x85,0x06,0x6a,0xa7,0xe5,0x84,0x78,0x96,0xbf,0xed,0xd5,0xc4,0xfb,0x6f,0xd3,0x4e,0x98,
0x1e,0x7e,0xdb,0x0b,0x64,0x65,0xd9,0xfe,0xb8,0x81,0x06,0xd0,0x12,0x1a,0xa5,0x86,0x70,0x4d,0xda,0x96,0xa5,0x26,0x80,0xe0,0x48,0x43,0x88,0x2e,0x50,0xad,0x9a,0x2d,
0x73,0xe3,0xa6,0x60,0xf3,0x88,0xd8,0x37,0xfc,0x4e,0xda,0x09,0xd3,0xb3,0x44,0xbf,0x8b,0x7b,0x35,0x2d,0x37,0xc7,0xa3,0x9e,0xcd,0xa7,0xd8,0xa2,0x92,0xb0,0x39,0x6d,
0xcb,0xd2,0x6b,0x02,0x0e,0x31,0x1d,0xcf,0xa2,0x96,0x6d,0xa3,0x65,0x94,0xd3,0xf4,0x76,0x3c,0x21,0x54,0x1c,0x9a,0x76,0xc2,0xf4,0x18,0xa1,0x88,0xb0,0x49,0x93,0xed,
0x6c,0x1a,0xea,0x05,0x6b,0x68,0xb9,0x2e,0xfa,0xb9,0xf9,0x84,0xe7,0xa5,0x6d,0x5e,0x7a,0x35,0x40,0x3e,0x1a,0x83,0xd7,0x8c,0xb4,0x94,0x96,0x7b,0x95,0xdb,0xcc,0xf2,
0x67,0x10,0xd2,0xbe,0x3c,0xb9,0x2d,0xb5,0x09,0x92,0x1e,0xe2,0xdd,0x78,0x58,0xdb,0xf1,0x86,0xc1,0x22,0x5e,0x7b,0xda,0x01,0xda,0x68,0xfe,0x4b,0x23,0xbc,0x40,0x94,
0xfa,0xc7,0xb7,0xa9,0x09,0xe0,0xc9,0x27,0x67,0x0c,0x40,0x41,0x41,0x48,0xc7,0x43,0x08,0x59,0x79,0x6b,0xdc,0x59,0xda,0x56,0xea,0x0b,0xcc,0x4e,0x3b,0x81,0xf6,0x90,
0x99,0xca,0x85,0xb4,0xac,0x30,0x4a,0xc8,0x9b,0xd7,0x39,0x4a,0x44,0xfe,0x4f,0x83,0xc3,0xad,0xc7,0xa3,0x69,0x1b,0x98,0xfe,0x8e,0xa0,0x4e,0x59,0xba,0xc6,0xc6,0x33,
0x81,0xaf,0x3d,0x19,0xaf,0x9a,0xad,0xbd,0x22,0xf6,0x0c,0x3b,0x93,0xbb,0x7a,0xff,0x06,0xfb,0x0f,0xd2,0x1d,0x97,0xfc,0xe2,0x64,0x15,0x22,0xd6,0xbc,0xe5,0x40,0x21,
0x2b,0xa6,0xf8,0x73,0x79,0x3a,0xdf,0xc9,0x9c,0x55,0x5c,0x84,0x0d,0x69,0x1b,0x9a,0xbe,0x00,0x06,0xaa,0xa5,0xb8,0x49,0xb9,0x0c,0xab,0xc7,0xca,0x09,0x79,0xe5,0xbb,
0x32,0x22,0x3a,0x7f,0x95,0xdc,0xf5,0x6e,0xda,0x66,0x7e,0x42,0xe6,0xc7,0xc3,0xda,0xfc,0x4c,0x6d,0x8a,0x2c,0x2d,0xb3,0x56,0xc0,0x9a,0xdf,0x78,0xc1,0x58,0x8a,0xaf,
0x3e,0xf1,0xd3,0x73,0x5a,0xf5,0x02,0x61,0xa7,0x2f,0x80,0xa2,0x95,0x44,0xeb,0xd5,0x2a,0xb0,0xe9,0x0a,0x95,0xca,0x99,0x3f,0x58,0x20,0xc7,0xfa,0x1b,0xe3,0x9b,0xa2,
0x92,0x10,0x52,0x4f,0xb0,0x8f,0x47,0x98,0x13,0x09,0x59,0x7f,0x93,0xed,0xda,0x59,0x70,0xb3,0x25,0xd6,0xb1,0x71,0x56,0xf4,0x43,0xcb,0x88,0xd6,0xa5,0x6d,0x61,0x89,
0xd4,0x05,0x10,0x2c,0x35,0x0f,0x0b,0xe4,0x1c,0x44,0xbe,0xc6,0x26,0x23,0x79,0x61,0xa6,0xbc,0x4a,0x5e,0xbc,0x30,0x5e,0x44,0xe9,0x58,0x98,0xdc,0xbe,0x38,0x6d,0x7b,
0x77,0x41,0x18,0x57,0xfd,0xed,0x13,0xe2,0xfd,0x03,0x8b,0xef,0x56,0xa5,0x8c,0x45,0x97,0xc9,0xd8,0x4e,0x5b,0xc1,0x5a,0xff,0x81,0xc7,0xd2,0x36,0xb4,0x44,0xea,0x02,
0x78,0x72,0x41,0xfd,0x3c,0x14,0x34,0x38,0x92,0x62,0x95,0xad,0x6a,0x58,0xb9,0xc2,0x40,0x11,0x8f,0xbe,0x14,0x7f,0x82,0xf5,0xe6,0x5d,0x71,0xc2,0x86,0x67,0xa7,0x6d,
0xef,0x47,0x10,0x21,0x20,0xda,0x8a,0x0c,0x6b,0xde,0x8d,0x9b,0xb4,0x87,0xee,0x33,0x5c,0x27,0x2b,0x8e,0x53,0xed,0x34,0x8a,0xc7,0x35,0x8e,0xaf,0x6f,0x26,0xf9,0xce,
0xa8,0x17,0x90,0xba,0x00,0x4a,0x44,0x6f,0x05,0xc7,0x60,0x96,0xaf,0x68,0xa6,0xf5,0x7b,0x0e,0xb5,0x95,0xa7,0xff,0xc8,0x40,0x45,0x9e,0x58,0x18,0x0b,0x60,0x73,0x69,
0xad,0xe0,0x91,0xd2,0x63,0x69,0xdb,0x9d,0x70,0x6a,0x3c,0x7f,0xd1,0xf2,0x7c,0x3c,0x9f,0x31,0x67,0x8c,0x76,0xa3,0x68,0xbc,0xce,0x60,0xdb,0x68,0x3d,0x76,0xce,0xe2,
0xfa,0x13,0xf1,0x4c,0xda,0x86,0xee,0x4c,0xaf,0x11,0x40,0x63,0xf5,0xf4,0x77,0xd0,0x99,0x99,0x1a,0x5d,0x4b,0xb8,0xce,0x6a,0x63,0x59,0xf9,0x94,0xe5,0xaa,0xf8,0x69,
0xa5,0x48,0x37,0x0f,0x5d,0x24,0xab,0xc8,0xe6,0x96,0xf8,0xa9,0xa8,0xb4,0x8f,0xa6,0x73,0x37,0xa3,0xdd,0x5d,0x92,0x3f,0xa2,0x88,0x5e,0x8f,0x17,0xd4,0x9b,0x47,0xcb,
0x1a,0xc8,0xec,0x8b,0x6c,0x33,0x9a,0x5b,0x4e,0x8a,0xe6,0x38,0x81,0xf5,0xc7,0x18,0xeb,0xb2,0x0f,0x6c,0x0e,0xed,0x65,0xa4,0xbe,0x21,0x61,0x67,0x9e,0xf8,0xd2,0x8c,
0xe7,0x10,0x4e,0x3b,0xe0,0xde,0xeb,0x08,0xf7,0x37,0x37,0x1a,0xc3,0xaa,0x29,0xc1,0x9d,0xc6,0x70,0x73,0xb5,0xb5,0x3e,0x43,0xeb,0x48,0x1b,0x0d,0xe1,0x8c,0x79,0xf1,
0x2a,0xe2,0xe8,0x71,0xf1,0xfc,0x41,0x66,0x49,0x12,0xcc,0xf0,0x4f,0xc9,0xbc,0x65,0xb1,0x53,0xfc,0x7a,0xdc,0xcb,0xdf,0x70,0x96,0xa1,0xda,0x98,0x3d,0xca,0x68,0xb5,
0xdc,0x31,0xd1,0x73,0x86,0xf1,0xc6,0x7f,0x35,0x06,0xf5,0x01,0xf6,0x4b,0x3b,0x3d,0x77,0x45,0xaf,0xa9,0x01,0x76,0xa6,0x71,0xdd,0xf4,0xb9,0x68,0x73,0x59,0xe1,0x14,
0xc2,0xb9,0x4e,0xf3,0x14,0x4b,0xae,0x52,0xd0,0xcd,0x4d,0x5f,0xd4,0x65,0x00,0xff,0x76,0xbb,0x6e,0x59,0x9e,0x6e,0x88,0x1b,0x83,0xb7,0x8f,0x8f,0xdd,0xce,0x5b,0x93,
0x60,0x6e,0xd8,0xcd,0xe8,0x4b,0x4d,0xcb,0x55,0xf1,0x2e,0xe0,0x8e,0xeb,0xe3,0xa2,0xf2,0xf6,0xc4,0xd8,0x6f,0xde,0x03,0xf2,0x8a,0xdc,0x74,0xae,0x6a,0xab,0xf8,0xf1,
0xb1,0xce,0xb7,0x90,0xd7,0xf2,0xfe,0x3d,0x38,0x97,0xe2,0x11,0x69,0xa7,0xdf,0xc7,0xa5,0xd7,0xd5,0x00,0x3b,0xd3,0xd8,0x78,0xee,0xb9,0x88,0xe2,0x0d,0xd6,0x85,0xba,
0xba,0x71,0x0d,0xa7,0xb2,0xf2,0x33,0x86,0x1a,0xcb,0x2d,0xed,0xf2,0xd6,0xf2,0xcc,0xe3,0x86,0xca,0x33,0x6d,0x5e,0x3c,0xfc,0x3a,0xef,0x27,0xf1,0x70,0xf2,0x0f,0x2e,
0x90,0x91,0xd9,0x8d,0x2d,0x66,0xdd,0xc8,0x50,0xfc,0x95,0x72,0xe5,0xbc,0x3a,0x5a,0xb5,0x90,0x7b,0x1b,0xb4,0xc2,0x13,0x13,0x2c,0x31,0x80,0x65,0x4b,0xc2,0x99,0x26,
0xd1,0xfe,0x57,0x73,0x27,0xd6,0xd7,0x23,0xf5,0xc5,0x9d,0x4f,0x4a,0xaf,0xad,0x01,0x3e,0x8a,0x39,0x6f,0xd6,0x7f,0x0b,0xef,0x44,0x23,0x1c,0x46,0x57,0xc6,0x3a,0xab,
0x59,0xbc,0xc4,0xeb,0x56,0xf0,0xf0,0x4a,0x9b,0x6d,0x66,0xc5,0x86,0xb8,0x8a,0x2e,0xee,0xee,0x7f,0xf5,0x74,0x21,0xa0,0x30,0x46,0x41,0x35,0xcb,0x96,0x89,0x1c,0xca,
0xc3,0xaf,0x39,0xdc,0x78,0x96,0xfc,0xda,0xc9,0xc6,0xd2,0x3e,0x63,0x6e,0xfd,0xde,0x99,0xf1,0x25,0xf6,0x3a,0x01,0x94,0x68,0x7c,0xac,0xbe,0x1c,0x9b,0xa2,0x39,0xee,
0xa0,0xf0,0x94,0x4d,0xd6,0x93,0x3f,0x54,0xb7,0x4e,0x3a,0xff,0x2e,0xf9,0x43,0x89,0x8b,0x76,0x33,0xf8,0xa4,0x09,0x88,0x0e,0x51,0x90,0xa3,0xe3,0x12,0x9b,0x1c,0x40,
0xfe,0xbf,0x2d,0x0a,0xbe,0x49,0xf1,0xe1,0x39,0xf7,0xd4,0x4f,0xc2,0x9c,0xb4,0xd3,0x61,0x4f,0xd9,0x6b,0x05,0xd0,0x4f,0xcf,0xd0,0x2f,0x80,0x3e,0x4e,0xbf,0x00,0xfa,
0x38,0xbd,0x7e,0x14,0xb0,0x4b,0xb2,0xb2,0x28,0xc6,0xbd,0x7d,0xd7,0x20,0x20,0x4c,0x3a,0x7f,0xc5,0x64,0x0d,0xa1,0x98,0x7c,0x74,0x19,0x7c,0x2e,0x79,0xea,0xe8,0xc4,
0x2d,0x7d,0x8d,0x5c,0x93,0xb8,0xa5,0xce,0xdc,0xfd,0xc9,0x73,0x47,0xc7,0x7d,0x89,0xb0,0x23,0x76,0xa3,0xeb,0x84,0x0a,0xf8,0x5d,0xda,0xaf,0xdd,0x53,0xec,0xfd,0x02,
0xa8,0x55,0x8b,0x05,0x2a,0x0d,0xa0,0xfd,0x1c,0x59,0x65,0x2c,0x9b,0x19,0x7f,0x73,0x38,0x22,0xd9,0x8f,0x5f,0xb1,0x25,0xde,0x6f,0x90,0xbd,0x1e,0x11,0xd9,0xef,0x2b,
0x8a,0x08,0x8e,0x56,0xa6,0x9c,0xe0,0x05,0x19,0x21,0x51,0xa0,0x20,0xa2,0x70,0x77,0x1c,0x78,0xfe,0x9b,0x8a,0x8a,0xbc,0xfe,0x1d,0x9d,0xf2,0x74,0x9c,0x66,0x9b,0x8d,
0x18,0x96,0xf6,0x6b,0xf7,0x14,0x7b,0xbf,0x00,0xc6,0x19,0x87,0x7f,0x55,0x6b,0x24,0xcd,0x0f,0x0b,0xd4,0x70,0xf7,0x2f,0x74,0xc9,0xf1,0xe8,0xd5,0x06,0xe9,0xa6,0xec,
0x1e,0xdb,0x45,0xe4,0x8e,0x97,0xd5,0x45,0xee,0x4a,0x5b,0x85,0x64,0xaa,0xe2,0xdd,0xb9,0xc1,0x79,0xaa,0x74,0x10,0x5d,0xad,0x59,0x81,0xae,0x29,0xf1,0xb7,0x8a,0x9d,
0xdb,0x75,0xe9,0xa0,0xe9,0xab,0x5a,0x6c,0x64,0x53,0xde,0x72,0x4b,0x89,0xce,0x48,0x62,0x7f,0x64,0x4f,0x4c,0xef,0x0d,0xec,0xf5,0x02,0x08,0x86,0x19,0x86,0xe5,0xb2,
0x0e,0xa0,0xeb,0xf4,0xe8,0x45,0x63,0x78,0xb3,0x21,0x18,0xa5,0x9b,0xd5,0x47,0x39,0xc3,0x2a,0x82,0x6b,0xac,0x15,0xe1,0x97,0xaa,0xb5,0x10,0xdc,0x61,0x95,0x6e,0x7c,
0xc5,0x68,0x47,0xa1,0xc6,0x81,0x9a,0x30,0xce,0x5b,0x0a,0x44,0x8f,0xc5,0xab,0x91,0xd1,0x1d,0xf1,0x1a,0x44,0x34,0xc4,0x00,0x79,0x1c,0x1d,0x35,0x45,0x4d,0x58,0x66,
0x44,0x7c,0xf8,0xca,0xde,0xce,0x5e,0x2f,0x80,0x39,0x9b,0xeb,0x7f,0xe0,0xbd,0xa9,0xdb,0x57,0x55,0x81,0x89,0xf1,0x65,0xc4,0xdd,0xde,0xe7,0x6d,0xe7,0x45,0x99,0x46,
0x0f,0xba,0x76,0x57,0x71,0x7c,0x80,0x11,0x69,0xbf,0x71,0xcf,0xd2,0x3f,0x0a,0xe8,0xe3,0xf4,0x0b,0xa0,0x8f,0xd3,0x2f,0x80,0x3e,0x4e,0xbf,0x00,0xfa,0x38,0xfd,0x02,
0xe8,0xe3,0xf4,0x0b,0xa0,0x8f,0xd3,0x2f,0x80,0x3e,0x4e,0x6e,0x87,0x5b,0x3a,0x5b,0x36,0x39,0x62,0x74,0xc7,0x49,0x93,0xfd,0xec,0x1b,0x24,0xf9,0xba,0x23,0x9f,0x63,
0x72,0xef,0x9d,0x26,0x9d,0x1c,0x2a,0xbc,0xe3,0x6c,0xd9,0xfe,0xc3,0xa3,0xf7,0x2d,0x76,0x3e,0x3c,0x3a,0xce,0xf7,0xa6,0x5c,0xe9,0x18,0xf1,0xf8,0xae,0x99,0x1b,0x4a,
0x87,0x0a,0x27,0x8f,0xed,0x6b,0x7f,0xd6,0xd4,0xd7,0x29,0x15,0xf8,0xa6,0x52,0xbe,0xff,0x3f,0x95,0x7f,0xfe,0xf1,0x77,0x6e,0x65,0xbc,0x00,0x00,0x00,0x00,0x49,0x45,
0x4e,0x44,0xae,0x42,0x60,0x82,};
//#define DEBUGGING
#include "WebSocketServer.h"
#include "crypto.h"
bool WebSocketServer::handshake(Client &client) {
socket_client = &client;
// If there is a connected client->
if (socket_client->connected()) {
// Check request and look for websocket handshake
#ifdef DEBUGGING
Serial.println(F("Client connected"));
#endif
if (analyzeRequest(BUFFER_LENGTH)) {
#ifdef DEBUGGING
Serial.println(F("Websocket established"));
#endif
isConnected = true;
return true;
} else {
// Might just need to break until out of socket_client loop.
#ifdef DEBUGGING
Serial.println(F("Disconnecting client"));
#endif
terminateStream(0x87);
return false;
}
} else {
return false;
}
}
bool WebSocketServer::analyzeRequest(int bufferLength) {
// Use String library to do some sort of read() magic here.
String temp,tempLc;
int charpos;
int bite;
bool foundupgrade = false;
String newkey;
temp.reserve(128);
tempLc.reserve(128);
newkey.reserve(128);
#ifdef DEBUGGING
Serial.println(F("Analyzing request headers"));
#endif
for (int i = 0; i < 10 && !socket_client->available(); i++) {
delay(20);
}
// TODO: More robust string extraction
while ((bite = socket_client->read()) != -1) {
temp += (char)bite;
if ((char)bite == '\n') {
// Recieved CRNL without content, so header is done
#ifdef DEBUGGING
Serial.print("Got Line: " + temp);
#endif
// Preserve mixed case vor values
tempLc = temp;
// Remove whitespaces and convert to lowercase to comply
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
tempLc.toLowerCase();
while ((charpos = temp.indexOf(' ')) != -1) {
temp.remove(charpos,1);
}
if (!foundupgrade && tempLc.startsWith("upgrade:") && temp.indexOf("WebSocket") != -1) {
// OK, it's a websockets handshake for sure
foundupgrade = true;
} else if (!foundupgrade && tempLc.startsWith("upgrade:") && temp.indexOf("websocket") != -1) {
foundupgrade = true;
} else if (tempLc.startsWith("origin:")) {
origin = temp.substring(7,temp.length() - 2); // Don't save last CR+LF
} else
if (tempLc.startsWith("sec-websocket-key:")) {
newkey=temp.substring(18,temp.length() - 2); // Don't save last CR+LF
}
temp = "";
}
if (!socket_client->available()) {
delay(20);
}
}
if (!socket_client->connected()) {
return false;
}
temp += 0; // Terminate string
// Assert that we have all headers that are needed. If so, go ahead and
// send response headers.
if (foundupgrade == true) {
if (newkey.length() > 0) {
// add the magic string
newkey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
uint8_t *hash;
char result[21];
char b64Result[30];
SHA1Context sha;
int err;
uint8_t Message_Digest[20];
//Serial.println("Calculating");
err = SHA1Reset(&sha);
err = SHA1Input(&sha, reinterpret_cast<const uint8_t *>(newkey.c_str()), newkey.length());
err = SHA1Result(&sha, Message_Digest);
hash = Message_Digest;
for (int i=0; i<20; ++i) {
result[i] = (char)hash[i];
}
result[20] = '\0';
base64_encode(b64Result, result, 20);
//Serial.println("Sending");
char * response = (char*)malloc(200);
sprintf(response, "HTTP/1.1 101 Web Socket Protocol Handshake\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n", b64Result);
socket_client->print(response);
#ifdef DEBUGGING
Serial.print(response);
#endif
free(response);
return true;
} else {
// something went horribly wrong
return false;
}
} else {
// Nope, failed handshake. Disconnect
#ifdef DEBUGGING
Serial.println(F("Header mismatch"));
#endif
return false;
}
}
bool WebSocketServer::connected()
{
if (isConnected)
{
if (socket_client->connected())
return true;
isConnected = false;
return false;
}
return false;
}
int WebSocketServer::handleStream(char* buffer, int maxlen) {
uint8_t msgtype;
unsigned int length;
uint8_t mask[4];
unsigned int i;
if (!socket_client->connected() || !socket_client->available())
return 0;
msgtype = timedRead();
if (msgtype == 0x88) {
disconnectStream();
return -1;
}
if (!socket_client->connected()) {
return -1;
}
length = timedRead() & 127;
if (!socket_client->connected()) {
return -1;
}
if (length == 126) {
length = timedRead() << 8;
if (!socket_client->connected()) {
return -1;
}
length |= timedRead();
if (!socket_client->connected()) {
return -1;
}
} else if (length == 127) {
#ifdef DEBUGGING
Serial.println(F("No support for over 16 bit sized messages"));
#endif
terminateStream(0x89);
return -1;
}
// get the mask
mask[0] = timedRead();
if (!socket_client->connected())
return -1;
mask[1] = timedRead();
if (!socket_client->connected())
return -1;
mask[2] = timedRead();
if (!socket_client->connected())
return -1;
mask[3] = timedRead();
if (!socket_client->connected())
return -1;
for (i=0; i<length; ++i) {
char c = (char) (timedRead() ^ mask[i % 4]);
if (i < maxlen-1)
{
buffer[i] = c;
buffer[i+1] = 0;
}
if (!socket_client->connected()) {
return -1;
}
}
if (msgtype == 0x89) {
sendPong(buffer);
} else if (msgtype == 0x8A) {
#ifdef DEBUGGING
Serial.println(F("Received pong"));
#endif
return -1;
}
return min((int)length, maxlen);
}
void WebSocketServer::terminateStream(uint8_t cause) {
#ifdef DEBUGGING
Serial.println(F("Terminating socket"));
#endif
// Should send termination sequence (87,88,89) to server to tell it I'm quitting here.
socket_client->write((uint8_t) cause);
socket_client->write((uint8_t) 0x00);
socket_client->flush();
delay(10);
socket_client->stop();
}
void WebSocketServer::disconnectStream() {
#ifdef DEBUGGING
Serial.println(F("Disconnecting socket"));
#endif
// Should send 0x8800 to server to tell it I'm quitting here.
socket_client->write((uint8_t) 0x88);
socket_client->write((uint8_t) 0x00);
socket_client->flush();
delay(10);
socket_client->stop();
}
char* WebSocketServer::getData() {
static char data[128];
if (handleStream(data, sizeof(data)-1) <= 0)
return NULL;
return data;
}
void WebSocketServer::sendData(char *str) {
#ifdef DEBUGGING
Serial.print(F("Sending data: "));
Serial.println(str);
#endif
if (socket_client->connected()) {
sendEncodedData(str, 0x81);
}
}
int WebSocketServer::timedRead() {
while (!socket_client->available()) {
delay(20);
}
return socket_client->read();
}
void WebSocketServer::sendEncodedData(char *str, uint8_t opcode) {
int size = strlen(str);
// string type
socket_client->write(opcode);
// NOTE: no support for > 16-bit sized messages
if (size > 125) {
socket_client->write(126);
socket_client->write((uint8_t) (size >> 8));
socket_client->write((uint8_t) (size & 0xFF));
} else {
socket_client->write((uint8_t) size);
}
socket_client->print(str);
}
void WebSocketServer::sendPing(char *str) {
sendEncodedData(str, 0x89);
}
void WebSocketServer::sendPong(char *str) {
sendEncodedData(str, 0x8A);
}
/*
Websocket-Arduino, a websocket implementation for Arduino
Copyright 2011 Per Ejeklint
Based on previous implementations by
Copyright 2010 Ben Swanson
and
Copyright 2010 Randall Brewer
and
Copyright 2010 Oliver Smith
Some code and concept based off of Webduino library
Copyright 2009 Ben Combee, Ran Talbott
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-------------
Now based off
http://www.whatwg.org/specs/web-socket-protocol/
- OLD -
Currently based off of "The Web Socket protocol" draft (v 75):
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
*/
#ifndef WEBSOCKETSERVER_H_
#define WEBSOCKETSERVER_H_
#include <Arduino.h>
#include <Stream.h>
#include "Server.h"
#include "Client.h"
// CRLF characters to terminate lines/handshakes in headers.
#define CRLF "\r\n"
// Amount of time (in ms) a user may be connected before getting disconnected
// for timing out (i.e. not sending any data to the server).
#define TIMEOUT_IN_MS 10000
#define BUFFER_LENGTH 32
// ACTION_SPACE is how many actions are allowed in a program. Defaults to
// 5 unless overwritten by user.
#ifndef CALLBACK_FUNCTIONS
#define CALLBACK_FUNCTIONS 1
#endif
// Don't allow the client to send big frames of data. This will flood the Arduinos
// memory and might even crash it.
#ifndef MAX_FRAME_LENGTH
#define MAX_FRAME_LENGTH 256
#endif
#define SIZE(array) (sizeof(array) / sizeof(*array))
class WebSocketServer {
public:
// Handle connection requests to validate and process/refuse
// connections.
bool handshake(Client &client);
// Get data off of the stream
char* getData();
// Write data to the stream
void sendData(char *str);
bool connected();
// Disconnect user gracefully.
void disconnectStream();
void sendPing(char *str);
private:
Client *socket_client;
unsigned long _startMillis;
const char *socket_urlPrefix;
bool isConnected{false};
String origin;
String host;
// Discovers if the client's header is requesting an upgrade to a
// websocket connection.
bool analyzeRequest(int bufferLength);
int handleStream(char* buffer, int maxlen);
int timedRead();
void sendEncodedData(char *str, uint8_t);
// Disconnect user gracefully.
void terminateStream(uint8_t);
void sendPong(char *str);
};
#endif
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include "WebSocketServer.h"
#include "resources.h"
class CWifi
{
WiFiServer webServer{80};
WiFiServer socketServer{88};
WiFiClient socketClient;
WebSocketServer webSocket;
DNSServer dnsServer;
bool bWsConnected = false;
const char *ssid = "cnc ap";
const byte DNS_PORT = 53;
public:
void connect()
{
webServer.stop();
socketServer.stop();
socketClient.stop();
Serial.print("Starting Wifi\n");
WiFi.softAP(ssid);
Serial.print("getting IP\n");
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
dnsServer.start(DNS_PORT, "CNC", myIP);
webServer.begin();
socketServer.begin();
Serial.println("HTTP server started");
}
void operator()()
{
dnsServer.processNextRequest();
doWebsockets();
doWebserver();
}
char* wsGetData()
{
if (!bWsConnected)
return NULL;
return webSocket.getData();
}
void wsSend(char* msg)
{
if (!bWsConnected)
return;
webSocket.sendData(msg);
}
private:
void doWebserver()
{
WiFiClient client = webServer.available();
if (!client)
return;
long l0 = millis();
Serial.print("web: new client: ");
Serial.print(client.status());
char strLine[64];
int nPos = 0;
while (millis() - l0 < 500)
{
if (!client.connected())
{
Serial.print("client disconnected\n");
break;
}
if (client.available())
{
char c = client.read();
if (c==0x0d)
{
strLine[nPos] = 0;
Serial.print("'");
Serial.print(strLine);
Serial.print("'\n");
client.flush();
processRequest(client, strLine);
//delay(100);
client.flush();
client.stop();
return;
}
strLine[nPos] = c;
if (nPos++ >= 64)
break;
}
}
Serial.print("Invalid request\n"); // TODO: WiFiClient members
client.stop();
}
bool processRequest(WiFiClient& client, char* req)
{
if (strcmp(req, "GET / HTTP/1.1") == 0)
{
client.print("HTTP/1.1 200 OK\r\n");
client.print("Content-Type: text/html\r\n");
char msg[64];
sprintf(msg, "Content-Length: %d\r\n", sizeof(_index_html));
client.print("\r\n");
client.write(_index_html, sizeof(_index_html));
return true;
}
if (strcmp(req, "GET /favicon.png HTTP/1.1") == 0)
{
client.print("HTTP/1.1 200 OK\r\n");
client.print("Content-Type: image/png\r\n");
char msg[64];
sprintf(msg, "Content-Length: %d\r\n", sizeof(_favicon_png));
client.print(msg);
client.print("\r\n");
client.write(_favicon_png, sizeof(_favicon_png));
return true;
}
client.print("HTTP/1.1 404 Not Found");
return false;
}
void doWebsockets()
{
WiFiClient newSocketClient = socketServer.available();
/*
if (bWsConnected)
{
char* data = webSocket.getData();
if (data)
{
wsOnData(data);
}
}*/
if (bWsConnected && !webSocket.connected())
{
Serial.print("Info: ws disconnected\n");
bWsConnected = false;
}
if (newSocketClient)
{
if (!webSocket.connected())
{
Serial.print("Info: ws connected\n");
socketClient = newSocketClient;
webSocket.handshake(socketClient);
bWsConnected = true;
} else
{
Serial.print("Info: ws refusing\n");
WebSocketServer dummyWebSocket;
if (dummyWebSocket.handshake(newSocketClient))
{
dummyWebSocket.sendData("error('Not available');\n");
dummyWebSocket.disconnectStream();
} else
{
newSocketClient.stop();
}
}
}
}
};
@gabonator
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment