mirror of
https://gitlab.com/zwirbel/illucat.git
synced 2025-12-15 01:42:22 +01:00
Merge branch 'release-1.0' into 'master'
Release 1.0 See merge request 0x1d/illucat!2
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -13,6 +13,8 @@
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp"
|
||||
"vector": "cpp",
|
||||
"tuple": "cpp",
|
||||
"utility": "cpp"
|
||||
}
|
||||
}
|
||||
46
README.md
46
README.md
@@ -1,43 +1,9 @@
|
||||
# Illumination-Cat
|
||||
This is the brain of the the almighty [Illumination Cat](https://www.thingiverse.com/thing:2974862).
|
||||
This is the brain of the the almighty Illumination-Cat.
|
||||
|
||||
## API
|
||||
### WebSocket
|
||||
Endpoint: /pixel
|
||||
## Resources & Documentation
|
||||
[3D Model](https://www.thingiverse.com/thing:2974862)
|
||||
[Installation](https://gitlab.com/0x1d/illucat/blob/master/installation.md)
|
||||
[API](https://gitlab.com/0x1d/illucat/blob/master/api.md)
|
||||
[OctoPrint Stuff](https://github.com/FrYakaTKoP/simple-octo-ws2812)
|
||||
|
||||
Fields:
|
||||
|Field|Type|Description|
|
||||
| --- |---| ---|
|
||||
| topic | String | Defines which functionality is executed, usually to set a value or activate a pattern |
|
||||
| payload | String | data to be set |
|
||||
|
||||
Example:
|
||||
``` json
|
||||
{
|
||||
"topic": "pixels/color",
|
||||
"payload": "13505813"
|
||||
}
|
||||
```
|
||||
#### Topics
|
||||
| Topic | Data |
|
||||
| ----- | ---- |
|
||||
| pixels/color | |
|
||||
| pixels/color2 | |
|
||||
| pixels/pattern | |
|
||||
| pixels/totalSteps | |
|
||||
| pixels/brightness | |
|
||||
|
||||
### REST
|
||||
#### Endpoints
|
||||
POST /pixel/state
|
||||
POST /config
|
||||
|
||||
### Mesh
|
||||
|
||||
## Features
|
||||
- Enduser setup: initial setup where the cat opens an access point for configuration
|
||||
- WiFi: connect to existing AP as client or build a mesh network where all cats act as a collective
|
||||
- Web controls: colors and patterns can be changed through the web interface
|
||||
- OTA plugin: cats connected to an AP can be updated over-the-air via TCP flash method
|
||||
- [0%] audio output
|
||||
- [0%] OctoPrint plugin: connect to an OctoPrint instance and reflect print status via colors
|
||||
74
api.md
Normal file
74
api.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# API
|
||||
Two API architectures are supported: WebSocket and REST.
|
||||
Both can be used with the same set of fields.
|
||||
As everything can be controlled with topics, only a few fields are required:
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ---- |
|
||||
| topic | String | Topic where the payload is dispatched |
|
||||
| payload | String | Payload to be dispatched |
|
||||
| broadcast | Integer | Where to send the payload; 0 = local, 1 = broadcast |
|
||||
|
||||
|
||||
|
||||
- topic
|
||||
- payload
|
||||
- broadcast
|
||||
|
||||
## Topics
|
||||
All functionality can be used by sending messages to these topics.
|
||||
|
||||
| topic | type | payload |
|
||||
| ----- | ---- | ---- |
|
||||
| pixels/colorWheel | Integer | Value from 0 to 255 to cycle through all colors |
|
||||
| pixels/color | Integer | RGB color as integer. By calling this topic, all LEDs of the strip are set synchronously, stopping current running animation. |
|
||||
| pixels/color2 | Integer |RGB color as integer. Sets the second color used in animations. Does not stop current running animation. |
|
||||
| pixels/pattern | Integer | Value from 0 to 5 to set the current animation. Available animations: { NONE = 0, RAINBOW_CYCLE = 1, THEATER_CHASE = 2, COLOR_WIPE = 3, SCANNER = 4, FADE = 5 } |
|
||||
| pixels/totalSteps | Integer | Number of steps of an animation. |
|
||||
| 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: /pixel
|
||||
Send a JSON String containing the mandatory fields.
|
||||
|
||||
Example:
|
||||
``` json
|
||||
{
|
||||
"topic": "pixels/color",
|
||||
"payload": 13505813
|
||||
}
|
||||
```
|
||||
|
||||
## REST
|
||||
#### Endpoints
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
POST /pixel/api
|
||||
Request: Form post with topic and payload as fields
|
||||
Response: 200 + final payload as JSON
|
||||
|
||||
Examples:
|
||||
```shell
|
||||
# set color by wheel
|
||||
curl -v -X POST \
|
||||
--data "topic=pixels/colorWheel" \
|
||||
--data "payload=20" \
|
||||
http://illucat/pixel/api
|
||||
|
||||
# set color only on current node
|
||||
curl -v -X POST \
|
||||
--data "topic=pixels/colorWheel" \
|
||||
--data "payload=20" \
|
||||
--data "broadcast=1"\
|
||||
http://illucat/pixel/api
|
||||
|
||||
# set brightness
|
||||
curl -v -X POST \
|
||||
--data "topic=pixels/brightness" \
|
||||
--data "payload=10" \
|
||||
http://illucat/pixel/api
|
||||
|
||||
# run rainbow pattern
|
||||
curl -v -X POST \
|
||||
--data "topic=pixels/pattern" \
|
||||
--data "payload=1" \
|
||||
http://illucat/pixel/api
|
||||
```
|
||||
@@ -2,10 +2,10 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>ESP Kit</title>
|
||||
<title>Illu-Cat</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" sizes="16x16" href="/favicon-32x32.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,6 +21,13 @@
|
||||
.sui > .content {
|
||||
padding: 16px;
|
||||
}
|
||||
@media screen and (min-width: 968px) {
|
||||
.sui > .content {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
.sui label {
|
||||
color: #b3b2b2;
|
||||
}
|
||||
@@ -150,6 +157,9 @@ input[type=range]:focus::-ms-fill-lower {
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: #0eb4bb;
|
||||
}
|
||||
.form-row input[type="range"] {
|
||||
margin-top: 12px;
|
||||
}
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
@@ -274,13 +284,13 @@ form .form-row input[type="checkbox"] {
|
||||
transform: scale(2);
|
||||
}
|
||||
.ColorPicker {
|
||||
flex: none !important;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
}
|
||||
.sui select {
|
||||
flex: 5;
|
||||
padding: .5em;
|
||||
color: #eeeeee;
|
||||
background: none;
|
||||
|
||||
@@ -20,6 +20,6 @@ void PRINT_MSG(Print &out, const char* prefix, const char* format, ...) {
|
||||
vsnprintf(ptr, sizeof(formatString)-1-strlen(formatString), formatString, args );
|
||||
va_end (args);
|
||||
formatString[ sizeof(formatString)-1 ]='\0';
|
||||
out.print(ptr);
|
||||
out.println(ptr);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,13 @@ lib_deps =
|
||||
ESPAsyncTCP
|
||||
TaskScheduler
|
||||
SPIFFS
|
||||
painlessMesh
|
||||
ESP8266mDNS
|
||||
ArduinoOTA
|
||||
ArduinoJson
|
||||
ESP Async WebServer
|
||||
ESPAsyncTCP
|
||||
Adafruit NeoPixel
|
||||
|
||||
[env:build]
|
||||
platform = ${common.platform}
|
||||
@@ -30,13 +37,17 @@ upload_speed = ${common.upload_speed}
|
||||
monitor_baud = ${common.monitor_baud}
|
||||
framework = ${common.framework}
|
||||
build_flags = -Wl,-Teagle.flash.4m1m.ld
|
||||
;lib_extra_dirs = ~/src/illucat/.piolibdeps/sprocket-core/lib
|
||||
-DSPROCKET_PRINT=1
|
||||
lib_deps = ${common.lib_deps}
|
||||
painlessMesh
|
||||
ESP8266mDNS
|
||||
ArduinoOTA
|
||||
ArduinoJson
|
||||
ESP Async WebServer
|
||||
ESPAsyncTCP
|
||||
Adafruit NeoPixel
|
||||
https://gitlab.com/wirelos/sprocket-core.git#develop
|
||||
|
||||
[env:release]
|
||||
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=0
|
||||
lib_deps = ${common.lib_deps}
|
||||
https://gitlab.com/wirelos/sprocket-core.git#master
|
||||
@@ -55,99 +55,93 @@ class IlluCat : public MeshSprocket {
|
||||
pixelConfig.defaultColor = 100;
|
||||
|
||||
}
|
||||
// TDOO remove
|
||||
virtual void scanningAnimation() {
|
||||
pixels->Scanner(pixels->Wheel(COLOR_NOT_CONNECTED), pixelConfig.updateInterval);
|
||||
//pixels->Fade(0, pixels->Color(255,255,255), 4, pixelConfig.updateInterval, FORWARD);
|
||||
}
|
||||
virtual void defaultAnimation() {
|
||||
String defaultStr = String(defaultState.value);
|
||||
PIXEL_FNCS[defaultState.mode](pixels, defaultStr.c_str());
|
||||
//pixels->RainbowCycle(150);
|
||||
}
|
||||
Sprocket* activate(Scheduler* scheduler, Network* network) {
|
||||
|
||||
net = static_cast<MeshNet*>(network);
|
||||
net->mesh.onNewConnection(bind(&IlluCat::onNewConnection,this, _1));
|
||||
//net->mesh.onChangedConnections(bind(&IlluCat::onConnectionChanged,this));
|
||||
|
||||
// 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");
|
||||
dnsServer = new DNSServer();
|
||||
|
||||
// add plugins
|
||||
addPlugin(new OtaTcpPlugin(otaConfig));
|
||||
addPlugin(new WebServerPlugin(webConfig, server));
|
||||
addPlugin(new WebConfigPlugin(server));
|
||||
addPlugin(new PixelPlugin(pixelConfig, pixels));
|
||||
|
||||
|
||||
// TODO plugin?
|
||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
|
||||
Serial.println("SoftAP IP: " + WiFi.softAPIP().toString());
|
||||
|
||||
defaultAnimation();
|
||||
|
||||
// TODO plugin
|
||||
// configure DNS
|
||||
// plugin?
|
||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
|
||||
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/pattern", HTTP_POST, bind(&IlluCat::patternWebRequestHandler, this, _1));
|
||||
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);
|
||||
|
||||
// FIXME OnDisable is triggered after last scan, aprx. 10 sec
|
||||
// FIXME chck if this is also triggered when new connection arrives
|
||||
//net->mesh.stationScan.task.setOnDisable(bind(&IlluCat::defaultAnimation,this));
|
||||
return MeshSprocket::activate(scheduler, network);
|
||||
} using MeshSprocket::activate;
|
||||
|
||||
void patternWebRequestHandler(AsyncWebServerRequest *request) {
|
||||
Serial.println("POST /pixel/state");
|
||||
if(request->hasParam("state", true)) {
|
||||
String inStr = request->getParam("state", true)->value();
|
||||
dispatch(0, inStr);
|
||||
// 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();
|
||||
}
|
||||
request->send(200, "text/plain", "OK");
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
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){
|
||||
net->mesh.sendBroadcast(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);
|
||||
net->mesh.sendBroadcast(frame);
|
||||
dispatch(0, frame);
|
||||
client->text(String(millis()));
|
||||
}
|
||||
}
|
||||
|
||||
virtual void dispatch( uint32_t from, String &msg ) {
|
||||
//Serial.println(msg);
|
||||
currentMessage.fromJsonString(msg);
|
||||
if(currentMessage.valid){
|
||||
currentMessage.from = from;
|
||||
publish(currentMessage.topic, currentMessage.payload);
|
||||
if(currentMessage.broadcast){
|
||||
net->mesh.sendBroadcast(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void onNewConnection(uint32_t nodeId){
|
||||
PRINT_MSG(Serial, SPROCKET_TYPE, "connected to %u", nodeId);
|
||||
// publish current state to new node
|
||||
// TODO broadcast enable/disable flag
|
||||
//String stateJson = currentMessage.toJsonString();
|
||||
//net->mesh.sendSingle(nodeId, stateJson);
|
||||
}
|
||||
//virtual void onConnectionChanged(){
|
||||
// PRINT_MSG(Serial, SPROCKET_TYPE, "connection changed");
|
||||
// //if(!net->mesh.getNodeList().size()){
|
||||
// // defaultAnimation();
|
||||
// //}
|
||||
//}
|
||||
|
||||
void loop(){
|
||||
MeshSprocket::loop();
|
||||
dnsServer->processNextRequest();
|
||||
|
||||
@@ -38,11 +38,17 @@ class PixelPlugin : public Plugin {
|
||||
animation.set(TASK_MILLISECOND * pixelConfig.updateInterval, TASK_FOREVER, bind(&PixelPlugin::animate, this));
|
||||
userScheduler->addTask(animation);
|
||||
animation.enable();
|
||||
Serial.println("NeoPixels activated");
|
||||
PRINT_MSG(Serial, SPROCKET_TYPE, "NeoPixels activated");
|
||||
}
|
||||
// TODO set the whole pixel state
|
||||
void setState(String msg){
|
||||
void setState(String msg) {
|
||||
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;
|
||||
}
|
||||
void colorWheel(String msg){
|
||||
int color = atoi(msg.c_str());
|
||||
|
||||
Reference in New Issue
Block a user