Skip to content

Commit

Permalink
feat: Add ADC implementation & sensors (#40)
Browse files Browse the repository at this point in the history
* chore: define Option type to know if value is set.

* feat: Add ADC implementation

Also add battery sensor that uses ADC.

* fix formatting value missing args

* initial power config Zigbee cluster

* remove debug adc loop execution

* add free calls for bufid in callbacks

This should've been done before, but I was not sure if it was needed or not.

* replace humidity with general water content cluster impl

This would allow sharing the cluster for other water measurement clusters, as they have same attributes and behavior.

* feat: Add ADC soil moisture sensor

It is generic, and should be configured properly for each use & sensor, but implementation is shared.
  • Loading branch information
ffenix113 authored Mar 31, 2024
1 parent cb8c72f commit 078c8ae
Show file tree
Hide file tree
Showing 34 changed files with 1,037 additions and 139 deletions.
1 change: 1 addition & 0 deletions cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21.4
require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.26.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
2 changes: 2 additions & 0 deletions cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
49 changes: 49 additions & 0 deletions cli/sensor/base/power_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package base

import (
"github.com/ffenix113/zigbee_home/cli/templates/extenders"
"github.com/ffenix113/zigbee_home/cli/types/appconfig"
"github.com/ffenix113/zigbee_home/cli/types/devicetree"
"github.com/ffenix113/zigbee_home/cli/types/generator"
"github.com/ffenix113/zigbee_home/cli/zcl/cluster"
)

type PowerConfiguration struct {
*Base `yaml:",inline"`
cluster.PowerConfiguration `yaml:",inline"`
ADCPin devicetree.ADCPin `yaml:"adc_pin"`
}

func (*PowerConfiguration) String() string {
return "PowerConfiguration"
}

func (*PowerConfiguration) Template() string {
return "sensors/power_config"
}

func (o *PowerConfiguration) Clusters() cluster.Clusters {
clusterConfig := o.PowerConfiguration
clusterConfig.BatteryRatedVoltage /= 100
clusterConfig.BatteryVoltageMinThreshold /= 100
return []cluster.Cluster{
clusterConfig,
}
}

func (*PowerConfiguration) AppConfig() []appconfig.ConfigValue {
return []appconfig.ConfigValue{
appconfig.NewValue("CONFIG_ADC").Required(appconfig.Yes),
}
}

func (o *PowerConfiguration) ApplyOverlay(overlay *devicetree.DeviceTree) error {
dtPin := devicetree.NewButton(o.ADCPin.Pin)
return dtPin.AttachSelf(overlay)
}

func (c *PowerConfiguration) Extenders() []generator.Extender {
return []generator.Extender{
extenders.NewADC(c.ADCPin),
}
}
48 changes: 48 additions & 0 deletions cli/sensor/base/soil_moisture_adc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package base

import (
"github.com/ffenix113/zigbee_home/cli/templates/extenders"
"github.com/ffenix113/zigbee_home/cli/types/appconfig"
"github.com/ffenix113/zigbee_home/cli/types/devicetree"
"github.com/ffenix113/zigbee_home/cli/types/generator"
"github.com/ffenix113/zigbee_home/cli/zcl/cluster"
)

type SoilMoistureADC struct {
*Base `yaml:",inline"`
MinMoistureMv uint16 `yaml:"min_moisture_mv"`
MaxMoistureMv uint16 `yaml:"max_moisture_mv"`
ADCPin devicetree.ADCPin `yaml:"adc_pin"`
}

func (*SoilMoistureADC) String() string {
return "SoilMoistureADC"
}

func (*SoilMoistureADC) Template() string {
return "sensors/soil_moisture_adc"
}

func (o *SoilMoistureADC) Clusters() cluster.Clusters {
return []cluster.Cluster{
// Hardcoded, as we don't configure this values.
cluster.NewSoilMoisture(0, 100),
}
}

func (*SoilMoistureADC) AppConfig() []appconfig.ConfigValue {
return []appconfig.ConfigValue{
appconfig.NewValue("CONFIG_ADC").Required(appconfig.Yes),
}
}

func (o *SoilMoistureADC) ApplyOverlay(overlay *devicetree.DeviceTree) error {
dtPin := devicetree.NewButton(o.ADCPin.Pin)
return dtPin.AttachSelf(overlay)
}

func (c *SoilMoistureADC) Extenders() []generator.Extender {
return []generator.Extender{
extenders.NewADC(c.ADCPin),
}
}
5 changes: 1 addition & 4 deletions cli/sensor/bosch/bme280.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ func (BME280) Clusters() cluster.Clusters {
MaxMeasuredValue: 110,
Tolerance: 0,
},
cluster.RelativeHumidity{
MinMeasuredValue: 10,
MaxMeasuredValue: 90,
},
cluster.NewRelativeHumidity(10, 90),
}
}

Expand Down
5 changes: 1 addition & 4 deletions cli/sensor/sensirion/scd4x.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ func (SCD4X) Clusters() cluster.Clusters {
MaxMeasuredValue: 60,
Tolerance: 1,
},
cluster.RelativeHumidity{
MinMeasuredValue: 0,
MaxMeasuredValue: 100,
},
cluster.NewRelativeHumidity(0, 100),
cluster.CarbonDioxide{
MinMeasuredValue: 400,
MaxMeasuredValue: 5000,
Expand Down
54 changes: 54 additions & 0 deletions cli/templates/extenders/adc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package extenders

import (
"fmt"

"github.com/ffenix113/zigbee_home/cli/types/devicetree"
"github.com/ffenix113/zigbee_home/cli/types/generator"
)

var _ generator.Extender = ADC{}
var _ devicetree.Applier = ADC{}

type ADC struct {
generator.SimpleExtender

Instances []devicetree.ADCPin
}

func NewADC(instances ...devicetree.ADCPin) generator.Extender {
return ADC{
Instances: instances,
}
}

func (l ADC) Template() string {
return "peripherals/adc"
}

func (l ADC) WriteFiles() []generator.WriteFile {
return []generator.WriteFile{
{
FileName: "adc.c",
TemplateName: "adc.c",
},
{
FileName: "adc.h",
TemplateName: "adc.h",
},
}
}

func (l ADC) Includes() []string {
return []string{"zephyr/drivers/adc.h", "adc.h"}
}

func (l ADC) ApplyOverlay(dt *devicetree.DeviceTree) error {
for _, instance := range l.Instances {
if err := instance.AttachSelf(dt); err != nil {
return fmt.Errorf("attach adc: %w", err)
}
}

return nil
}
6 changes: 3 additions & 3 deletions cli/templates/extenders/i2c.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (i I2C) ApplyOverlay(dt *devicetree.DeviceTree) error {
for _, instance := range i.Instances {
// Add pin definitions only if we have some.
// Otherwise just enable the I2C instance.
if instance.SDA.Pin != 0 && instance.SCL.Pin != 0 {
if instance.SDA.PinsDefined() && instance.SCL.PinsDefined() {
pinctrl.AddNodes(buildI2C(instance.ID, instance)...)
}

Expand Down Expand Up @@ -78,8 +78,8 @@ func buildI2CNode(i I2CInstance, lowPowerEnable bool) *devicetree.Node {
Properties: []devicetree.Property{
devicetree.NewProperty("psels",
devicetree.Array(
devicetree.NrfPSel("TWIM_SDA", i.SDA.Port, i.SDA.Pin),
devicetree.NrfPSel("TWIM_SCL", i.SCL.Port, i.SCL.Pin),
devicetree.NrfPSel("TWIM_SDA", i.SDA.Port.Value(), i.SDA.Pin.Value()),
devicetree.NrfPSel("TWIM_SCL", i.SCL.Port.Value(), i.SCL.Pin.Value()),
),
),
},
Expand Down
6 changes: 3 additions & 3 deletions cli/templates/extenders/uart.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (i UART) ApplyOverlay(dt *devicetree.DeviceTree) error {

// Add pin definitions only if we have some.
// Otherwise just enable the UART instance.
if instance.RX.Pin != 0 && instance.TX.Pin != 0 {
if instance.RX.PinsDefined() && instance.TX.PinsDefined() {
pinctrl.AddNodes(buildPinctrlUART(instance.ID, instance)...)
}

Expand Down Expand Up @@ -103,8 +103,8 @@ func buildUARTNode(i UARTInstance, lowPowerEnable bool) *devicetree.Node {
Properties: []devicetree.Property{
devicetree.NewProperty("psels",
devicetree.Array(
devicetree.NrfPSel("UART_TX", i.TX.Port, i.TX.Pin),
devicetree.NrfPSel("UART_RX", i.RX.Port, i.RX.Pin),
devicetree.NrfPSel("UART_TX", i.TX.Port.Value(), i.TX.Pin.Value()),
devicetree.NrfPSel("UART_RX", i.RX.Port.Value(), i.RX.Pin.Value()),
),
),
},
Expand Down
47 changes: 47 additions & 0 deletions cli/templates/src/extenders/adc.c.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <stdint.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/logging/log.h>

LOG_MODULE_DECLARE(app, LOG_LEVEL_INF);

int zigbee_home_read_adc_mv(const struct adc_dt_spec *spec, int32_t *valp) {
int err;
uint16_t buf;
struct adc_sequence sequence = {
.buffer = &buf,
/* buffer size in bytes, not number of samples */
.buffer_size = sizeof(buf),
};

(void)adc_sequence_init_dt(spec, &sequence);
err = adc_read(spec->dev, &sequence);
if (err < 0) {
LOG_DBG("ADC %s@%d: Could not read (%d)\n", spec->dev->name, spec->channel_id, err);
return err;
}

/*
* If using differential mode, the 16 bit value
* in the ADC sample buffer should be a signed 2's
* complement value.
*/
int32_t val_mv;
if (spec->channel_cfg.differential) {
val_mv = (int32_t)((int16_t)buf);
} else {
val_mv = (int32_t)buf;
}

LOG_DBG("ADC %s@%d raw value: %d", spec->dev->name, spec->channel_id, val_mv);
err = adc_raw_to_millivolts_dt(spec, &val_mv);
/* conversion to mV may not be supported, skip if not */
if (err < 0) {
LOG_DBG(" (value in mV not available)");
return err;
}

LOG_DBG("ADC %s@%d mv value: %d", spec->dev->name, spec->channel_id, val_mv);

*valp = val_mv;
return 0;
}
3 changes: 3 additions & 0 deletions cli/templates/src/extenders/adc.h.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

int zigbee_home_read_adc_mv(const struct adc_dt_spec *spec, int32_t *valp);
20 changes: 20 additions & 0 deletions cli/templates/src/extenders/peripherals/adc.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{define "top_level"}}
/* Data of ADC io-channels specified in devicetree. */
{{ range $i, $instance := .Extender.Instances }}
static const struct adc_dt_spec adc_channel_{{$instance.Name}} = ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), {{$i}});
{{end}}
{{end}}

{{ define "loop"}} {{end}}


{{ define "main"}}
int err;
{{ range .Extender.Instances }}
err = adc_channel_setup_dt(&adc_channel_{{.Name}});
if (err < 0) {
LOG_ERR("Could not setup channel '{{.Name}}' (%d)\n", err);
return 0;
}
{{end}}
{{end}}
4 changes: 4 additions & 0 deletions cli/templates/src/extenders/sensors/ias_zone.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ void update_zone_status(zb_bufid_t bufid, bool status) {
ZB_ZCL_IAS_ZONE_CLEAR_BITS(bufid, 2, ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM1);
break;
}

if (bufid) {
zb_buf_free(bufid);
}
}
{{ end }}

Expand Down
51 changes: 51 additions & 0 deletions cli/templates/src/extenders/sensors/power_config.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{{/* The templates are non-empty to force their usage. */}}
{{ define "top_level" }} {{end}}
{{ define "button_changed"}} {{/* button_status = has_button_changed(&{{.Sensor.Pin.Label}}, button_state, has_changed); */}}{{end}}
{{ define "loop"}}

int32_t batt_mv;
int err = zigbee_home_read_adc_mv(&adc_channel_{{.Sensor.ADCPin.Name}}, &batt_mv);
if (err) {
LOG_ERR("Failed to read ADC value from ADC channel {{.Sensor.ADCPin.Name}}");
}
{{ $cluster := (index .Sensor.Clusters 0) }}
zb_uint8_t batt_mv_divided = batt_mv / 100;
zb_zcl_status_t status = zb_zcl_set_attr_val({{.Endpoint}},
{{ $cluster.ID }},
ZB_ZCL_CLUSTER_SERVER_ROLE,
ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_ID,
&(batt_mv_divided),
ZB_FALSE);
if (status) {
LOG_ERR("Failed to set ZCL attribute for battery voltage sensor: %d", status);
}
{/* Endpoint subtracts 1 because seems that there is a bug
in how endpoints are calculated for attribute structs.
*/}
zb_uint16_t rated_voltage = dev_ctx.{{$cluster.CVarName}}_{{sum .Endpoint -1}}_attrs.rated_voltage * 100;
zb_uint16_t min_threshold = dev_ctx.{{$cluster.CVarName}}_{{sum .Endpoint -1}}_attrs.voltage_min_threshold * 100;

zb_uint8_t percentage;
if (batt_mv >= rated_voltage) {
percentage = 200;
} else if (batt_mv <= min_threshold) {
percentage = 0;
} else {
percentage = (((batt_mv-min_threshold)*200) / (rated_voltage - min_threshold));
}

// Have only 10% increments
percentage = ((percentage + 10) / 20) * 20;

LOG_DBG("ADC battery channel {{.Sensor.ADCPin.Name}}: rated: %d, threshold: %d, current mv: %d, percent(x2): %d", rated_voltage, min_threshold, batt_mv, percentage);
status = zb_zcl_set_attr_val({{.Endpoint}},
{{ $cluster.ID }},
ZB_ZCL_CLUSTER_SERVER_ROLE,
ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_REMAINING_ID,
&percentage,
ZB_FALSE);
if (status) {
LOG_ERR("Failed to set ZCL attribute for battery percentage sensor: %d", status);
}
{{end}}
{{ define "main"}} {{end}}
Loading

0 comments on commit 078c8ae

Please sign in to comment.