letscontrolit / ESPEasy

Easy MultiSensor device based on ESP8266/ESP32
http://www.espeasy.com
Other
3.28k stars 2.22k forks source link

[New plugin - P112] I2C AS7265X Triad Spectroscopy Sensor (18 channels) #3557

Closed heinemannj closed 1 year ago

heinemannj commented 3 years ago

I'm working on a new plugin for an 18 channel sensor:

// #######################################################################################################
// #################### Plugin 112 I2C AS7265X Triad Spectroscopy Sensor and White, IR and UV LED ########
// #######################################################################################################
//
// Triad Spectroscopy Sensor and White, IR and UV LED
// like this one: https://www.sparkfun.com/products/15050
// based on this library: https://github.com/sparkfun/SparkFun_AS7265x_Arduino_Library
// this code is based on 29 Mar 2019-03-29 version of the above library
//
// 2021-03-23 heinemannj: Initial commit
//

#include "src/PluginStructs/P112_data_struct.h"

#define PLUGIN_112
#define PLUGIN_ID_112         112
#define PLUGIN_NAME_112       "Color - AS7265X [DEVELOPMENT]"
#define PLUGIN_VALUENAME1_112 "410"
#define PLUGIN_VALUENAME2_112 "435"
#define PLUGIN_VALUENAME3_112 "460"
#define PLUGIN_VALUENAME4_112 "485"
#define PLUGIN_VALUENAME5_112 "510"
#define PLUGIN_VALUENAME6_112 "535"
#define PLUGIN_VALUENAME7_112 "560"
#define PLUGIN_VALUENAME8_112 "585"
#define PLUGIN_VALUENAME9_112 "610"
#define PLUGIN_VALUENAME10_112 "645"
#define PLUGIN_VALUENAME11_112 "680"
#define PLUGIN_VALUENAME12_112 "705"
#define PLUGIN_VALUENAME13_112 "730"
#define PLUGIN_VALUENAME14_112 "760"
#define PLUGIN_VALUENAME15_112 "810"
#define PLUGIN_VALUENAME16_112 "860"
#define PLUGIN_VALUENAME17_112 "900"
#define PLUGIN_VALUENAME18_112 "940"

#define AS7265X_ADDR 0x49
    case PLUGIN_DEVICE_ADD:
    {
      Device[++deviceCount].Number           = PLUGIN_ID_112;
      Device[deviceCount].Type               = DEVICE_TYPE_I2C;
      Device[deviceCount].VType              = Sensor_VType::SENSOR_TYPE_QUAD;
      Device[deviceCount].Ports              = 0;
      Device[deviceCount].ValueCount         = 18;
      Device[deviceCount].PullUpOption       = false;
      Device[deviceCount].InverseLogicOption = false;
      Device[deviceCount].FormulaOption      = false;
      Device[deviceCount].SendDataOption     = true;
      Device[deviceCount].GlobalSyncOption   = true;
      Device[deviceCount].TimerOption        = true;
      break;
    }

    case PLUGIN_GET_DEVICENAME:
    {
      string = F(PLUGIN_NAME_112);
      break;
    }

    case PLUGIN_GET_DEVICEVALUENAMES:
    {
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[4], PSTR(PLUGIN_VALUENAME5_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[5], PSTR(PLUGIN_VALUENAME6_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[6], PSTR(PLUGIN_VALUENAME7_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[7], PSTR(PLUGIN_VALUENAME8_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[8], PSTR(PLUGIN_VALUENAME9_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[9], PSTR(PLUGIN_VALUENAME10_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[10], PSTR(PLUGIN_VALUENAME11_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[11], PSTR(PLUGIN_VALUENAME12_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[12], PSTR(PLUGIN_VALUENAME13_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[13], PSTR(PLUGIN_VALUENAME14_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[14], PSTR(PLUGIN_VALUENAME15_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[15], PSTR(PLUGIN_VALUENAME16_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[16], PSTR(PLUGIN_VALUENAME17_112));
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[17], PSTR(PLUGIN_VALUENAME18_112));
      break;
    }
    case PLUGIN_READ:
    {
      P112_data_struct *P112_data =
        static_cast<P112_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (P112_data->begin()) {
        if (PCONFIG(3)) // Integrated LEDs?
        {
          P112_data->sensor.takeMeasurementsWithBulb();
        } else {
          P112_data->sensor.takeMeasurements();
        }

        addLog(LOG_LEVEL_DEBUG, F("Found AS7265X sensor"));

        if (PCONFIG(3)) // Calibrated Measurements?
        {
          UserVar[event->BaseVarIndex + 0] = P112_data->sensor.getCalibratedA();
          UserVar[event->BaseVarIndex + 1] = P112_data->sensor.getCalibratedB();
          UserVar[event->BaseVarIndex + 2] = P112_data->sensor.getCalibratedC();
          UserVar[event->BaseVarIndex + 3] = P112_data->sensor.getCalibratedD();
          UserVar[event->BaseVarIndex + 4] = P112_data->sensor.getCalibratedE();
          UserVar[event->BaseVarIndex + 5] = P112_data->sensor.getCalibratedF();
          UserVar[event->BaseVarIndex + 6] = P112_data->sensor.getCalibratedG();
          UserVar[event->BaseVarIndex + 7] = P112_data->sensor.getCalibratedH();
          UserVar[event->BaseVarIndex + 8] = P112_data->sensor.getCalibratedR();
          UserVar[event->BaseVarIndex + 9] = P112_data->sensor.getCalibratedI();
          UserVar[event->BaseVarIndex + 10] = P112_data->sensor.getCalibratedS();
          UserVar[event->BaseVarIndex + 11] = P112_data->sensor.getCalibratedJ();
          UserVar[event->BaseVarIndex + 12] = P112_data->sensor.getCalibratedT();
          UserVar[event->BaseVarIndex + 13] = P112_data->sensor.getCalibratedU();
          UserVar[event->BaseVarIndex + 14] = P112_data->sensor.getCalibratedV();
          UserVar[event->BaseVarIndex + 15] = P112_data->sensor.getCalibratedW();
          UserVar[event->BaseVarIndex + 16] = P112_data->sensor.getCalibratedK();
          UserVar[event->BaseVarIndex + 17] = P112_data->sensor.getCalibratedL();
        } else {
          UserVar[event->BaseVarIndex + 0] = P112_data->sensor.getA();
          UserVar[event->BaseVarIndex + 1] = P112_data->sensor.getB();
          UserVar[event->BaseVarIndex + 2] = P112_data->sensor.getC();
          UserVar[event->BaseVarIndex + 3] = P112_data->sensor.getD();
          UserVar[event->BaseVarIndex + 4] = P112_data->sensor.getE();
          UserVar[event->BaseVarIndex + 5] = P112_data->sensor.getF();
          UserVar[event->BaseVarIndex + 6] = P112_data->sensor.getG();
          UserVar[event->BaseVarIndex + 7] = P112_data->sensor.getH();
          UserVar[event->BaseVarIndex + 8] = P112_data->sensor.getR();
          UserVar[event->BaseVarIndex + 9] = P112_data->sensor.getI();
          UserVar[event->BaseVarIndex + 10] = P112_data->sensor.getS();
          UserVar[event->BaseVarIndex + 11] = P112_data->sensor.getJ();
          UserVar[event->BaseVarIndex + 12] = P112_data->sensor.getT();
          UserVar[event->BaseVarIndex + 13] = P112_data->sensor.getU();
          UserVar[event->BaseVarIndex + 14] = P112_data->sensor.getV();
          UserVar[event->BaseVarIndex + 15] = P112_data->sensor.getW();
          UserVar[event->BaseVarIndex + 16] = P112_data->sensor.getK();
          UserVar[event->BaseVarIndex + 17] = P112_data->sensor.getL();
        }
      }

      success = true;
      break;
    }

The sensor reading are properly captured and also visible in Device vie and in json output. But the reading names are not properly stored:

image

Custom build based on Commits from Mar 23, 2021

Is there any peace of code which needs to be fine tuned in addition to above ? Any help is appreciated.

TD-er commented 3 years ago

Please have a look at the sysinfo plugin on how to implement the selection of a subset of output values. ESPEasy only allows upto 4 task values, and you're using 18. So I'm a bit surprised things don't crash as you are running out of range on some objects.

If you want to use all values and share the same device, you may also have a look at the Eastron plugin.

heinemannj commented 3 years ago

First of all: I'm able to setup a plugin with the really annoying limitation of 4 output values

My workarround - obviously a shot from behind through the chest in the eye:

1) Generate events for all required 18 sensor readings:

    case PLUGIN_READ:
    {
      P112_data_struct *P112_data =
        static_cast<P112_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (P112_data->begin()) {
        if (PCONFIG(3)) // Integrated LEDs?
        {
          P112_data->sensor.takeMeasurementsWithBulb();
        } else {
          P112_data->sensor.takeMeasurements();
        }

        String RuleEvent;
        RuleEvent  = getTaskDeviceName(event->TaskIndex);
        RuleEvent += '#';

        if (PCONFIG(3)) // Calibrated Measurements?
        {
          eventQueue.add(RuleEvent + "410=" + P112_data->sensor.getCalibratedA());
          eventQueue.add(RuleEvent + "435=" + P112_data->sensor.getCalibratedB());
          eventQueue.add(RuleEvent + "460=" + P112_data->sensor.getCalibratedC());

          eventQueue.add(RuleEvent + "485=" + P112_data->sensor.getCalibratedD());
          eventQueue.add(RuleEvent + "510=" + P112_data->sensor.getCalibratedE());
          eventQueue.add(RuleEvent + "535=" + P112_data->sensor.getCalibratedF());

          eventQueue.add(RuleEvent + "560=" + P112_data->sensor.getCalibratedG());
          eventQueue.add(RuleEvent + "585=" + P112_data->sensor.getCalibratedH());
          eventQueue.add(RuleEvent + "610=" + P112_data->sensor.getCalibratedR());

          eventQueue.add(RuleEvent + "645=" + P112_data->sensor.getCalibratedI());
          eventQueue.add(RuleEvent + "680=" + P112_data->sensor.getCalibratedS());
          eventQueue.add(RuleEvent + "705=" + P112_data->sensor.getCalibratedJ());

          eventQueue.add(RuleEvent + "730=" + P112_data->sensor.getCalibratedT());
          eventQueue.add(RuleEvent + "760=" + P112_data->sensor.getCalibratedU());
          eventQueue.add(RuleEvent + "810=" + P112_data->sensor.getCalibratedV());

          eventQueue.add(RuleEvent + "860=" + P112_data->sensor.getCalibratedW());
          eventQueue.add(RuleEvent + "900=" + P112_data->sensor.getCalibratedK());
          eventQueue.add(RuleEvent + "940=" + P112_data->sensor.getCalibratedL());

          UserVar[event->BaseVarIndex + 0] = P112_data->sensor.getCalibratedC();
          UserVar[event->BaseVarIndex + 1] = P112_data->sensor.getCalibratedF();
          UserVar[event->BaseVarIndex + 2] = P112_data->sensor.getCalibratedR();
          UserVar[event->BaseVarIndex + 3] = P112_data->sensor.getCalibratedW();
        } else {
          eventQueue.add(RuleEvent + "410=" + P112_data->sensor.getA());
          eventQueue.add(RuleEvent + "435=" + P112_data->sensor.getB());
          eventQueue.add(RuleEvent + "460=" + P112_data->sensor.getC());

          eventQueue.add(RuleEvent + "485=" + P112_data->sensor.getD());
          eventQueue.add(RuleEvent + "510=" + P112_data->sensor.getE());
          eventQueue.add(RuleEvent + "535=" + P112_data->sensor.getF());

          eventQueue.add(RuleEvent + "560=" + P112_data->sensor.getG());
          eventQueue.add(RuleEvent + "585=" + P112_data->sensor.getH());
          eventQueue.add(RuleEvent + "610=" + P112_data->sensor.getR());

          eventQueue.add(RuleEvent + "645=" + P112_data->sensor.getI());
          eventQueue.add(RuleEvent + "680=" + P112_data->sensor.getS());
          eventQueue.add(RuleEvent + "705=" + P112_data->sensor.getJ());

          eventQueue.add(RuleEvent + "730=" + P112_data->sensor.getT());
          eventQueue.add(RuleEvent + "760=" + P112_data->sensor.getU());
          eventQueue.add(RuleEvent + "810=" + P112_data->sensor.getV());

          eventQueue.add(RuleEvent + "860=" + P112_data->sensor.getW());
          eventQueue.add(RuleEvent + "900=" + P112_data->sensor.getK());
          eventQueue.add(RuleEvent + "940=" + P112_data->sensor.getL());

          UserVar[event->BaseVarIndex + 0] = P112_data->sensor.getC();
          UserVar[event->BaseVarIndex + 1] = P112_data->sensor.getF();
          UserVar[event->BaseVarIndex + 2] = P112_data->sensor.getR();
          UserVar[event->BaseVarIndex + 3] = P112_data->sensor.getW();
        }
      }
      success = true;
      break;
    }

2) Setup 6 dummy devices to store all required 18 sensor readings (6 x 3 = 18) 3) Setup 18 rules to feed the dummy device readings

On System#Boot do
  Let,10,0
  Let,11,0
  Let,80,16                        // Sample switch
  Let,90,12                        // LED
  GPIO,[INT#80],0            // Sample switch
  Pulse,[INT#90],1,1000   // LED
endon

on ResetSample do
  TaskValueSet,%eventvalue1%,1,0
  TaskValueSet,%eventvalue1%,2,0
  TaskValueSet,%eventvalue1%,3,0
  TaskValueSet,%eventvalue1%,4,0
endon

on UpdateSample do
  TaskValueSet,%eventvalue1%,%eventvalue2%,%eventvalue3%
  Let,11,[INT#11]+1                          // Reading counter
  if [INT#11]=18
    GPIO,[INT#80],0                           // Turn sample switch off
    GPIO,[INT#90],0                           // Turn LED off
    Let,10,0
  endif
endon

on SampleSwitch#State=1 do         // Sample switch active
  GPIO,[INT#90],1                             // Turn LED on
  asyncevent,ResetSample=2           // Reset dummy device readings
  asyncevent,ResetSample=3
  asyncevent,ResetSample=4
  asyncevent,ResetSample=5
  asyncevent,ResetSample=6
  asyncevent,ResetSample=7
  Let,10,1                                          // Sample flag active
  Let,11,0                                          // Reading counter
  TaskRun,1                                      // Start sample
endon

on AS7265X#410 do
  if [INT#10]=1 and [Spectrum_1#410]=0
    asyncevent,UpdateSample=2,1,%eventvalue1%
  endif
endon

on AS7265X#435 do
  if [INT#10]=1 and [Spectrum_1#435]=0
    asyncevent,UpdateSample=2,2,%eventvalue1%
  endif
endon

on AS7265X#460 do
  if [INT#10]=1 and [Spectrum_1#460]=0
    asyncevent,UpdateSample=2,3,%eventvalue1%
  endif
endon

on AS7265X#485 do
  if [INT#10]=1 and [Spectrum_2#485]=0
    asyncevent,UpdateSample=3,1,%eventvalue1%
  endif
endon

on AS7265X#510 do
  if [INT#10]=1 and [Spectrum_2#510]=0
    asyncevent,UpdateSample=3,2,%eventvalue1%
  endif
endon

on AS7265X#535 do
  if [INT#10]=1 and [Spectrum_2#535]=0
    asyncevent,UpdateSample=3,3,%eventvalue1%
  endif
endon

on AS7265X#560 do
  if [INT#10]=1 and [Spectrum_3#560]=0
    asyncevent,UpdateSample=4,1,%eventvalue1%
  endif
endon

on AS7265X#585 do
  if [INT#10]=1 and [Spectrum_3#585]=0
    asyncevent,UpdateSample=4,2,%eventvalue1%
  endif
endon

on AS7265X#610 do
  if [INT#10]=1 and [Spectrum_3#610]=0
    asyncevent,UpdateSample=4,3,%eventvalue1%
  endif
endon

on AS7265X#645 do
  if [INT#10]=1 and [Spectrum_4#645]=0
    asyncevent,UpdateSample=5,1,%eventvalue1%
  endif
endon

on AS7265X#680 do
  if [INT#10]=1 and [Spectrum_4#680]=0
    asyncevent,UpdateSample=5,2,%eventvalue1%
  endif
endon

on AS7265X#705 do
  if [INT#10]=1 and [Spectrum_4#705]=0
    asyncevent,UpdateSample=5,3,%eventvalue1%
  endif
endon

on AS7265X#730 do
  if [INT#10]=1 and [Spectrum_5#730]=0
    asyncevent,UpdateSample=6,1,%eventvalue1%
  endif
endon

on AS7265X#760 do
  if [INT#10]=1 and [Spectrum_5#760]=0
    asyncevent,UpdateSample=6,2,%eventvalue1%
  endif
endon

on AS7265X#810 do
  if [INT#10]=1 and [Spectrum_5#810]=0
    asyncevent,UpdateSample=6,3,%eventvalue1%
  endif
endon

on AS7265X#860 do
  if [INT#10]=1 and [Spectrum_6#860]=0
    asyncevent,UpdateSample=7,1,%eventvalue1%
  endif
endon

on AS7265X#900 do
  if [INT#10]=1 and [Spectrum_6#900]=0
    asyncevent,UpdateSample=7,2,%eventvalue1%
  endif
endon

on AS7265X#940 do
  if [INT#10]=1 and [Spectrum_6#940]=0
    asyncevent,UpdateSample=7,3,%eventvalue1%
  endif
endon

Result:

image

If there is any way to simplify the plugin and the rules code please let me know.

With the help of this sensor you can perform simple but accurate spectral analysis over the range of visible and near infrared light spectrum without the need of really expensive spectrometers (> 4000 €)!

Further improvement with the help of UV sensors (e.g. VEML6070, VEML6075, ...) is possible. If you want I can make a pull request.

TD-er commented 3 years ago

Why not doint the same as with the Eastron plugin? That plugin does use a single object to communicate and collect the data. The separate tasks then just pick their values from that single object and thus act per extra task as a very simple instance of the plugin.

Handling it via rules like this is using quite a lot of resources.

heinemannj commented 3 years ago

I've prepared a pull request. I've reviewed the Eastron plugin but with my limited coding experience I was not able to implement.

In addition this plugin is special: there are 3 different sensors synchronized by the main one One measurement over all 18 channels takes a couple of seconds - Initiating a measurement should only done by one single plugin instance.

Please feel free to improve - I will test and report

TD-er commented 3 years ago

Hmm can you also show a screenshot of the timing stats page running this plugin? "A couple of seconds" is a big red flag for me.

heinemannj commented 3 years ago

image

But potentially there is a solution implemented within the sparkfun lib to avoid waiting till sensor readings are finished. But again my coding experiences are limited.

TD-er commented 3 years ago

I've done something similar to the Dallas DS18b20 and BME280 (and some more) to actually split the reading process into several stages and make sure the task is rescheduled to perform a PLUGIN_READ to actually delilver the values.

Close to 2 seconds blocking is not usable as it will literally block all other things running on the ESP.

tonhuisman commented 1 year ago

This issue is no longer valid, as several alternative solutions are available. Can be closed.