misc/watchy-experimentation#4: Query Solar Status from InfluxDB



Issue Information

Issue Type: issue
Status: closed
Reported By: btasker
Assigned To: btasker

Created: 14-Jun-23 22:14



Description

In #3 I compiled in the Arduino InfluxDB Client in order to write watch originated stats onwards into InfluxDB.

I'd like to play with also pulling some basic information though.

As a start point, I think it'd be good to be able to add a simple indicator of battery status - Charging, Discharging or Idle.

Docs on querying with the client library are here



Issue Links

Toggle State Changes

Activity


assigned to @btasker

OK, so the client library uses Influx's V2 API for queries - the query will need to be written in Flux.

The value should (obviously) mirror the value shown on my solar dashboard so we probably want to apply the same logic:

Query:

SELECT 
   ( last("batteryDischargeRate")*-1 )*1000 + last("batteryChargeRate")*1000 AS "charge" 
FROM "Systemstats"."autogen"."solar_inverter" 
WHERE $timeFilter 

Value mappings: Screenshot_20230615_083949

The Flux query is a little more complex than the InfluxQL version, in part because it also rolls in the logic used for the value mappings

from(bucket: "Systemstats/autogen")
  |> range(start: -20m)
  |> filter(fn: (r) => r._measurement == "solar_inverter")
  |> filter(fn: (r) => r._field == "batteryDischargeRate" or r._field == "batteryChargeRate")
  |> keep(columns: ["_time", "_field", "_value"])
  |> last()
  |> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> map(fn: (r) => {
    // Discharge rate is negative, turn it into a positive number
    // and convert from amps to milliamps
    batteryDischargeMA = ((r.batteryDischargeRate * -1.0) * 1000.0)

    // Convert charge rate to milliamps
    batteryChargeMA = (r.batteryChargeRate * 1000.0)

    // Add them together
    netMa = batteryDischargeMA + batteryChargeMA

    // Calculate the charging state
    return {
      charge_state:  if netMa > 100 then
                "Charging" 
            else if netMa < -100 then 
                "Supplying"
            else 
                "Idle"
    }
  })

Technically that logic could be applied in the calling code instead, but then we're using CPU cycles (and battery) on the watch when the far end can do it just fine.

So, we've got a query to run.

That's only part of the challenge though - if we want to add an indicator to the watch face we need to

  1. Decide what form the indicator will take (icon, words etc)
  2. Where on the watchface to display it
  3. (likely) what to remove to make space to display it

Number 3's quite an important one - the 7Seg display is quite densely packed (at least, once you've done some steps).

One option - at least for proving the concept - might be to commandeer the areas used for weather information. It displays temperature and a sun/cloud/etc - that's all pulled from OpenWeatherMap so is approximately right. We could, technically, replace that with more specific information pulled from the Solar's information and my weather station.

I suppose, technically, if we really wanted to run InfluxQL we could just use http.get() and JSON.parse() (like the weather code does) from the HTTPClient to craft a request and decode the response.

verified

mentioned in commit sysconfigs/watchy-code@5c0828e2dc9a4585a1ffb2383cc6b18cd961fed0

Commit: sysconfigs/watchy-code@5c0828e2dc9a4585a1ffb2383cc6b18cd961fed0 
Author: B Tasker                            
                            
Date: 2023-06-15T14:49:06.000+01:00 

Message

Display proportion of locally supplied electricity (misc/watchy-experimentation#4)

This queries an InfluxDB instance (which may be different to the one that Watchy's stats are written to) to get and display what percentage of our power consumption was locally supplied (i.e. came from Solar or batteries)

Also adds a bitmap to be used as a percent symbol

+71 -3 (74 lines changed)

I decided to use InfluxQL after all - means the watch can target an InfluxDB instance of basically any version and the query can be performed without the processing overhead of any additional libraries.

The meat and bones of this is

void Watchy7SEG::getSolarState(){
    // Only do anything if the Wifi connects
    if (connectWiFi()) {
      HTTPClient http; 

      http.setConnectTimeout(3000); // 3 second max timeout, no point burning battery

      // Add the auth header (underlying http library requires user/pass)
      // so we can't set a Bearer header
      http.setAuthorization("any", INFLUXDB_READ_AUTH_HEADER);

      // the library doesn't urlencode for us, so we need to supply pre-encoded query strings
      String QueryURL = INFLUXDB_READ_URL + String("/query?db=") + INFLUXDB_READ_DB + String("&q=SELECT%20round%28last%28localSupplyPercToday%3A%3Ainteger%29%29%20as%20i%20FROM%20solar_inverter%20WHERE%20time%20%3E%20now%28%29%20-%2020m");

      // Place the request
      http.begin(QueryURL.c_str());
      int httpResponseCode = http.GET();

      //display.print(httpResponseCode);
      if (httpResponseCode == 200) {
        String payload             = http.getString();
        JSONVar responseObject     = JSON.parse(payload);

        // This needs a double caste - JSONVar can't go straight to string if the json
        // entry wasn't itself a string
        uint32_t supplyPerc = responseObject["results"][0]["series"][0]["values"][0][1];
        String suppliedString = String(supplyPerc);

        // The text will be right aligned, so work out where to start writing it
        int16_t  x1, y1;
        uint16_t w, h;
        display.getTextBounds(suppliedString, 0, 0, &x1, &y1, &w, &h);
        display.setCursor(159 - w - x1, 150);

        // Print the value
        display.println(suppliedString);

        // Draw a percent symbol
        display.drawBitmap(165, 130, percent, 26, 20, DARKMODE ? GxEPD_WHITE : GxEPD_BLACK);
      }      
      http.end();
      WiFi.mode(WIFI_OFF);
      btStop();
    }
}

The bitmap for the percent symbol was created with Gimp and then converted with image2cpp

Currently, it won't cache the result - so if I wander out of wifi range the value should (I assume) disappear. Might be good to look at having it cache.

signal-2023-06-15-145625_002_circle

To make space, I sacrificed the temperature display.

It occurred to me though, that it should be possible to have the two alternate: if the minute is odd display this, if it's even, display the temperature.

verified

mentioned in commit sysconfigs/watchy-code@eeb882597b9b3a6adc4391e27ff36917db190883

Commit: sysconfigs/watchy-code@eeb882597b9b3a6adc4391e27ff36917db190883 
Author: B Tasker                            
                            
Date: 2023-06-15T17:18:23.000+01:00 

Message

Make display alternate between temperature and solar efficiency misc/watchy-experimentation#4

If the minute is an odd number, then during repaint we display the temperature reported by the weather service.

If it's even, we display the solar efficiency figure

+19 -16 (35 lines changed)

mentioned in issue #5

marked this issue as related to #5

I'm going to mark this as done - there's a little bit of follow up to do, but I've raised #5 for that as it affects stuff done in other issues too.