rjwats / esp8266-react

A framework for ESP8266 & ESP32 microcontrollers with a React UI
GNU Lesser General Public License v3.0
465 stars 143 forks source link

How do I push websocket data from backend to ui #226

Open alon24 opened 3 years ago

alon24 commented 3 years ago

I am trying to figure out how to create my own (CAR) service, that will push data (button pushed) from backend to frontend using the websocket.

I am failing at this. Could you add a small example of 2 way websocket send and recieve? prefebly one which will also show how to send an object ({btn1: fale, xxx:"some value" ....}

I managed to read my button but cannot understrand the update and what is needed from backend to ui.

Thanks

rjwats commented 3 years ago

I'm not sure if I fully understand your question, however I'll try and clarify how to update state when using a StatefulService instance. Remember, the docs should give you plenty of info here in this section.

With the observer pattern you are not responsible for sending or receiving over WebSockets. If you have registered a WebSocketTxRx instance the StatefulService instance takes care of that for you when you update the state that it manages.

As an example, if I wanted to turn an LED on (in the default example given by the framework), it would be a simple matter of updating the state through a call to the update function:

lightStateService.update([&](LightState& state) {
  state.on = true;
  return StateUpdateResult::CHANGED;
}, "button_press");

If I wanted to toggle the LED I could do the following:

lightStateService.update([&](LightState& state) {
  state.on = !state.on;
  return StateUpdateResult::CHANGED;
}, "button_press");

Doing this, and returning "CHANGED" should automatically cause any observers (in this case the WebSockeTxRx instance) to react to the state change and broadcast the state update to connected clients.

Of course there is no obligation to use the event pattern. You could develop your own code to manage state however you choose if the framework's design isn't working for your requirements.

alon24 commented 3 years ago

Thanks I managed to push data, Now trying to get the buildl working. I am trying to offload the updates to a CarCommand class, where it would handle moving the car still not able to get things compiled :-(

Getting:

Indexing .pio/build/node32s/lib240/libframework.a
Linking .pio/build/node32s/firmware.elf
.pio/build/node32s/src/car/CarStateService.cpp.o:(.literal._ZN15CarStateServiceC2EP14AsyncWebServerP15SecurityManagerP15AsyncMqttClientP22CarMqttSettingsService+0x44): undefined reference to `CarCommand<CarState>::CarCommand(std::function<void (CarState&, ArduinoJson6172_91::ObjectRef&)>, std::function<StateUpdateResult (ArduinoJson6172_91::ObjectRef&, CarState&)>, int, int, int, int)'
.pio/build/node32s/src/car/CarStateService.cpp.o: In function `CarStateService::CarStateService(AsyncWebServer*, SecurityManager*, AsyncMqttClient*, CarMqttSettingsService*)':
/Users/iklein/.platformio/packages/toolchain-xtensa32/xtensa-esp32-elf/include/c++/5.2.0/functional:1754: undefined reference to `CarCommand<CarState>::CarCommand(std::function<void (CarState&, ArduinoJson6172_91::ObjectRef&)>, std::function<StateUpdateResult (ArduinoJson6172_91::ObjectRef&, CarState&)>, int, int, int, int)'
collect2: error: ld returned 1 exit status
*** [.pio/build/node32s/firmware.elf] Error 1

CarCommand.h

#ifndef CarCommand_h
#define CarCommand_h

#include <functional>
#include <Arduino.h>
#include <ArduinoJson.h>
#include <StatefulService.h>

// http://www.smartarduino.com/2wd-wifi-rc-smart-car-with-nodemcu-shield-for-esp-12e_p94572.html
// https://smartarduino.gitbooks.io/user-manual-for-wifi-car-by-nodemcu-doitcar-/content/31_code_for_ap_case_on_doitcar.html
//--GPIO Define
// 2     function initGPIO()
// 3     --1,2EN     D1 GPIO5
// 4     --3,4EN     D2 GPIO4
// 5     --1A  ~2A   D3 GPIO0
// 6     --3A  ~4A   D4 GPIO2

#ifdef ESP32
// PINS
#define BUTTON_PIN_INT 22
#define leftMotorPwmPin 4
#define rightMotorPwmPin 5
#define leftMotorDirPin 2
#define rightMotorDirPin 0
#elif defined(ESP8266)
// PINS
#define leftMotorPwmPin 4
#define rightMotorPwmPin 5
#define leftMotorDirPin 2
#define rightMotorDirPin 0
#endif

// Direcrtion
#define FW 0
#define BK 1
#define STOP 2
//#define FW_NEUTRAL 3

// Turn
#define TL 4
#define TR 5
#define STRAIGHT 6

// Motor movements
#define MOTOR_FW 1
#define MOTOR_BK 0

#define headLights 12
#define ULTRASONIC_TRIG_PIN 4
#define ULTRASONIC_ECHO_PIN 5

struct CarParamaters {
  int freq = 50;  // 30;
  int xConvertionRation = 10;
  bool useSteeringMotor = true;
};

template <class T>
class CarCommand {
 public:
  CarCommand(JsonStateReader<T> stateReader,
             JsonStateUpdater<T> stateUpdater,
             int leftMotorPWM,
             int rightMotorPWM,
             int leftMotorDir,
             int rightMotorDir);
  void initCommand();
  CarParamaters carParams;

 private:
  JsonStateReader<T> _stateReader;
  JsonStateUpdater<T> _stateUpdater;

  int lastY = 0;
  int lastX = 0;
  int currentX = 0;

  // Direction
  int dir = FW;

  // Turn Status
  int t = 0;
  int tdir = STRAIGHT;
  int tcount = 0;

  uint8_t leftMotorPWM;
  uint8_t rightMotorPWM;
  int leftMotorDir;
  int rightMotorDir;
};

#endif  // end CarCommand_h

CarCommnd.cpp:

#include "CarCommand.h"

template <class T>
CarCommand<T>::CarCommand(JsonStateReader<T> stateReader,
                          JsonStateUpdater<T> stateUpdater,
                          int leftMotorPWM,
                          int rightMotorPWM,
                          int leftMotorDir,
                          int rightMotorDir) {
  // //   this->leftMotorPWM = leftMotorPWM;
  // //   this->rightMotorPWM = rightMotorPWM;
  // //   this->leftMotorDir = leftMotorDir;
  // //   this->rightMotorDir = rightMotorDir

  // //       uint8_t pins[8] = {(uint8_t)leftMotorPWM,
  // //                          (uint8_t)rightMotorPWM};  // List of pins that you want to connect to pwm

  //   // pwmMotors = new HardwarePWM(pins, 2);
  //   // setFreq(carParams.freq);
  //   // debugf("CarCommand Instantiating");
}

CarStateService.h

class CarStateService : public StatefulService<CarState> {
 public:
  CarStateService(AsyncWebServer* server,
                    SecurityManager* securityManager,
                    AsyncMqttClient* mqttClient,
                    CarMqttSettingsService* carMqttSettingsService );
  void begin();
  void loop();

 private:
  HttpEndpoint<CarState> _httpEndpoint;
  MqttPubSub<CarState> _mqttPubSub;
  WebSocketTxRx<CarState> _webSocket;
  AsyncMqttClient* _mqttClient;
  CarMqttSettingsService* _carMqttSettingsService;
  CarCommand<CarState> _carCommand;

  void registerConfig();
  void onConfigUpdated();
};

CarStateService.cpp:

CarStateService::CarStateService(AsyncWebServer* server,
                                 SecurityManager* securityManager,
                                 AsyncMqttClient* mqttClient,
                                 CarMqttSettingsService* carMqttSettingsService) :
    _httpEndpoint(CarState::read,
                  CarState::update,
                  this,
                  server,
                  CAR_SETTINGS_ENDPOINT_PATH,
                  securityManager,
                  AuthenticationPredicates::IS_AUTHENTICATED),
    _mqttPubSub(CarState::haRead, CarState::haUpdate, this, mqttClient),
    _webSocket(CarState::read,
               CarState::update,
               this,
               server,
               CAR_SETTINGS_SOCKET_PATH,
               securityManager,
               AuthenticationPredicates::IS_AUTHENTICATED),
    _mqttClient(mqttClient),
    _carMqttSettingsService(carMqttSettingsService),
    _carCommand(CarState::read, CarState::update, 4, 5, 6, 7)
{
  // configure led to be output
  pinMode(LED_PIN, OUTPUT);

  // // configure button to be input
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(button1.PIN, INPUT_PULLDOWN);
rjwats commented 3 years ago

Have you managed to resolve this? Can you post up your CarState class?

alon24 commented 3 years ago

yes, now I am facing memory issues, I am trying to add: screen (ssd1306) - works websocket - I got the hang of it BUT I want to add BLE and it fails on memory issues

Archiving .pio/build/node32s/libFrameworkArduino.a
Indexing .pio/build/node32s/libFrameworkArduino.a
Linking .pio/build/node32s/firmware.elf
Retrieving maximum program size .pio/build/node32s/firmware.elf
Building .pio/build/node32s/firmware.bin
Checking size .pio/build/node32s/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [==        ]  21.9% (used 71756 bytes from 327680 bytes)
Flash: [==========]  143.Error: The program size (1880876 bytes) is greater than maximum allowed (1310720 bytes)
5% (used 1880876 bytes from 1310720 bytes)
*** [checkprogsize] Explicit exit, status 1
esptool.py v3.0
======================================================================================== [FAILED] Took 34.76 seconds ========================================================================================

Environment    Status    Duration
-------------  --------  ------------
node32s        FAILED    00:00:34.759
=====================================================================

Do I need to take MQTT out of the project to make room in mem? Or is something else boating me? I am serving the react page from SPIFFS (I sure hope I do)

So what am I doing wrong here (I think it started with 93% from the begining) ? Here is my code: https://github.com/alon24/esp8266-react/tree/cardev

alon24 commented 3 years ago

So how do I change my partitions sizes? Willing to not have OTA if needed (do not want, but if needed ...)

proddy commented 3 years ago

put it in flash (PROGMEM) not SPIFFS. The ESP32 has a lot so might as well use it.

alon24 commented 3 years ago

I though the correct course (so as to NOT make the size big, was to use spiffs, testing

So apparently I did not add 'board_build.partitions = min_spiffs.csv' when NOT in PROGMEM mode. So this thing added more mem, for me. and things compile

Can you explain why use PROGMEM - what is the differnece? Does PROGMEM mean everytime it loads, it loads more to the esp?

proddy commented 3 years ago

PROGMEM is a lot faster since its reading from Flash memory. Although if opt to use the File System I would switch from SPIFFS to LittleFS which will give 2x read speed. On my project which has a ton of code, with all strings in PROGMEM and quite some customized web pages it looks like:

RAM:   [==        ]  15.7% (used 51420 bytes from 327680 bytes)
Flash: [========  ]  83.3% (used 1637168 bytes from 1966080 bytes)
alon24 commented 3 years ago

How to move to littlefs? "with all strings in PROGMEM" - you mean the react code?

alon24 commented 3 years ago

Can u explain what is the benefit of spiffs/littlefs over progmem, or why choose each?

rjwats commented 3 years ago

Some details here: https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#spiffs-and-littlefs

I don't think support for littlefs has yet made it's way into an Arduino esp32 release, though it's been integrated for an upcoming release, see comment in README here:

https://github.com/lorol/LITTLEFS

As soon as it makes it's way into an Arduino release it will become the default for the framework.

IIRC @proddy has modified quite a few of the libraries this project depends on in order to move as many strings as possible out of RAM, which may or may not be required in your instance.

On the BLE front... It seems like BLE adds significantly to the artefact size and that running the stack is very memory hungry. This not from personal experience but from comments from another user who is using it extensively in their own project. They recommended https://github.com/h2zero/NimBLE-Arduino/ as a good starting point. Perhaps you're using this already?

justoke commented 3 years ago

I have used NimBLE - the recent release has greatly improved the flash memory footprint and the memory usage. Without it the default BLE libraries are just too big. Alternatively you can order 8MB ESP32 chips, although ready made modules with such versions are not easy to come by - probably need to make a custom PCB.

mjkl-gh commented 3 years ago

Alternatively you can order 8MB ESP32 chips, although ready made modules with such versions are not easy to come by Going off-topic but might be interesting:

Esp32 goes up to 16mb as in this

lilygo has a development board with 16mb https://nl.aliexpress.com/item/33048962331.html

But they have 2 variants (4mb and 16mb) so watch what you buy