6 Commits

Author SHA1 Message Date
499f8447ba foo 2018-12-01 15:49:07 +01:00
7cc4451c1c some nice intro text 2018-11-29 19:17:26 +01:00
Patrick Balsiger
f558a09974 remove redundant section 2018-10-20 16:04:41 +00:00
Patrick Balsiger
fd205348f8 add default password to guide 2018-10-20 16:04:12 +00:00
Patrick Balsiger
9f417340c5 fix typos 2018-10-20 15:59:57 +00:00
Patrick Balsiger
acae6c1b09 simplify enduser setup 2018-10-20 15:54:10 +00:00
43 changed files with 13468 additions and 531 deletions

7
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.pio
.vscode
.pioenvs
.piolibdeps
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
data/config.json

View File

@@ -20,9 +20,10 @@ firmware:
before_script:
- pip install -U platformio
script:
- mv data/example.config.json data/config.json
- pio run -t clean
- pio run
- pio run -t buildfs
- pio run -e release
- pio run -e release -t buildfs
release:
stage: release

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>illucat</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

View File

@@ -1,7 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

View File

@@ -1,6 +1,6 @@
{
"terminal.integrated.env.linux": {
"PATH": "/home/master/.platformio/penv/bin:/home/master/.platformio/penv:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl",
"PATH": "/home/master/.platformio/penv/bin:/home/master/.platformio/penv:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl",
"PLATFORMIO_CALLER": "vscode"
},
"files.associations": {

View File

@@ -1,3 +0,0 @@
FROM python:3.6-stretch as builder
RUN pip install -U platformio

View File

@@ -1,4 +0,0 @@
build:
docker build -t wirelos/platformio .
run-workspace:
docker run -it --rm -v $PWD:/project wirelos/platformio bash

View File

@@ -7,11 +7,6 @@ From the software point of view, the IlluCat is the exemplary model of the [Spro
IlluCat can embed several plugins, like web interface, MQTT, IRC or even mesh networking.
As the framework supports easy integration of many available Arduino libraries, IlluCat can be easily customized to your needs. This repository contains some example implementations that you can use to build your own custom cat.
## OTA
pio run -e build && \
curl -v -F file=@.pioenvs/build/firmware.bin http://illucat.lan/update && \
curl -X POST http://illucat.lan/restart
## Resources & Documentation
[3D Model](https://www.thingiverse.com/thing:2974862)
[Installation](https://gitlab.com/0x1d/illucat/blob/master/installation.md)

2
api.md
View File

@@ -27,7 +27,7 @@ All functionality can be used by sending messages to these topics.
| pixels/brightness | Integer | Integer from 0 to 255 to set the overall brightness of the strip by bitshifting the current colors in memory. Use with caution as running the LEDs on full brightness requires a lot of power. |
## WebSocket
Endpoint: /ws
Endpoint: /pixel
Send a JSON String containing the mandatory fields.
Example:

View File

@@ -1,9 +0,0 @@
{
"stationMode": 1,
"hostname": "bar-light",
"apSSID": "bar-light",
"apPassword": "th3r31sn0sp00n",
"connectTimeout": 5000,
"stationSSID": "UPC7823296",
"stationPassword": "rthYx4Vnfeza"
}u

13
data/example.config.json Normal file
View File

@@ -0,0 +1,13 @@
{
"stationMode": 0,
"hostname": "illucat",
"apSSID": "illucat",
"apPassword": "illumination",
"connectTimeout": 20000,
"stationSSID": "MyWifi",
"stationPassword": "myWifiPassword",
"meshSSID": "illucat",
"meshPassword": "illumination",
"meshPort": 5555,
"channel": 5
}

View File

@@ -1,6 +0,0 @@
{
"ircServer": "chat.freenode.net",
"ircPort": 6665,
"ircNickname": "illucat",
"ircUser": "illucat"
}

View File

@@ -1,6 +0,0 @@
{
"mqttClientName" : "illucat1",
"mqttBrokerHost" : "192.168.1.2",
"mqttBrokerPort" : 1883,
"mqttRootTopic" : "wirelos/illucat"
}

View File

@@ -1,7 +1,7 @@
{
"pin": 4,
"length": 8,
"brightness": 50,
"brightness": 64,
"updateInterval": 100,
"defaultColor": 100
}

BIN
data/www/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -2,10 +2,10 @@
<html>
<head>
<title>IlluCat</title>
<title>ESP Kit</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="icon" type="image/png" href="/favicon-32x32.png">
<link rel="stylesheet" type="text/css" href="styles.css">
<script src="script.js"></script>
</head>
@@ -21,8 +21,8 @@
data-name="hue"
data-topic="pixels/hue"
data-default="0"
data-external="/gradients.json">
</li>
data-external="/gradients.json"
></li>
<li class="form-row ParamColor"
data-name="color"
data-topic="pixels/color"
@@ -39,7 +39,7 @@
data-name="pattern"
data-topic="pixels/pattern"
data-default="0"
data-entries='[{"text": "None", "value": "0"}, {"text": "Rainbow", "value": "1"}, {"text": "TheaterChase", "value": "2"}, {"text": "Color Wipe", "value": "3"}, {"text": "Scanner", "value": "4"}, {"text": "Fade", "value": "5"}, {"text": "Fire", "value": "6"}]'
data-entries='[{"text": "None", "value": "0"}, {"text": "Rainbow", "value": "1"}, {"text": "TheaterChase", "value": "2"}, {"text": "Color Wipe", "value": "3"}, {"text": "Scanner", "value": "4"}, {"text": "Fade", "value": "5"}]'
></li>
<li class="form-row ParamSlider"
data-name="brightness"
@@ -60,33 +60,19 @@
</ul>
</div>
</form>
<!-- <div class="settings container collapsible open">
<span class="heading">IlluChat</span>
<div class="content">
<div class="Chat" data-name="Ruedi" data-label="foo" data-placeholder="msg" data-topic="out/chat/log"></div>
</div>
</div> -->
<div class="settings container collapsible">
<span class="heading">Settings</span>
<div class="content">
<h2>Network</h2>
<div class="Form" data-fileName="/config/wifi.json" data-name="configForm" data-from="/config/wifi.json" data-endpoint="/config"></div>
<div class="Form" data-fileName="/config.json" data-name="configForm" data-from="/config.json" data-endpoint="/config"></div>
<h2>NeoPixel</h2>
<div class="Form" data-fileName="/pixelConfig.json" data-name="configForm" data-from="/pixelConfig.json" data-endpoint="/config"></div>
<!-- <h2>MQTT</h2>
<div class="Form" data-fileName="/mqttConfig.json" data-name="configForm" data-from="/mqttConfig.json" data-endpoint="/config"></div>
<h2>IRC</h2>
<div class="Form" data-fileName="/ircConfig.json" data-name="configForm" data-from="/ircConfig.json" data-endpoint="/config"></div> -->
<div class="Form" data-fileName="/pixelConfig.json" data-name="configForm" data-from="/pixelConfig.json" data-endpoint="/config"></div>
</div>
</div>
<div class="settings container collapsible">
<span class="heading">System</span>
<div class="content">
<div><label>Free Heap: </label><span class="js-heap"></span><span>&nbsp;bytes</span><br><br></div>
<form method='POST' action='/update' enctype='multipart/form-data'>
<input type='file' name='update'><input type='submit' value='Update'>
</form>
<br>
<button class="js-restart">Restart</button>
</div>
</div>

12245
data/www/script.js Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -306,24 +306,3 @@ form .form-row input[type="checkbox"] {
.sui select option {
background: #333333;
}
.Chat .message-container {
max-height: 200px;
overflow: auto;
}
.Chat .message-container .messages {
list-style-type: none;
}
.Chat .message-container .messages .user-label {
color: lightblue;
}
.Chat .message-container .messages .user-label:before {
color: #097479;
content: '<';
}
.Chat .message-container .messages .user-label:after {
color: #097479;
content: '>';
}
.Chat .message-container .messages .message-text {
font-weight: normal;
}

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -0,0 +1,384 @@
/**
* Original NeoPattern code by Bill Earl
* https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
*
* TODO
* - cleanup the mess
* - fnc table for patterns to replace switch case
*
* Custom modifications by 0x1d:
* - default OnComplete callback that sets pattern to reverse
* - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer
*/
#ifndef __NeoPattern_INCLUDED__
#define __NeoPattern_INCLUDED__
#include <Adafruit_NeoPixel.h>
using namespace std;
using namespace std::placeholders;
// Pattern types supported:
enum pattern
{
NONE = 0,
RAINBOW_CYCLE = 1,
THEATER_CHASE = 2,
COLOR_WIPE = 3,
SCANNER = 4,
FADE = 5
};
// Patern directions supported:
enum direction
{
FORWARD,
REVERSE
};
// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPattern : public Adafruit_NeoPixel
{
public:
// Member Variables:
pattern ActivePattern = RAINBOW_CYCLE; // which pattern is running
direction Direction = FORWARD; // direction to run the pattern
unsigned long Interval = 150; // milliseconds between updates
unsigned long lastUpdate = 0; // last update of position
uint32_t Color1 = 0;
uint32_t Color2 = 0; // What colors are in use
uint16_t TotalSteps = 32; // total number of steps in the pattern
uint16_t Index; // current step within the pattern
uint16_t completed = 0;
// FIXME return current NeoPatternState
void (*OnComplete)(int); // Callback on completion of pattern
uint8_t *frameBuffer;
int bufferSize = 0;
// Constructor - calls base-class constructor to initialize strip
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int))
: Adafruit_NeoPixel(pixels, pin, type)
{
frameBuffer = (uint8_t*)malloc(768);
OnComplete = callback;
TotalSteps = numPixels();
}
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type)
: Adafruit_NeoPixel(pixels, pin, type)
{
frameBuffer = (uint8_t*)malloc(768);
TotalSteps = numPixels();
}
void handleStream(uint8_t *data, size_t len)
{
//const uint16_t *data16 = (uint16_t *)data;
bufferSize = len;
memcpy(frameBuffer, data, len);
}
void drawFrameBuffer(int w, uint8_t *frame, int length){
for (int i = 0; i < length; i++){
uint8_t r = frame[i];
uint8_t g = frame[++i];
uint8_t b = frame[++i];
setPixelColor(i, r ,g, b);
}
}
void onCompleteDefault(int pixels)
{
//Serial.println("onCompleteDefault");
// FIXME no specific code
if (ActivePattern == THEATER_CHASE)
{
return;
}
Reverse();
//Serial.println("pattern completed");
}
// Update the pattern
void Update()
{
switch (ActivePattern)
{
case RAINBOW_CYCLE:
RainbowCycleUpdate();
break;
case THEATER_CHASE:
TheaterChaseUpdate();
break;
case COLOR_WIPE:
ColorWipeUpdate();
break;
case SCANNER:
ScannerUpdate();
break;
case FADE:
FadeUpdate();
break;
default:
if(bufferSize > 0){
drawFrameBuffer(TotalSteps, frameBuffer, bufferSize);
}
break;
}
}
void UpdateScheduled()
{
if ((millis() - lastUpdate) > Interval) // time to update
{
lastUpdate = millis();
Update();
}
}
// Increment the Index and reset at the end
void Increment()
{
completed = 0;
if (Direction == FORWARD)
{
Index++;
if (Index >= TotalSteps)
{
Index = 0;
completed = 1;
if (OnComplete != NULL)
{
OnComplete(numPixels()); // call the comlpetion callback
}
else
{
onCompleteDefault(numPixels());
}
}
}
else // Direction == REVERSE
{
--Index;
if (Index <= 0)
{
Index = TotalSteps - 1;
completed = 1;
if (OnComplete != NULL)
{
OnComplete(numPixels()); // call the comlpetion callback
}
else
{
onCompleteDefault(numPixels());
}
}
}
}
// Reverse pattern direction
void Reverse()
{
if (Direction == FORWARD)
{
Direction = REVERSE;
Index = TotalSteps - 1;
}
else
{
Direction = FORWARD;
Index = 0;
}
}
// Initialize for a RainbowCycle
void RainbowCycle(uint8_t interval, direction dir = FORWARD)
{
ActivePattern = RAINBOW_CYCLE;
Interval = interval;
TotalSteps = 255;
Index = 0;
Direction = dir;
}
// Update the Rainbow Cycle Pattern
void RainbowCycleUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
}
show();
Increment();
}
// Initialize for a Theater Chase
void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD)
{
ActivePattern = THEATER_CHASE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Theater Chase Pattern
void TheaterChaseUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if ((i + Index) % 3 == 0)
{
setPixelColor(i, Color1);
}
else
{
setPixelColor(i, Color2);
}
}
show();
Increment();
}
// Initialize for a ColorWipe
void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = COLOR_WIPE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color;
Index = 0;
Direction = dir;
}
// Update the Color Wipe Pattern
void ColorWipeUpdate()
{
setPixelColor(Index, Color1);
show();
Increment();
}
// Initialize for a SCANNNER
void Scanner(uint32_t color1, uint8_t interval)
{
ActivePattern = SCANNER;
Interval = interval;
TotalSteps = (numPixels() - 1) * 2;
Color1 = color1;
Index = 0;
}
// Update the Scanner Pattern
void ScannerUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if (i == Index) // Scan Pixel to the right
{
setPixelColor(i, Color1);
}
else if (i == TotalSteps - Index) // Scan Pixel to the left
{
setPixelColor(i, Color1);
}
else // Fading tail
{
setPixelColor(i, DimColor(getPixelColor(i)));
}
}
show();
Increment();
}
// Initialize for a Fade
void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = FADE;
Interval = interval;
TotalSteps = steps;
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Fade Pattern
void FadeUpdate()
{
// Calculate linear interpolation between Color1 and Color2
// Optimise order of operations to minimize truncation error
uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
ColorSet(Color(red, green, blue));
show();
Increment();
}
// Calculate 50% dimmed version of a color (used by ScannerUpdate)
uint32_t DimColor(uint32_t color)
{
// Shift R, G and B components one bit to the right
uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
return dimColor;
}
// Set all pixels to a color (synchronously)
void ColorSet(uint32_t color)
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, color);
}
show();
}
// Returns the Red component of a 32-bit color
uint8_t Red(uint32_t color)
{
return (color >> 16) & 0xFF;
}
// Returns the Green component of a 32-bit color
uint8_t Green(uint32_t color)
{
return (color >> 8) & 0xFF;
}
// Returns the Blue component of a 32-bit color
uint8_t Blue(uint32_t color)
{
return color & 0xFF;
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos)
{
//if(WheelPos == 0) return Color(0,0,0);
WheelPos = 255 - WheelPos;
if (WheelPos < 85)
{
return Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else if (WheelPos < 170)
{
WheelPos -= 85;
return Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
else
{
WheelPos -= 170;
return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
};
#endif

View File

@@ -0,0 +1,96 @@
#ifndef __NEOPATTERN_STATE__
#define __NEOPATTERN_STATE__
#include <ArduinoJson.h>
#include "NeoPattern_api_json.h"
#include "NeoPattern_api_modes.cpp"
#include "utils_print.h"
#include "JsonStruct.h"
// TODO move ARRAY_LENGTH to core lib
#define ARRAY_LENGTH(array) sizeof(array)/sizeof(array[0])
struct NeoPixelConfig : public JsonStruct {
int pin;
int length;
int brightness;
int updateInterval;
int defaultColor;
void mapJsonObject(JsonObject& root) {
root["pin"] = pin;
root["length"] = length;
root["brightness"] = brightness;
root["updateInterval"] = updateInterval;
root["defaultColor"] = defaultColor;
}
void fromJsonObject(JsonObject& json) {
pin = getIntAttrFromJson(json, "pin");
length = getIntAttrFromJson(json, "length");
brightness = getIntAttrFromJson(json, "brightness");
updateInterval = getIntAttrFromJson(json, "updateInterval");
defaultColor = getIntAttrFromJson(json, "defaultColor");
}
};
struct NeoPatternState : public JsonStruct {
uint pattern = 0;
uint color= 0;
uint color2= 0;
uint totalSteps = 16;
uint brightness = 64;
void mapJsonObject(JsonObject& root) {
root["pattern"] = pattern;
root["color"] = color;
root["color2"] = color2;
root["totalSteps"] = totalSteps;
root["brightness"] = brightness;
}
// Map a json object to this struct.
void fromJsonObject(JsonObject& json){
if(!verifyJsonObject(json)){
PRINT_MSG(Serial, "fromJsonObject", "cannot parse JSON");
valid = 0;
return;
}
color = getIntAttrFromJson(json, "color", color);
color2 = getIntAttrFromJson(json, "color2", color2);
pattern = getIntAttrFromJson(json, "pattern", pattern);
brightness = getIntAttrFromJson(json, "brightness", brightness);
totalSteps = getIntAttrFromJson(json, "totalSteps", totalSteps);
valid = 1;
};
};
struct NeoPatternDto : public JsonStruct {
uint mode;
uint value;
const char* valueStr;
// ------------------------------------------------------------------------------------------
//Check if given object is valid and contains fields: JSON_MODE_NODE, JSON_VALUE
int verifyJsonObject(JsonObject& json){
return json.success()
&& json.containsKey(JSON_MODE_NODE)
&& json.containsKey(JSON_VALUE);
};
void mapJsonObject(JsonObject& root) {
root[JSON_MODE_NODE] = mode;
root[JSON_VALUE] = value;
}
// Map a json object to this struct.
void fromJsonObject(JsonObject& json){
if(!verifyJsonObject(json)){
PRINT_MSG(Serial, "fromJsonObject", "cannot parse JSON");
valid = 0;
return;
}
mode = atoi(json[JSON_MODE_NODE]);
mode = mode < ARRAY_LENGTH(PIXEL_FNCS) ? mode : 0;
value = json[JSON_VALUE];
valueStr = json[JSON_VALUE];
valid = 1;
};
};
#endif

View File

@@ -0,0 +1,16 @@
#ifndef __PIXEL_JSON_API__
#define __PIXEL_JSON_API__
/*
modes: PIXELS_OFF = 0, COLOR_WHEEL_MODE = 1, COLOR_MODE = 2, PATTERN_MODE = 3
patterns: NONE = 0, RAINBOW_CYCLE = 1, THEATER_CHASE = 2, COLOR_WIPE = 3, SCANNER = 4, FADE = 5
{
"mode": int,
"value": int || String
}
*/
#define JSON_MODE_NODE "mode"
#define JSON_VALUE "value"
#define JSON_ACTION_NODE "action"
#endif

View File

@@ -0,0 +1,64 @@
#ifndef __NEOPATTERN_API_MODES__
#define __NEOPATTERN_API_MODES__
#include "NeoPattern.cpp"
enum PIXEL_MODE { PIXELS_OFF = 0, COLOR_WHEEL_MODE = 1, COLOR_MODE = 2, PATTERN_MODE = 3};
typedef void (*PIXEL_FP)(NeoPattern*, const char *);
/*
Array of function pointers to be used as lookup table using the int values of PIXEL_MODE.
TODO header file + separate functions instead of lambdas
*/
const PIXEL_FP PIXEL_FNCS[] = {
/*
PIXESL_OFF
Sets all pixels to black.
*/
[](NeoPattern* pixels, const char *color){
pixels->clear();
pixels->ColorSet(0);
},
/*
COLOR_WHEEL_MODE
Input: integer color from 0 to 155
Uses the color wheel to set a color.
If given integer is <= 1, set the color to black.
By using this function, Color1 and Color2 is set on the pixels;
Color1 = new color, Color2 = last color.
*/
[](NeoPattern* pixels, const char *color){
int c1 = atoi(color);
int c2 = pixels->Color1;
pixels->Color1 = c1;
pixels->Color2 = c2;
pixels->ActivePattern = NONE;
if(c1 <= 1) {
pixels->ColorSet(0);
return;
}
pixels->ColorSet(pixels->Wheel(c1));
},
/*
COLOR_MODE
sets the color from an rgb int
*/
[](NeoPattern* pixels, const char *color){
pixels->ActivePattern = NONE;
pixels->ColorSet(atoi(color));
},
/*
PATTERN_MODE
Input: id of the pattern
Sets the active pattern on the strip.
As every pattern has another API, all fields need to be set before, for example by using COLOR_WHEEL_MODE.
*/
[](NeoPattern* pixels, const char *id){
pattern p = (pattern)atoi(id);
//pixels->Interval = 50;
//pixels->TotalSteps = pixels->numPixels();
pixels->ActivePattern = p;
}
};
#endif

View File

@@ -0,0 +1,25 @@
#include "utils_print.h"
int FORMAT_BUFFER_SIZE(const char* format, ...) {
va_list args;
va_start(args, format);
int result = vsnprintf(NULL, 0, format, args);
va_end(args);
return result + 1; // safe byte for \0
}
void PRINT_MSG(Print &out, const char* prefix, const char* format, ...) {
if(SPROCKET_PRINT){
out.print(String(prefix) + String(": "));
char formatString[128], *ptr;
strncpy_P( formatString, format, sizeof(formatString) ); // copy in from program mem
// null terminate - leave last char since we might need it in worst case for result's \0
formatString[ sizeof(formatString)-2 ]='\0';
ptr=&formatString[ strlen(formatString)+1 ]; // our result buffer...
va_list args;
va_start (args,format);
vsnprintf(ptr, sizeof(formatString)-1-strlen(formatString), formatString, args );
va_end (args);
formatString[ sizeof(formatString)-1 ]='\0';
out.println(ptr);
}
}

View File

@@ -0,0 +1,15 @@
#ifndef __SPROCKET_UTILS__
#define __SPROCKET_UTILS__
#include <Arduino.h>
#ifndef SPROCKET_PRINT
#define SPROCKET_PRINT 1
#endif
// TODO move to sprocket
int FORMAT_BUFFER_SIZE(const char* format, ...);
void PRINT_MSG(Print &out, const char* prefix, const char* format, ...);
#endif

View File

@@ -0,0 +1,126 @@
#ifndef __WSUTILS_H___
#define __WSUTILS_H___
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <AsyncWebSocket.h>
#include <ESPAsyncTCP.h>
class WsUtils {
public:
static String parseFrame(AwsEventType type, void * arg, uint8_t *data, size_t len) {
String msg = "";
if(type == WS_EVT_DATA){
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->opcode == WS_TEXT){
for(size_t i=0; i < info->len; i++) {
msg += (char) data[i];
}
} else {
char buff[3];
for(size_t i=0; i < info->len; i++) {
sprintf(buff, "%02x ", (uint8_t) data[i]);
msg += buff ;
}
}
}
return msg;
}
static String parseFrameAsString(AwsEventType type, void * arg, uint8_t *data, size_t len, int start = 0) {
String msg = "";
if(type == WS_EVT_DATA){
AwsFrameInfo * info = (AwsFrameInfo*)arg;
//if(info->final && info->index == 0 && info->len == len){
if(info->opcode == WS_TEXT){
for(size_t i=start; i < info->len; i++) {
msg += (char) data[i];
}
} else {
char buff[3];
for(size_t i=start; i < info->len; i++) {
sprintf(buff, "%02x ", (uint8_t) data[i]);
msg += buff ;
}
}
//}
}
return msg;
}
/* static void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if(type == WS_EVT_CONNECT){
Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
client->printf("Hello Client %u :)", client->id());
client->ping();
} else if(type == WS_EVT_DISCONNECT){
Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if(type == WS_EVT_ERROR){
Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
} else if(type == WS_EVT_PONG){
Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
} else if(type == WS_EVT_DATA){
AwsFrameInfo * info = (AwsFrameInfo*)arg;
String msg = "";
//the whole message is in a single frame and we got all of it's data
if(info->final && info->index == 0 && info->len == len){
Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
if(info->opcode == WS_TEXT){
for(size_t i=0; i < info->len; i++) {
msg += (char) data[i];
}
} else {
char buff[3];
for(size_t i=0; i < info->len; i++) {
sprintf(buff, "%02x ", (uint8_t) data[i]);
msg += buff ;
}
}
Serial.printf("%s\n",msg.c_str());
if(info->opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
}
//message is comprised of multiple frames or the frame is split into multiple packets
else {
if(info->index == 0){
if(info->num == 0)
Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
}
Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
if(info->opcode == WS_TEXT){
for(size_t i=0; i < info->len; i++) {
msg += (char) data[i];
}
} else {
char buff[3];
for(size_t i=0; i < info->len; i++) {
sprintf(buff, "%02x ", (uint8_t) data[i]);
msg += buff ;
}
}
Serial.printf("%s\n",msg.c_str());
if((info->index + len) == info->len){
Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
if(info->final){
Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
if(info->message_opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
}
}
}
}
} */
};
#endif

View File

@@ -13,38 +13,25 @@ env_default = build
[common]
framework = arduino
platform = espressif8266@2.0.4
platform = espressif8266
board = esp12e
upload_speed = 921600
monitor_baud = 115200
lib_deps =
Hash
ESPAsyncTCP
TaskScheduler
SPIFFS
ArduinoJson@5.13.4
Adafruit NeoPixel
ESPAsyncTCP
painlessMesh
ESP8266mDNS
ArduinoOTA
ArduinoJson
ESP Async WebServer
https://gitlab.com/wirelos/sprocket-lib.git#develop
https://gitlab.com/wirelos/sprocket-plugin-neopixel.git
ESPAsyncTCP
Adafruit NeoPixel
[env:build-esp32]
platform = espressif32
board = esp32dev
src_filter = +<*> -<var/> +<var/wifi/>
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
build_flags = -DSPROCKET_PRINT=1
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/sprocket-network-wifi.git#next
https://gitlab.com/wirelos/sprocket-plugin-web.git#next
[env:build]
src_filter = +<*> -<var/> +<var/wifi/>
src_filter = +<*> -<wifiMesh/> +<wifi/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
@@ -53,12 +40,11 @@ framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
-DSPROCKET_PRINT=1
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/sprocket-network-wifi.git#next
https://gitlab.com/wirelos/sprocket-plugin-web.git#next
https://gitlab.com/wirelos/sprocket-core.git#develop
[env:build-mesh]
src_filter = +<*> -<var/> +<var/wifiMesh/>
src_filter = +<*> +<wifiMesh/> -<wifi/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
@@ -67,11 +53,10 @@ framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
-DSPROCKET_PRINT=1
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/painlessMesh.git
https://gitlab.com/wirelos/sprocket-network-mesh.git#forked-mesh
https://gitlab.com/wirelos/sprocket-core.git#develop
[env:release]
src_filter = +<*> -<var/> +<var/wifi/>
src_filter = +<*> -<wifiMesh/> +<wifi/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
@@ -80,38 +65,17 @@ framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
-DSPROCKET_PRINT=0
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/sprocket-network-wifi.git#next
https://gitlab.com/wirelos/sprocket-plugin-web.git#next
https://gitlab.com/wirelos/sprocket-core.git#develop
[env:mqcatt]
src_filter = +<*> -<var/> +<var/mqcatt/>
[env:nodemcu]
src_filter = +<*> -<wifiMesh/> +<wifi/>
platform = ${common.platform}
board = ${common.board}
board = nodemcu
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
-DSPROCKET_PRINT=1
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/sprocket-network-wifi.git#next
https://gitlab.com/wirelos/sprocket-plugin-web.git#next
https://gitlab.com/wirelos/sprocket-plugin-mqtt.git#next
PubSubClient
[env:illuchat]
src_filter = +<*> -<var/> +<var/illuchat/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
-DSPROCKET_PRINT=1
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/sprocket-network-wifi.git#next
https://gitlab.com/wirelos/sprocket-plugin-web.git#next
https://gitlab.com/wirelos/sprocket-plugin-irc.git
https://gitlab.com/wirelos/sprocket-plugin-mqtt.git#next
PubSubClient
ArduinoIRC
https://gitlab.com/wirelos/sprocket-core.git#develop

View File

@@ -1,38 +1,150 @@
#ifndef __ILLUCAT__
#define __ILLUCAT__
#ifndef __MESH_APP__
#define __MESH_APP__
#include "config.h"
#include <TaskScheduler.h>
#include <MeshNet.h>
#include <Sprocket.h>
#include "WebServerConfig.h"
#include "WebServerPlugin.h"
#include "WebConfigPlugin.h"
#include "WebApiPlugin.h"
#include "config.h"
#include "NeoPattern.cpp"
#include "NeoPatternDto.h"
#include "NeoPattern_api_json.h"
#include "NeoPattern_api_modes.cpp"
#include "utils_print.h"
#include "utils_ws.h"
#include <plugins/WebSO.h>
#include <plugins/OtaTcpPlugin.cpp>
#include <plugins/WebServerPlugin.cpp>
#include <plugins/WebConfigPlugin.cpp>
#include "PixelPlugin.h"
using namespace std;
using namespace std::placeholders;
class IlluCat : public Sprocket
{
public:
AsyncWebServer *server;
SprocketConfig sprocketConfig;
WebServerConfig webConfig;
IlluCat(SprocketConfig cfg, WebServerConfig webCfg) : Sprocket(cfg)
{
sprocketConfig = cfg;
webConfig = webCfg;
server = new AsyncWebServer(webConfig.port);
server->serveStatic(PIXEL_CONFIG_FILE, SPIFFS, "pixelConfig.json");
addPlugin(new PixelPlugin());
addPlugin(new WebServerPlugin(webConfig, server));
addPlugin(new WebConfigPlugin(server));
addPlugin(new WebApiPlugin(server));
}
class IlluCat : public Sprocket {
public:
NeoPattern* pixels;
NeoPatternDto defaultState;
NeoPatternDto state;
AsyncWebServer* server;
AsyncWebSocket* ws;
//AsyncWebSocket* wsStream;
NeoPixelConfig pixelConfig;
SprocketConfig sprocketConfig;
OtaConfig otaConfig;
WebServerConfig webConfig;
SprocketMessage currentMessage;
IlluCat(SprocketConfig cfg, OtaConfig otaCfg, WebServerConfig webCfg) : Sprocket(cfg) {
sprocketConfig = cfg;
otaConfig = otaCfg;
webConfig = webCfg;
pixelConfig.pin = 4;
pixelConfig.length = 8;
pixelConfig.brightness = 32;
pixelConfig.updateInterval = 100;
pixelConfig.defaultColor = 100;
}
virtual void scanningAnimation() {
pixels->Scanner(pixels->Wheel(COLOR_NOT_CONNECTED), pixelConfig.updateInterval);
}
virtual void defaultAnimation() {
pixels->RainbowCycle(pixelConfig.updateInterval);
}
Sprocket* activate(Scheduler* scheduler, Network* network) {
// load config files from SPIFFS
if(SPIFFS.begin()){
pixelConfig.fromFile("/pixelConfig.json");
defaultState.fromFile("/pixelState.json");
state = defaultState;
}
// initialize services
pixels = new NeoPattern(pixelConfig.length, pixelConfig.pin, NEO_GRB + NEO_KHZ800);
server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/pixel");
//wsStream = new AsyncWebSocket("/stream");
defaultAnimation();
// add plugins
// TODO add HTTP OTA instead of TCP
//addPlugin(new OtaTcpPlugin(otaConfig));
addPlugin(new WebServerPlugin(webConfig, server));
addPlugin(new WebConfigPlugin(server));
addPlugin(new PixelPlugin(pixelConfig, pixels));
// FIXME move to networking
//String softApPrt = "SoftAP IP: " + WiFi.softAPIP().toString();
//PRINT_MSG(Serial, SPROCKET_TYPE, softApPrt.c_str());
// TODO move to plugin
// setup web stuff
server->serveStatic("/pixelConfig.json", SPIFFS, "pixelConfig.json");
server->on("/pixel/api", HTTP_POST, bind(&IlluCat::patternWebRequestHandler, this, _1));
ws->onEvent(bind(&IlluCat::onWsEvent, this, _1, _2, _3, _4, _5, _6));
server->addHandler(ws);
//wsStream->onEvent(bind(&IlluCat::onStream, this, _1, _2, _3, _4, _5, _6));
//server->addHandler(wsStream);
return Sprocket::activate(scheduler, network);
} using Sprocket::activate;
// TODO move to utils
String getRequestParameterOrDefault(AsyncWebServerRequest *request, String param, String defaultValue, bool isPost = true){
if(request->hasParam(param, isPost)) {
return request->getParam(param, isPost)->value();
}
return defaultValue;
}
void onStream(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if(type == WS_EVT_DATA){
PRINT_MSG(Serial, SPROCKET_TYPE, WsUtils::parseFrameAsString(type, arg, data, len, 0).c_str());
pixels->ActivePattern = NONE;
pixels->handleStream(data, len);
}
}
void patternWebRequestHandler(AsyncWebServerRequest *request) {
PRINT_MSG(Serial, SPROCKET_TYPE, "POST /pixel/api");
currentMessage.topic = getRequestParameterOrDefault(request, "topic", "");
currentMessage.payload = getRequestParameterOrDefault(request, "payload", "");
currentMessage.broadcast = atoi(getRequestParameterOrDefault(request, "broadcast", "0").c_str());
String msg = currentMessage.toJsonString();
publish(currentMessage.topic, currentMessage.payload);
if(currentMessage.broadcast){
network.broadcast(msg);
}
request->send(200, "text/plain", msg);
}
virtual void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if(type == WS_EVT_DATA){
String frame = WsUtils::parseFrameAsString(type, arg, data, len, 0);
dispatch(0, frame);
network.broadcast(frame);
}
}
virtual void dispatch( uint32_t from, String &msg ) {
currentMessage.fromJsonString(msg);
if(currentMessage.valid){
currentMessage.from = from;
publish(currentMessage.topic, currentMessage.payload);
}
}
void loop(){
Sprocket::loop();
yield();
}
};
#endif

96
src/PixelPlugin.h Normal file
View File

@@ -0,0 +1,96 @@
#ifndef __PIXEL_PLUGIN__
#define __PIXEL_PLUGIN__
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#include "TaskSchedulerDeclarations.h"
#include "MeshNet.h"
#include "Plugin.h"
#include "NeoPatternDto.h"
#include "NeoPattern.cpp"
using namespace std;
using namespace std::placeholders;
class PixelPlugin : public Plugin {
private:
NeoPixelConfig pixelConfig;
NeoPattern* pixels;
NeoPatternState state;
public:
Task animation;
PixelPlugin(NeoPixelConfig cfg, NeoPattern* neoPattern){
pixelConfig = cfg;
pixels = neoPattern;
pixels->begin();
pixels->setBrightness(pixelConfig.brightness);
}
void activate(Scheduler* userScheduler, Network* network){
subscribe("pixels/colorWheel", bind(&PixelPlugin::colorWheel, this, _1));
subscribe("pixels/color", bind(&PixelPlugin::setColor, this, _1));
subscribe("pixels/color2", bind(&PixelPlugin::setColor2, this, _1));
subscribe("pixels/pattern", bind(&PixelPlugin::setPattern, this, _1));
subscribe("pixels/totalSteps", bind(&PixelPlugin::setTotalSteps, this, _1));
subscribe("pixels/brightness", bind(&PixelPlugin::setBrightness, this, _1));
subscribe("pixels/state", bind(&PixelPlugin::setState, this, _1));
animation.set(TASK_MILLISECOND * pixelConfig.updateInterval, TASK_FOREVER, bind(&PixelPlugin::animate, this));
userScheduler->addTask(animation);
animation.enable();
PRINT_MSG(Serial, SPROCKET_TYPE, "NeoPixels activated");
}
void setState(String msg) {
PRINT_MSG(Serial, SPROCKET_TYPE, msg.c_str());
state.fromJsonString(msg);
//pixels->setBrightness(state.brightness);
//pixels->ColorSet(state.color);
pixels->Index = 0;
pixels->Color1 = state.color;
pixels->Color2 = state.color2;
pixels->TotalSteps = state.totalSteps;
pixels->ActivePattern = (pattern) state.pattern;
pixels->Direction = FORWARD;
}
void colorWheel(String msg){
int color = atoi(msg.c_str());
pixels->ActivePattern = NONE;
pixels->ColorSet(pixels->Wheel(color));
}
void setTotalSteps(String msg){
pixels->TotalSteps = atoi(msg.c_str());
}
void setBrightness(String msg){
int inVal = atoi(msg.c_str());
pixels->setBrightness(inVal);
pixels->show();
}
void setColor(String msg){
pixels->ActivePattern = NONE;
pixels->Color1 = atoi(msg.c_str());
//if(pixels->ActivePattern == NONE){
pixels->ColorSet(pixels->Color1);
//}
}
void setColor2(String msg){
pixels->Color2 = atoi(msg.c_str());
}
void setPattern(String msg){
pixels->Index = 0;
pixels->Direction = FORWARD;
pixels->ActivePattern = (pattern)atoi(msg.c_str());
}
void animate(){
pixels->Update();
}
void enable(){
animation.enable();
}
void disable(){
animation.disable();
}
};
#endif

View File

@@ -1,10 +1,9 @@
#ifndef __ILLUCAT_CONFIG__
#define __ILLUCAT_CONFIG__
#ifndef __MESH_CONFIG__
#define __MESH_CONFIG__
// Scheduler config
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
// Chip config
#define SPROCKET_TYPE "ILLUCAT"
@@ -24,9 +23,7 @@
#define HOSTNAME "illucat"
#define CONNECT_TIMEOUT 10000
#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION
//#define MESH_DEBUG_TYPES ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE
#define PIXEL_CONFIG_FILE "/pixelConfig.json"
//ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE
// OTA config
#define OTA_PORT 8266
@@ -36,7 +33,6 @@
#define WEB_CONTEXT_PATH "/"
#define WEB_DOC_ROOT "/www"
#define WEB_DEFAULT_FILE "index.html"
#define WEB_PORT 80
// NeoPixel
#define LED_STRIP_PIN D2

View File

@@ -1,62 +0,0 @@
#ifndef __ILLUCHAT_CONFIG__
#define __ILLUCHAT_CONFIG__
// Scheduler config
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
// Chip config
#define SPROCKET_TYPE "ILLUCAT"
#define SERIAL_BAUD_RATE 115200
#define STARTUP_DELAY 1000
// Network config
#define WIFI_MODE 0
#define WIFI_CHANNEL 11
#define AP_SSID "illucat"
#define AP_PASSWORD "illumination"
#define MESH_PREFIX "illucat-mesh"
#define MESH_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "MyAP"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "illucat"
#define CONNECT_TIMEOUT 10000
// config files
#define PIXEL_CONFIG_FILE "/pixelConfig.json"
#define MQTT_CONFIG_FILE "/mqttConfig.json"
#define IRC_CONFIG_FILE "/ircConfig.json"
// NeoPixel
#define LED_STRIP_PIN D2
#define LED_STRIP_LENGTH 8
#define LED_STRIP_BRIGHTNESS 48
#define LED_STRIP_UPDATE_INTERVAL 200
#define LED_STRIP_DEFAULT_COLOR 100
#define COLOR_CONNECTED LED_STRIP_DEFAULT_COLOR
#define COLOR_NOT_CONNECTED 255
#define IRC_SERVER "chat.freenode.net"
#define IRC_PORT 6665
#define IRC_NICKNAME ""
#define IRC_USER ""
// OTA config
#define OTA_PORT 8266
#define OTA_PASSWORD ""
// WebServer
#define WEB_CONTEXT_PATH "/"
#define WEB_DOC_ROOT "/www"
#define WEB_DEFAULT_FILE "index.html"
#define WEB_PORT 80
// mqtt config
#define MQTT_CLIENT_NAME "illucat"
#define MQTT_HOST "192.168.1.2"
#define MQTT_PORT 1883
#define MQTT_ROOT_TOPIC "wirelos/led/illucat"
#endif

View File

@@ -1,78 +0,0 @@
#include "illuchat_config.h"
#include <WiFiNet.h>
#include <Sprocket.h>
#include <ESPAsyncWebServer.h>
#include <WebServerConfig.h>
#include <WebServerPlugin.h>
#include <WebConfigPlugin.h>
#include <WebApiPlugin.h>
#include <PixelPlugin.h>
#include <IrcPlugin.h>
#include <MqttPlugin.h>
WiFiNet *network;
Sprocket *sprocket;
WebServerPlugin *webServerPlugin;
WebConfigPlugin *webConfigPlugin;
WebApiPlugin *webApiPlugin;
PixelPlugin *pixelPlugin;
IrcPlugin *ircPlugin;
MqttPlugin *mqttPlugin;
void setup()
{
const char *chipName = String("Sprocket" + String(ESP.getChipId())).c_str();
sprocket = new Sprocket({STARTUP_DELAY, SERIAL_BAUD_RATE});
pixelPlugin = new PixelPlugin({LED_STRIP_PIN, LED_STRIP_LENGTH, LED_STRIP_BRIGHTNESS, LED_STRIP_UPDATE_INTERVAL});
ircPlugin = new IrcPlugin({IRC_SERVER, IRC_PORT, chipName, chipName});
mqttPlugin = new MqttPlugin({MQTT_CLIENT_NAME, MQTT_HOST, MQTT_PORT, MQTT_ROOT_TOPIC});
webServerPlugin = new WebServerPlugin({WEB_CONTEXT_PATH, WEB_DOC_ROOT, WEB_DEFAULT_FILE, WEB_PORT});
webConfigPlugin = new WebConfigPlugin(webServerPlugin->server);
webApiPlugin = new WebApiPlugin(webServerPlugin->server);
sprocket->addPlugin(pixelPlugin);
sprocket->addPlugin(webServerPlugin);
sprocket->addPlugin(webConfigPlugin);
sprocket->addPlugin(webApiPlugin);
sprocket->addPlugin(ircPlugin);
sprocket->addPlugin(mqttPlugin);
network = new WiFiNet(
WIFI_MODE,
STATION_SSID,
STATION_PASSWORD,
AP_SSID,
AP_PASSWORD,
HOSTNAME,
CONNECT_TIMEOUT);
network->connect();
webServerPlugin->server->serveStatic(PIXEL_CONFIG_FILE, SPIFFS, "pixelConfig.json");
webServerPlugin->server->serveStatic(IRC_CONFIG_FILE, SPIFFS, "ircConfig.json");
webServerPlugin->server->serveStatic(MQTT_CONFIG_FILE, SPIFFS, "mqttConfig.json");
sprocket->subscribe("irc/connected", [](String msg) {
if (atoi(msg.c_str()))
{
sprocket->subscribe("irc/log", [](String msg) {
PRINT_MSG(Serial, "CHAT", String("incoming: " + msg).c_str());
webApiPlugin->ws->textAll(msg);
});
sprocket->subscribe("out/chat/log", [](String msg) {
PRINT_MSG(Serial, "CHAT", String("outgoing: " + msg).c_str());
sprocket->publish("irc/sendMessage", msg);
webApiPlugin->ws->textAll("You:"+msg);
});
sprocket->publish("chat/connected", "");
}
});
sprocket->activate();
sprocket->publish("irc/connect","");
}
void loop()
{
sprocket->loop();
yield();
}

View File

@@ -1,55 +0,0 @@
#include "mqcatt_config.h"
#include <WiFiNet.h>
#include <Sprocket.h>
#include <ESPAsyncWebServer.h>
#include <WebServerConfig.h>
#include <WebServerPlugin.h>
#include <WebConfigPlugin.h>
#include <WebApiPlugin.h>
#include <PixelPlugin.h>
#include <MqttPlugin.h>
WiFiNet *network;
Sprocket *sprocket;
WebServerPlugin *webServerPlugin;
WebConfigPlugin *webConfigPlugin;
WebApiPlugin *webApiPlugin;
PixelPlugin *pixelPlugin;
MqttPlugin *mqttPlugin;
void setup()
{
sprocket = new Sprocket({STARTUP_DELAY, SERIAL_BAUD_RATE});
pixelPlugin = new PixelPlugin({LED_STRIP_PIN, LED_STRIP_LENGTH, LED_STRIP_BRIGHTNESS, LED_STRIP_UPDATE_INTERVAL});
mqttPlugin = new MqttPlugin({MQTT_CLIENT_NAME, MQTT_HOST, MQTT_PORT, MQTT_ROOT_TOPIC});
webServerPlugin = new WebServerPlugin({WEB_CONTEXT_PATH, WEB_DOC_ROOT, WEB_DEFAULT_FILE, WEB_PORT});
webConfigPlugin = new WebConfigPlugin(webServerPlugin->server);
webApiPlugin = new WebApiPlugin(webServerPlugin->server);
sprocket->addPlugin(pixelPlugin);
sprocket->addPlugin(webServerPlugin);
sprocket->addPlugin(webConfigPlugin);
sprocket->addPlugin(webApiPlugin);
sprocket->addPlugin(mqttPlugin);
network = new WiFiNet(
WIFI_MODE,
STATION_SSID,
STATION_PASSWORD,
AP_SSID,
AP_PASSWORD,
HOSTNAME,
CONNECT_TIMEOUT);
network->connect();
webServerPlugin->server->serveStatic(PIXEL_CONFIG_FILE, SPIFFS, "pixelConfig.json");
webServerPlugin->server->serveStatic(MQTT_CONFIG_FILE, SPIFFS, "mqttConfig.json");
sprocket->activate();
sprocket->publish("pixels/pattern", "1");
}
void loop()
{
sprocket->loop();
yield();
}

View File

@@ -1,57 +0,0 @@
#ifndef __MQCATT_CONFIG__
#define __MQCATT_CONFIG__
// Scheduler config
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
// Chip config
#define SPROCKET_TYPE "ILLUCAT"
#define SERIAL_BAUD_RATE 115200
#define STARTUP_DELAY 1000
// Network config
#define WIFI_MODE 0
#define WIFI_CHANNEL 11
#define AP_SSID "illucat"
#define AP_PASSWORD "illumination"
#define MESH_PREFIX "illucat-mesh"
#define MESH_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "MyAP"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "illucat"
#define CONNECT_TIMEOUT 10000
// config files
#define PIXEL_CONFIG_FILE "/pixelConfig.json"
#define MQTT_CONFIG_FILE "/mqttConfig.json"
// NeoPixel
#define LED_STRIP_PIN D2
#define LED_STRIP_LENGTH 8
#define LED_STRIP_BRIGHTNESS 48
#define LED_STRIP_UPDATE_INTERVAL 200
#define LED_STRIP_DEFAULT_COLOR 100
#define COLOR_CONNECTED LED_STRIP_DEFAULT_COLOR
#define COLOR_NOT_CONNECTED 255
// OTA config
#define OTA_PORT 8266
#define OTA_PASSWORD ""
// mqtt config
#define MQTT_CLIENT_NAME "illucat"
#define MQTT_HOST "192.168.1.2"
#define MQTT_PORT 1883
#define MQTT_ROOT_TOPIC "wirelos/illucat"
// WebServer
#define WEB_CONTEXT_PATH "/"
#define WEB_DOC_ROOT "/www"
#define WEB_DEFAULT_FILE "index.html"
#define WEB_PORT 80
#endif

View File

@@ -1,29 +0,0 @@
#include "config.h"
#include "WiFiNet.h"
#include "IlluCat.h"
WiFiNet *network;
IlluCat *sprocket;
void setup()
{
sprocket = new IlluCat(
{STARTUP_DELAY, SERIAL_BAUD_RATE},
{WEB_CONTEXT_PATH, WEB_DOC_ROOT, WEB_DEFAULT_FILE, WEB_PORT});
network = new WiFiNet(
SPROCKET_MODE,
STATION_SSID,
STATION_PASSWORD,
AP_SSID,
AP_PASSWORD,
HOSTNAME,
CONNECT_TIMEOUT);
network->connect();
sprocket->activate();
}
void loop()
{
sprocket->loop();
yield();
}

View File

@@ -1,28 +0,0 @@
#include "config.h"
#include "Sprocket.h"
#include <MeshNetworkPlugin.cpp>
#include "PixelPlugin.h"
Sprocket *sprocket;
void setup()
{
sprocket = new Sprocket(
{STARTUP_DELAY, SERIAL_BAUD_RATE});
sprocket->addPlugin(new PixelPlugin(
{LED_STRIP_PIN,
LED_STRIP_LENGTH,
LED_STRIP_BRIGHTNESS,
LED_STRIP_UPDATE_INTERVAL}));
sprocket->addPlugin(new MeshNetworkPlugin(
{SPROCKET_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES}));
sprocket->activate();
}
void loop()
{
sprocket->loop();
yield();
}

118
src/wifi/WebCat.h Normal file
View File

@@ -0,0 +1,118 @@
#ifndef __WEB_CAT__
#define __WEB_CAT__
#include <TaskScheduler.h>
#include <MeshNet.h>
#include <Sprocket.h>
#include "config.h"
#include "NeoPattern.cpp"
#include "NeoPatternDto.h"
#include "NeoPattern_api_json.h"
#include "NeoPattern_api_modes.cpp"
#include "utils_print.h"
#include "utils_ws.h"
#include <plugins/WebSO.h>
#include <plugins/OtaTcpPlugin.cpp>
#include <plugins/WebServerPlugin.cpp>
#include <plugins/WebConfigPlugin.cpp>
#include "PixelPlugin.h"
#include "IlluCat.h"
using namespace std;
using namespace std::placeholders;
class WebCat : public IlluCat {
public:
AsyncWebServer* server;
AsyncWebSocket* ws;
//AsyncWebSocket* wsStream;
WebServerConfig webConfig;
SprocketMessage currentMessage;
WebCat(SprocketConfig cfg, OtaConfig otaCfg, WebServerConfig webCfg) : IlluCat(cfg, otaCfg, webCfg) {
webConfig = webCfg;
}
Sprocket* activate(Scheduler* scheduler, Network* network) {
Serial.println("SETUP SERVER");
// initialize services
server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/pixel");
//wsStream = new AsyncWebSocket("/stream");
Serial.println("init handlers");
// TODO move to plugin
// setup web stuff
server->serveStatic("/pixelConfig.json", SPIFFS, "pixelConfig.json");
server->on("/pixel/api", HTTP_POST, bind(&WebCat::patternWebRequestHandler, this, _1));
ws->onEvent(bind(&WebCat::onWsEvent, this, _1, _2, _3, _4, _5, _6));
server->addHandler(ws);
//wsStream->onEvent(bind(&WebCat::onStream, this, _1, _2, _3, _4, _5, _6));
//server->addHandler(wsStream);
Serial.println("add plugins");
// add plugins
// TODO add HTTP OTA instead of TCP
//addPlugin(new OtaTcpPlugin(otaConfig));
addPlugin(new WebServerPlugin(webConfig, server));
addPlugin(new WebConfigPlugin(server));
return IlluCat::activate(scheduler, network);;
};
// TODO move to utils
String getRequestParameterOrDefault(AsyncWebServerRequest *request, String param, String defaultValue, bool isPost = true){
if(request->hasParam(param, isPost)) {
return request->getParam(param, isPost)->value();
}
return defaultValue;
}
void onStream(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if(type == WS_EVT_DATA){
PRINT_MSG(Serial, SPROCKET_TYPE, WsUtils::parseFrameAsString(type, arg, data, len, 0).c_str());
pixels->ActivePattern = NONE;
pixels->handleStream(data, len);
}
}
void patternWebRequestHandler(AsyncWebServerRequest *request) {
PRINT_MSG(Serial, SPROCKET_TYPE, "POST /pixel/api");
currentMessage.topic = getRequestParameterOrDefault(request, "topic", "");
currentMessage.payload = getRequestParameterOrDefault(request, "payload", "");
currentMessage.broadcast = atoi(getRequestParameterOrDefault(request, "broadcast", "0").c_str());
String msg = currentMessage.toJsonString();
publish(currentMessage.topic, currentMessage.payload);
if(currentMessage.broadcast){
network.broadcast(msg);
}
request->send(200, "text/plain", msg);
}
virtual void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if(type == WS_EVT_DATA){
String frame = WsUtils::parseFrameAsString(type, arg, data, len, 0);
dispatch(0, frame);
network.broadcast(frame);
}
}
virtual void dispatch( uint32_t from, String &msg ) {
currentMessage.fromJsonString(msg);
if(currentMessage.valid){
currentMessage.from = from;
publish(currentMessage.topic, currentMessage.payload);
}
}
void loop(){
Sprocket::loop();
yield();
}
};
#endif

27
src/wifi/main.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "config.h"
#include "WiFiNet.h"
#include "IlluCat.h"
WiFiNet net(
SPROCKET_MODE,
STATION_SSID,
STATION_PASSWORD,
AP_SSID,
AP_PASSWORD,
HOSTNAME,
CONNECT_TIMEOUT
);
IlluCat sprocket(
{ STARTUP_DELAY, SERIAL_BAUD_RATE },
{ OTA_PORT, OTA_PASSWORD },
{ WEB_CONTEXT_PATH, WEB_DOC_ROOT, WEB_DEFAULT_FILE }
);
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}

39
src/wifiMesh/MeshCat.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef __MESHCAT__
#define __MESHCAT__
#include <TaskSchedulerDeclarations.h>
#include <MeshNet.h>
#include <SprocketConfig.h>
#include "utils_print.h"
#include "IlluCat.h"
#include "config.h"
using namespace std;
using namespace std::placeholders;
class MeshCat : public IlluCat {
public:
Scheduler* meshScheduler;
MeshCat(SprocketConfig cfg, OtaConfig otaCfg, WebServerConfig webCfg) : IlluCat(cfg, otaCfg, webCfg) {
meshScheduler = new Scheduler();
}
Sprocket* join(Network& net){
PRINT_MSG(Serial, SPROCKET_TYPE, "join mesh network");
net.init(meshScheduler);
net.onReceive(bind(&IlluCat::dispatch,this, _1, _2));
net.connect();
network = net;
return activate(&scheduler, &net);
}
void loop() {
meshScheduler->execute();
yield();
Sprocket::loop();
yield();
}
};
#endif

24
src/wifiMesh/main.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "config.h"
#include "MeshNet.h"
#include "MeshCat.h"
MeshNet net({
SPROCKET_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES
});
MeshCat sprocket(
{ STARTUP_DELAY, SERIAL_BAUD_RATE },
{ OTA_PORT, OTA_PASSWORD },
{ WEB_CONTEXT_PATH, WEB_DOC_ROOT, WEB_DEFAULT_FILE }
);
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}