calvinmclean / automated-garden Goto Github PK
View Code? Open in Web Editor NEWweather-based smart irrigation controller and backend
License: Apache License 2.0
weather-based smart irrigation controller and backend
License: Apache License 2.0
The config generator should ask for fields when they are missing to improve beginner experience. Also it can be further improved to directly write to files.
It would be relatively easy to add metrics wrappers to count the number of requests, timing of requests, etc.
weather
influxdb
mqtt
Control a fan on a schedule to increase airflow in a garden. Useful specifically for seed-starting setups.
Zone
WaterAction
, telling the controller to run it for a specific period of time, or like a LightAction
that requires the server to toggle ON/OFF states in the controller? Most likely it will be similar to LightAction because I will be running for 10+ minutes. Maybe the same should be done for WaterAction
because I am adapting this to work better for a yard irrigation setup and will be doing long periods of wateringLightAction
, do I combine those types somehow?Currently, a response from GET /zones/{zoneID}
returns information about the rain/moisture data and next watering time/duration. It would be useful to have additional data, such as recent temperature (used by scaling), plus some details on the scale factor currently available.
Currently I implement a Patch method for high-level API types. I should break this up into smaller parts for sub-types because it is getting unruly
https://docs.acorn.io/running/args-and-secrets
https://docs.acorn.io/authoring/secrets
This would allow me to use weather client easily without maintaining separate config.
Depends on #56
After merging the changes for #13, the existing YAML storage client becomes obsolete because it can be replaced by the hord
implementation. The ConfigMap, however, is not replaced so it might still make sense to keep it. On the other hand, it could be removed since the redis
option is very easy to use in a K8s environment.
Ideally there will be a secure option for storing API secrets like this. Consider Hashicorp Vault or similar.
This will require a new feature for a composite StorageClient which should be pretty easy to achieve using the current interface setup.
Follow-up for #77
When generating a config with multiple zones, there are a few issues:
NUM_ZONES
is 0 (actually this was just missing from config, but should be inferred by len of Zones)https://grafana.com/docs/loki/latest/getting-started
garden-app
Re-add:
// TODO: Can I re-enable this if moisture comes from WeatherClient instead of garden? Follow up issue #95
if ws.HasSoilMoistureControl() {
logger.Debug("getting moisture data for WaterSchedule")
soilMoisture, err := wsr.getMoisture(ctx, garden, ws)
if err != nil {
logger.WithError(err).Warn("unable to get moisture data for WaterSchedule")
} else {
logger.Debugf("successfully got moisture data for WaterSchedule: %f", soilMoisture)
response.WeatherData.SoilMoisturePercent = &soilMoisture
}
}
around line 60 in water_schedule_responses.go
(NewWaterScheduleResponse
)
This might be a little tricky because the weather client in this case needs access to InfluxDB client
Record data about weather-based scaling in order to display data about water savings.
When WaterSchedule.Interval
is 12h
, GetRainData fails with error:
unable to get rain data from weather client: unable to read response body '{\"body\":[],\"status\":\"ok\",\"time_exec\":0.02210092544555664,\"time_server\":1668203473}': json: cannot unmarshal array into Go struct field weatherDataResponse.body of type map[string][]float32
This requires 2 changes:
If someone uses aggressively water-saving scaling, it could result in long periods without watering which might not be good even if the low water usage is great the rest of the time. Additionally, you might want to experiment with some low-water setups but don't want to risk killing any plants.
This feature will solve those problems by allowing the user to configure a minimum watering duration for a specified period of time. Ideally, this time should be a multiple of the watering interval since this is when it will be checked. This should definitely be a requirement because someone might mistakenly have a large watering interval and expect to be saved by minimum watering.
{
"water_schedule": {
"minimum_watering": {
"duration": "2h",
"interval": "168h"
}
}
}
This feature is a pre-requisite to some other features related to WeatherClient because it will assign an ID to the client.
Related features:
SoilMoistureControl
because that is tied to a physical garden-controller
and should only work with Zone's in a Garden with moisture sensorsaccess_token
and refresh_token
when using refresh tokenWeatherControl
now needs to reference a WeatherClient's IDyaml
and json
tags to structsStorageClient
Validate
method to WeatherClient so Options
can be validated for specific clients/test
endpoint or something similar to test weather client configuration/authconfigmap
storage clientThis would allow you to choose “every Sunday and Wednesday” instead of time-based constant interval.
Depends on #22 for more complex data structure for schedules
I could also do something like "every month"
Since cron
already supports both of these cases, is easily compatible with gocron
library, and is well-known and somewhat easy to use (at least it is with all of the helpful resources available). I was originally going to make this a special case for parsing the pkg.Duration
string, but this won't make sense in the cases where it is used as an actual Duration instead of a interval, so I might extend the struct to have a similar Interval struct.
This would be an additional option for the RainControl
that allows watering to just be scaled proportionally to the amount of rain, rather than just skipping.
TemperatureControl
? Will they compound? I think that makes the most senseScaleControl
because higher inputs should scale negatively. Also I do not want to scale down when there is no rain, only scale down when threshold is exceededFix the following deprecations:
set-output
command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/save-state
command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/MQTT broker supports authentication so I should enable it in the broker, server, and controller. Another benefit of this is that it enables using a cloud-hosted MQTT.
An alternative to returning warnings would be to just use the built-in logger that I forgot about... I would have preferred to keep the logger out of action execution, but it is good to log this stuff, especially as I add other data-influence behaviors that could benefit from clear logging.
These clients have a lot of duplication since they are both just reading from the same YAML files, but reading/writing from different sources. I was thinking about having the YAML client struct have replaceable functions for read/write, but realized I don't want those to be exported. I might move them into the same package and just have different sources.
The primary reason for this is to reduce extra HTTP calls. /health
endpoint provides a small amount of data that could easily fit into the regular response:
{
"status": "UP",
"details": "last contact from Garden was 21.593868678s ago",
"last_contact": "2023-02-13T22:23:18.299911341Z"
}
However, it could reduce the performance of this endpoint because of calls to external client (InfluxDB)
It would be nice to be able to define more complex WaterSchedules
for a Zone
. This would allow for watering a Zone multiple times in one day (such as morning/evening, mid-afternoon top-off, or a multiple short soaks for even, deep watering). A complex configuration like this might be annoying to work with as a JSON object nested under a Zone. Also, after putting careful consideration into the WaterSchedule, you might want to use it in multiple Zones without reconfiguring.
WaterSchedules
as separate API resources, independent of Gardens or Zonesnext_watering_time
and weather_data
is now tied to the WaterSchedule + Zone. I will implement the response on the WaterSchedule API and then the ZoneResponse can use the same methods and compensate for any configured skipsworker/zone_actions.go:106
This would allow changing the watering intervals to water less frequently in winter and more frequently in summer, rather than just using weather data to increase/decrease durations
Depends on #22
Add the ability to read data from temperature/humidity sensors. Even though I added support for external weather APIs, this feature can still be useful for indoor gardens and for monitoring the electronics in outdoor locations.
garden-controller
, publishing data to InfluxDB
via Telegraf
and MQTT
garden-app
to read data from InfluxDBThis is a simple enhancement to reduce some code duplication on middlewares for restricting access to end-dated API resources
It would be good to introduce some integration tests that verify the results of various actions when the entire application stack is running. The mock controller could be extended with some sort of assertions/expectations for better testing.
Overall coverage before integration testing: 53.3%
Integration tests should focus on areas that are not currently tested by unit tests, plus maybe a few high-level API tests for basic peace of mind testing and end-to-end creation of resources and seeing their effects.
Specific areas that need testing (based on low coverage and fit for integration testing):
controller/controller.go
(0%)controller/handlers.go
(0%)influxdb/client.go
(0%)mqtt/mqtt.go
(0%)yaml/yaml.go
(40%, could be covered by more unit tests)server
package code like NewGardensResource
, routes
, etc.
server/server.go
(0%)garden_responses.go
(80%, some NextLightTime
code that could be covered, maybe could be unit test?)Maybe I can create a struct that lets me write my own JSON Unmarshal so I can easily integrate all the error detecting that I have to do throughout the whole system. This would save a lot of pointless error checks and will improve the front-end experience.
Initially I wanted to Marshal the value as the integer representation for an action's message to the controller, but it will make more sense to have the string representation for JSON to the front-end. I will look into options to make converting to integer representation easier
Currently the backend does everything in metric, but it would be nice to choose preferred units on the UI
After adding the WeatherClient
functionality for reading from my Netatmo weather station, the built-in soil moisture is kind of disconnected. It would make sense to integrate this into the WeatherControl
feature.
GetTotalRain
functionSoilMoistureControl
will be part of weather.Control
next to RainControl
structImplement another storage.Client
that uses actual database backend, whether it is SQL or NoSQL.
I recently found madflojo/hord
and added support for local file storage in this PR. Now, I can switch my local storage solution to use this and will just need some additional configuration to support multiple KV and NoSQL storage solutions.
storage.Client
with type KV
that can be compatible with all current hord
backendshord
will return an error if something is not found. I need to either handle this to just return nil, nil
like other clients or implement a new NotFound error that can be used by all client implementationshord
, but it won't support saving to a configmap. I could just remove that since I don't use it anymore, but it is also fine to keep it aroundInstead of generating an access token and refresh token, you should be able to use the oauth flow to redirect to Netatmo website for login. This could be implemented as a CLI helper and/or as part of the process for creating a client using API.
Since I am not using a database or DB migrations, whenever I change my data structures to add a new "required" field, my existing test data might not have these fields. This will cause a lot of errors that should not exist if new data was created through the API. I should have the application validate at startup and fail to run (make optional/forceable with config/CLI arg) if current data doesn't fit API constraints.
The library that I am using allows for having help tips for each prompt. This could improve the user experience for beginners that are relying on the config generator. This also helps reduce the need for documentation.
Add HTTP metrics and export using Prometheus
After #13 adds support for hord
database client, I can easily use the in-memory hashmap
option in all of my tests which will increase coverage on actual storage client.
Once this is done, I can remove the MockClient which also allows me to remove the storage interface.
However, would it be better to implement direct unit tests in-package instead of relying on semi-integration tests from the server
and worker
packages?
It would be nice to have code coverage reports on PRs and on the whole projects. Two good free options are codecov
and coveralls
. These should both integrate easily with GitHub
curl
curl
With the addition of WeatherControl
, it would be cool to be able to use current, or recent, temperature and humidity data to scale watering durations up/down. This feature would also be a good opportunity to implement an open weather API because it doesn't need data to be as accurate as the RainControl
.
Add another implementation of weather.Client
that uses a publicly-available weather API to remove the current hardware barrier for weather-based watering.
After some recent changes, documentation and API specification need to be updated.
Some configuration + API changes that need documentation:
Also need to document:
MQTT logs after plugging controller in:
2023-01-26T22:55:34: New connection from 10.42.0.1:24331 on port 1883.
2023-01-26T22:55:34: New client connected from 10.42.0.1:24331 as seed-garden (p2, c0, k15).
Based on logs it looks like it was being disconnected due to conflict? Now I am no longer messing with different controllers so maybe it is a non-issue. I will keep an eye on it and check logs if it fails again.
So far this has not reproduced, probably since I haven't been configuring any other controllers, but I should still make sure the controller code supports wifi reconnect and MQTT reconnect.
When a Zone/Garden is disabled, all scheduled actions and on-demand actions will be ignored.
In addition to next_water_time
, display what the calculated duration would be based on current weather conditions in requests to GET /zones/{zoneID}
. This is useful for UI details and for debugging weather scaling.
It is difficult to think of useful metrics in this case. A lot of things are already covered on the InfluxDB side of things (based off actual watering and controller data). The main thing is that I do not have a lot of visibility into the scheduled jobs. Certain things like execution counts and durations are not particularly useful, although it could be interesting if there is an MQTT issue causing delays.
Useful metrics:
"type": "garden"/"zone"
and `"id": ""It might be nice to have more flexible scaling configurations that have different parameters for scaling up or down.
Some possible use-cases are:
Implementation:
I noticed that if a very small watering duration value is used for a WaterActionRequest
, such as 10 nanoseconds, the value is not sent correctly to the microcontroller or is not decoded correctly and results in the default watering time. These types of values are not realistic to use and cannot accurately be done by the controller so it should not be allowed anyways.
This was discovered because the iOS app was out of date with the backend version (specifically changes from #66), so a value of 10s sent from iOS was decoded as 10 microseconds by the backend.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.