Nice(?) and cheap ESP32+Colour display

You can use the time directly on the screen -

HINT: It's done with the template property e.g. - template: "%H:%M"

Where is that documented in OpenHASP? I can't find anything. Ah, wait, I see - it isn't yet documented: Use the ntp to show time locally on screen (solved) Ā· HASwitchPlate/openHASP Ā· Discussion #595 Ā· GitHub

@TotallyInformation How are you getting on :wink:

I didn't think this would be needed, but now I am playing with outputs on the hasp devices, it seems the output state doesn't survive a restart, so I may need to replay at least some commands.

I could imagine that you will need to filter some commands as sending a restart, which then gets resent again after the restart will likely end up in a boot loop ?

Would you mind sharing these two bits so I can tweak and add them to the flow ?

Sadly, life getting in the way :frowning: But it is sat propped up on my desk glowing a nice red colour that matches my office LED mood lighting. :face_with_spiral_eyes:

Yup.

I figure that I certainly need to filter idle commands. I'll set up a filter array so I can adjust things as needed. I'm only caching commands and not the config stuff

I'm just using the web page for that for now. Once things stabilise, I shouldn't really need to tell it to restart. :crossed_fingers:

[{"id":"3cac07a6e54e3633","type":"inject","z":"2d13f0a223a9f12b","name":"hrly 5am-midnight","props":[],"repeat":"","crontab":"0 5-23 * * *","once":true,"onceDelay":0.1,"topic":"","x":170,"y":140,"wires":[["52ef6aa9e036fa07"]]},{"id":"52ef6aa9e036fa07","type":"change","z":"2d13f0a223a9f12b","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"excludeParameterMetadata\":false,\"includeLocationName\":true,\"latitude\":53.3891423,\"longitude\":-1.4999209} ","tot":"json"},{"t":"set","p":"headers","pt":"msg","to":"{\"accept\":\"application/json\",\"apikey\": \"APIKEYNEEDEDHERE\"}","tot":"json"},{"t":"set","p":"url","pt":"msg","to":"https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/hourly?dataSource=BD1","tot":"str"},{"t":"set","p":"method","pt":"msg","to":"GET","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":295,"y":140,"wires":[["f9bee04cda61d53c"]],"l":false,"info":"[https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api](https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)"},{"id":"f9bee04cda61d53c","type":"http request","z":"2d13f0a223a9f12b","name":"API call","method":"use","ret":"obj","paytoqs":"query","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[],"x":440,"y":140,"wires":[["d7d704393070ed9b"]]},{"id":"d7d704393070ed9b","type":"function","z":"2d13f0a223a9f12b","name":"Met Office Example","func":"/** Interpret & enhance the UK Met Office \"Site Specific\" forecast data\n * Save to global `metOfficeWeather` variable and output to MQTT.\n * \n * Note that the free API license only allows 360 calls per day so we only\n * run this from 5am to midnight hourly.\n */\n\n// Save a copy of the API so we don't have to keep re-running for texting\nflow.set('metofficeapiout', msg.payload)\n\nconst weatherData = msg.payload.features[0].properties.timeSeries\n\nconst metIcons = context.get('metIcons') // See On Start for creation of these\nconst metOfficeWeather = global.get('metOfficeWeather', 'file') ?? {}\n\nconst currentTime = new Date()\nconst currentMillis = currentTime.getTime()\n\nlet closestTimeIndex = 0\nlet closestTimeDifference = Infinity\n\n// Enhance and normalise data\nfor (let i = 0; i < weatherData.length; i++) {\n  const dataTime = new Date(weatherData[i].time)\n  const dataMillis = dataTime.getTime()\n  const timeDifference = Math.abs(currentMillis - dataMillis)\n\n  // More sensible names\n  weatherData[i].temperature = weatherData[i].screenTemperature\n  weatherData[i].humidity = weatherData[i].screenRelativeHumidity\n\n  // Icon and description for OpenHASP\n  const icon = metIcons[weatherData[i].significantWeatherCode]\n  weatherData[i].icon = icon.icon\n  weatherData[i].description = icon.text\n  \n  // Is the barometer rising or falling?\n  if (i < (weatherData.length - 1)) {\n    if (weatherData[i].mslp < weatherData[i + 1].mslp) weatherData[i].barometerChange = 'rising'\n    else if (weatherData[i].mslp > weatherData[i + 1].mslp) weatherData[i].barometerChange = 'falling'\n    else weatherData[i].barometerChange = 'steady'\n  } else {\n    weatherData[i].barometerChange = 'NA'\n  }\n\n  // Work out which entry is closest to now so we can record \"current\" forecast\n  if (timeDifference < closestTimeDifference) {\n    closestTimeDifference = timeDifference;\n    closestTimeIndex = i;\n  }\n}\n\nmetOfficeWeather.forecast = weatherData\n// What does the data mean?\nmetOfficeWeather.schema = msg.payload.parameters[0]\n// Where is the forecast actually for and how far away is that?\nmetOfficeWeather.location = {\n  place: msg.payload.features[0].properties.location.name,\n  distance: msg.payload.features[0].properties.requestPointDistance,\n  coordinates: msg.payload.features[0].geometry.coordinates,\n}\n// When was the forecast updated?\nmetOfficeWeather.modelRunDate = msg.payload.features[0].properties.modelRunDate\n\nglobal.set('metOfficeWeather', metOfficeWeather, 'file')\n\n// Splits the data into MQTT topics and outputs to port #2\ndoMqttOut('INFO/weather/current', weatherData[closestTimeIndex] )\ndoMqttOut('INFO/weather/forecast', makeObject(weatherData, 'time') )\n\n// Output the current forecast on port #1\nreturn {\n  topic: 'Met Office Current Weather',\n  payload: weatherData[closestTimeIndex]\n}\n\n// Split input & output MQTT topic/value pairs to port #2\nfunction doMqttOut(rootTopic, val) {\n    if ( val!== null && val.constructor.name === 'Object' ) {\n        // Still an object so recurse to next level\n        Object.keys(val).forEach( key => {\n            doMqttOut(`${rootTopic}/${key}`, RED.util.getObjectProperty(val, key) )\n        })\n    } else {\n        const msg1 = {}\n        msg1.topic   = rootTopic\n        msg1.payload = val\n        node.send([null,msg1])\n    }\n}\n\n// Convert an array of objects into an object of objects\nfunction makeObject(myarray, key) {\n  const out = {}\n  myarray.forEach( obj => {\n    out[obj[key]] = obj\n  })\n  return out\n}\n/*\nhttps://datahub.metoffice.gov.uk/docs/f/category/site-specific/overview\n{\n  \"time\":\"2024-04-14T15:00Z\",\n  \"screenTemperature\":11.35,\n  \"maxScreenAirTemp\":11.81,\n  \"minScreenAirTemp\":11,\n  \"screenDewPointTemperature\":1.26,\n  \"feelsLikeTemperature\":8.61,\n  \"windSpeed10m\":6.03,\n  \"windDirectionFrom10m\":252,\n  \"windGustSpeed10m\":10.36,\n  \"max10mWindGust\":10.36,\n  \"visibility\":28666,\n  \"screenRelativeHumidity\":49.3,\n  \"mslp\":101913,\"uvIndex\":2,\n  \"significantWeatherCode\":3,\n  \"precipitationRate\":0,\n  \"totalPrecipAmount\":0,\n  \"totalSnowAmount\":0,\n  \"probOfPrecipitation\":2\n}\n*/","outputs":2,"timeout":0,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n\ncontext.set('metIcons', {\n    0: { \"icon\": \"\\uE594\", \"text\": \"Clear\" },\n    1: { \"icon\": \"\\uE5A8\", \"text\": \"Sunny\" },\n    2: { \"icon\": \"\\uE590\", \"text\": \"Partly cloudy\" },\n    3: { \"icon\": \"\\uE590\", \"text\": \"Partly cloudy\" },\n\n    5: { \"icon\": \"\\uE591\", \"text\": \"Mist\" },\n    6: { \"icon\": \"\\uE591\", \"text\": \"Fog\" },\n\n    7: { \"icon\": \"\\uE590\", \"text\": \"Cloudy\" },\n\n    8: { \"icon\": \"\\uE590\", \"text\": \"Overcast\" },\n\n    9: { \"icon\": \"\\uE597\", \"text\": \"Light rain shower\" },\n    13: { \"icon\": \"\\uE596\", \"text\": \"Heavy rain shower\" },\n\n    10: { \"icon\": \"\\uE597\", \"text\": \"Light rain shower\" },\n    14: { \"icon\": \"\\uE596\", \"text\": \"Heavy rain shower\" },\n\n    11: { \"icon\": \"\\uE597\", \"text\": \"Drizzle\" },\n    12: { \"icon\": \"\\uE597\", \"text\": \"Light rain\" },\n\n    15: { \"icon\": \"\\uE596\", \"text\": \"Heavy rain\" },\n\n    16: { \"icon\": \"\\uE67F\", \"text\": \"Sleet shower\" },\n    17: { \"icon\": \"\\uE67F\", \"text\": \"Sleet shower\" },\n    18: { \"icon\": \"\\uE67F\", \"text\": \"Sleet\" },\n    19: { \"icon\": \"\\uE592\", \"text\": \"Hail shower\" },\n    20: { \"icon\": \"\\uE592\", \"text\": \"Hail shower\" },\n    21: { \"icon\": \"\\uE592\", \"text\": \"Hail\" },\n\n    22: { \"icon\": \"\\uE598\", \"text\": \"Light snow shower\" },\n    23: { \"icon\": \"\\uE598\", \"text\": \"Light snow shower\" },\n    24: { \"icon\": \"\\uE598\", \"text\": \"Light snow\" },\n    25: { \"icon\": \"\\uE598\", \"text\": \"Heavy snow shower\" },\n    26: { \"icon\": \"\\uE598\", \"text\": \"Heavy snow shower\" },\n    27: { \"icon\": \"\\uE598\", \"text\": \"Heavy snow\" },\n\n    28: { \"icon\": \"\\uE593\", \"text\": \"Thunder shower\" },\n    29: { \"icon\": \"\\uE593\", \"text\": \"Thunder shower\" },\n    30: { \"icon\": \"\\uE593\", \"text\": \"Thunder\" }\n})\n","finalize":"","libs":[],"x":630,"y":140,"wires":[["f2f4189482bd50c9"],["ac786b4daf224ac3"]],"info":"const weatherData = msg.payload.features[0].properties.timeSeries\r\n\r\nconst currentTime = new Date();\r\nconst currentMillis = currentTime.getTime();\r\n\r\nlet closestTimeIndex = 0;\r\nlet closestTimeDifference = Infinity;\r\n\r\nfor (let i = 0; i < weatherData.length; i++) {\r\n    const dataTime = new Date(weatherData[i].time);\r\n    const dataMillis = dataTime.getTime();\r\n    const timeDifference = Math.abs(currentMillis - dataMillis);\r\n\r\n    if (timeDifference < closestTimeDifference) {\r\n        closestTimeDifference = timeDifference;\r\n        closestTimeIndex = i;\r\n        \r\n    }\r\n    node.warn(closestTimeIndex +\" \" +timeDifference);\r\n}\r\n\r\n// Use node.warn to print warnings in the debug sidebar\r\nnode.warn(\"Index of closest time: \" + closestTimeIndex);\r\nnode.warn(\"Closest time: \" + weatherData[closestTimeIndex].time);\r\nreturn msg"},{"id":"f2f4189482bd50c9","type":"debug","z":"2d13f0a223a9f12b","name":"debug 425","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":930,"y":100,"wires":[]},{"id":"7046321cb2fba395","type":"inject","z":"2d13f0a223a9f12b","name":"replay api","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"replay api","payload":"metofficeapiout","payloadType":"flow","x":420,"y":200,"wires":[["d7d704393070ed9b"]]},{"id":"ac786b4daf224ac3","type":"mqtt out","z":"2d13f0a223a9f12b","name":"Retained","topic":"","qos":"0","retain":"true","respTopic":"","contentType":"","userProps":"{\"flow\":\"weather\"}","correl":"","expiry":"","broker":"cc1c989613865d14","x":920,"y":160,"wires":[]},{"id":"cc1c989613865d14","type":"mqtt-broker","name":"home (remote, v5)","broker":"MQTTSERVER","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"5","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"services/nrdev","birthQos":"0","birthRetain":"false","birthPayload":"Online","birthMsg":{"userProps":"{\"source\":\"Node-RED Dev\"}","respTopic":""},"closeTopic":"services/nrdev","closeQos":"0","closeRetain":"false","closePayload":"Offline - disconnected","closeMsg":{"userProps":"{\"source\":\"Node-RED Dev\"}","respTopic":""},"willTopic":"services/nrdev","willQos":"0","willRetain":"false","willPayload":"Offline - unexpected","willMsg":{"userProps":"{\"source\":\"Node-RED Dev\"}","respTopic":""},"userProps":"{\"source\":\"Node-RED Dev\"}","sessionExpiry":""}]

You'll need your API key of course and to change the server name/ip for the MQTT server.

So just setting the output commands to retained seems to be all that I need to restore the gpio outputs after a restart.

1 Like

Except if you issue brightness or idle commands which you probably wouldn't want to be retained.

The idle message comes from the hasp device, after the pre-set time, so I don't initiate those. I react to them by changing brightness and pages etc.

But yes I'm only retaining literally the "output" commands from NR. So I can treat any onboard relays etc as I would a Tasmota type device now. I switch them via buttons on the screen layout, and update button state to match the relays, based on feedback from the screen. That button state is cached by me, but the relay states get resent via MQTT on a restart.

Also incorporated your modification to the weather, with hourly updates to the API and 10 min updates to the displayed data. a restart just triggers a refresh from the saved data so no need to cache that.

I've been experimenting, partly because I've created cmd scripts for each event type (including the seeming undocumented mqtt_on.cmd?). So I've been testing running the cmd's from Node-RED as well.

I tend not to have local relays - I don't trust myself to handle mains voltages via my cheap Chinese boards :slight_smile: I typically have sensors only whether environment sensors or touch switches and similar.

For mains relays I use mostly Zigbee like the Ikea stuff or some Shelly. I've also got some legacy 433MHz devices. So mains switch and lighting I would want Node-RED to coordinate external to the CYD.

Yes, nice. And it means you can reuse the same data for a web page or other integrations if you want to, all without having to blow your free tier API call limits.

Well I can feel a hasp weather page coming on now then :wink:

Yup, that's on my plan as well - be interested to see yours.

A quick mock-up to see how much data can be on the screen, and still be readable.

Will be tricky to show much on a small screen I think.

image

Yes, but I'd use my phone for details. I would likely want a summary of today and tomorrow just to get a quick overview.

Slight tweak to the firmware, to increase options payload for btnmatrix and I can get this -

image

Should suffice until there is a chart widget :wink:

1 Like

If you could make the CYD show anything at all (which is what I was hoping for!), what would the ideal weather display be like?

The Mac weather widget takes a lot of beating I think
image

yr.no meteogram - pretty good too but harder to read.

This is my Node-red template which needs a rebuild.
image

1 Like

That last one is probably best for a CYD with no psram.

Indeed the last option is basically the table I posted above.

You could show the YR.no one on a screen with 800X480 like the not so cheap yellow display -
https://www.aliexpress.com/item/1005004952726089.html

@TotallyInformation did you make any progress :wink:

I have been away, so just looking at this again the met API doesn't really have much summary info.
There is a nice text forecast in the old API but cannot find an equivalent in the new one.

No, no more progress I'm afraid. Too many other things going on right now.

The basics work which is good. I've also seen that the graphics library used for OpenHASP is also available for MicroPython and there are various people working on integrating it with ESPHome.

So I consider it a successful Proof of Concept. Got some other stuff to do that will take priority and I'll come back to it later.

Still very interested in what other people have done with it though.


Met Office do have several other API's though, not just the hourly forecast.

This is one of the reasons I kept the forecast flows separated. To make it easier to combine data should I wish to.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.