Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BL0942 powermeter #1515

Merged
merged 2 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions mos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ config_schema:
- ["gains.bwgain", "f", 4194304, {title: ""}]
- ["gains.phcalb", "f", 0, {title: ""}]

- ["scales", "o", {title: "", abstract: true}]
- ["scales.voltage_scale", "f", 1, {title: ""}]
- ["scales.current_scale", "f", 1, {title: ""}]
- ["scales.aenergy_scale", "f", 1, {title: ""}]
- ["scales.apower_scale", "f", 1, {title: ""}]

build_vars:
# BLE disabled for most models.
MGOS_HAP_BLE: 0
Expand Down Expand Up @@ -700,6 +706,7 @@ conds:
name: Plus1PMMini
sources:
- src/ShellyMini1PMGen3
- src/BL0942
libs:
- location: https://github.com/mongoose-os-libs/mongoose
build_vars:
Expand All @@ -721,6 +728,8 @@ conds:
PRODUCT_HW_REV: "0.1.0"
STOCK_FW_MODEL: Plus1PMMini
MAX_NUM_HAP_SESSIONS: 16
MGOS_CONFIG_DEV_6: "shelly"
MGOS_CONFIG_DEV_7: "shelly,4096"
config_schema:
- ["device.id", "ShellyPlus1PMMini-??????"]
- ["shelly.name", "ShellyPlus1PMMini-??????"]
Expand All @@ -734,11 +743,21 @@ conds:
- ["gdo1.name", "Garage Door"]
- ["gdo1.open_sensor_mode", 2]

- ["factory", "o", {title: ""}]
- ["factory.batch", "s", "", {title: ""}]
- ["factory.model", "s", "", {title: ""}]
- ["factory.version", "i", 0, {title: ""}]
- ["factory.calib", "o", {title: "Factory calib settings"}]
- ["factory.calib.done", "b", false, {title: ""}]
- ["factory.calib.scales0", "scales", {title: ""}]

- when: build_vars.MODEL == "ShellyMini1PMGen3"
apply:
name: Mini1PMG3
libs:
- location: https://github.com/mongoose-os-libs/mongoose
sources:
- src/BL0942
build_vars:
OTA_DATA_ADDR: 0x10000
OTA_DATA_SIZE: 0x4000
Expand All @@ -763,10 +782,12 @@ conds:
PRODUCT_HW_REV: "0.1.0"
STOCK_FW_MODEL: Mini1PMG3
MAX_NUM_HAP_SESSIONS: 16
MGOS_CONFIG_DEV_6: "shelly"
MGOS_CONFIG_DEV_7: "shelly,4096"
config_schema:
- ["device.id", "ShellyMini1PMG3-??????"]
- ["shelly.name", "ShellyMini1PMG3-??????"]
- ["wifi.ap.ssid", "ShellyMini1PMG3-??????"]
- ["device.id", "Shelly1PMMiniG3-????????????"]
- ["shelly.name", "Shelly1PMMiniG3-????????????"]
- ["wifi.ap.ssid", "Shelly1PMMiniG3-????????????"]
- ["sw1", "sw", {title: "SW1 settings"}]
- ["sw1.name", "Shelly SW"]
- ["in1", "in", {title: "Input 1 settings"}]
Expand All @@ -776,6 +797,14 @@ conds:
- ["gdo1.name", "Garage Door"]
- ["gdo1.open_sensor_mode", 2]

- ["factory", "o", {title: ""}]
- ["factory.batch", "s", "", {title: ""}]
- ["factory.model", "s", "", {title: ""}]
- ["factory.version", "i", 0, {title: ""}]
- ["factory.calib", "o", {title: "Factory calib settings"}]
- ["factory.calib.done", "b", false, {title: ""}]
- ["factory.calib.scales0", "scales", {title: ""}]

- when: build_vars.MODEL == "ShellyMini1Gen3"
apply:
name: Mini1G3
Expand Down
197 changes: 197 additions & 0 deletions src/BL0942/shelly_pm_bl0942.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright (c) Shelly-HomeKit Contributors
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "shelly_pm_bl0942.hpp"

#include <cmath>

#include "mgos.hpp"

struct packet {
uint8_t frame_header;
uint8_t i_rms[3];
uint8_t v_rms[3];
uint8_t i_fast_rms[3];
uint8_t watt[3];
uint8_t cf_cnt[3];
uint8_t frequency[2];
uint8_t reserved1;
uint8_t status;
uint8_t reserved2;
uint8_t reserved3;
uint8_t checksum;
} __attribute__((packed));

namespace shelly {

BL0942PowerMeter::BL0942PowerMeter(int id, int tx_pin, int rx_pin,
int meas_time, int uart_no)
: PowerMeter(id),
tx_pin_(tx_pin),
rx_pin_(rx_pin),
meas_time_(meas_time),
uart_no_(uart_no),
meas_timer_(std::bind(&BL0942PowerMeter::MeasureTimerCB, this)) {
}

BL0942PowerMeter::~BL0942PowerMeter() {
}

#define BL_READ 0x58
#define BL_WRITE 0xA8

#define BL_SOFT_RESET 0x1C
#define BL_USR_WRPROT 0x1D
#define BL_MODE 0x19
#define BL_TPS_CTRL 0x1B
#define BL_I_FAST_RMS_CTRL 0x10

#define BL_ADDR 0x0

#define BL_WATT 0x6

Status BL0942PowerMeter::Init() {
if (rx_pin_ < 0 && tx_pin_ < 0) {
return mgos::Errorf(STATUS_INVALID_ARGUMENT, "no valid pins");
}

struct mgos_uart_config ucfg;
mgos_uart_config_set_defaults(uart_no_, &ucfg);

ucfg.baud_rate = 9600;

ucfg.dev.rx_gpio = rx_pin_;
ucfg.dev.tx_gpio = tx_pin_;
ucfg.dev.cts_gpio = -1;
ucfg.dev.rts_gpio = -1;

if (!mgos_uart_configure(uart_no_, &ucfg)) {
return mgos::Errorf(STATUS_INVALID_ARGUMENT, "Failed to configure UART");
}

mgos_uart_set_rx_enabled(uart_no_, true);

meas_timer_.Reset(meas_time_ * 1000, MGOS_TIMER_REPEAT);
LOG(LL_INFO, ("BL0942 @ %d/%d", rx_pin_, tx_pin_));

this->WriteReg(BL_SOFT_RESET, 0x5a5a5a);
// this->WriteReg(BL_USR_WRPROT, 0x550000);
// this->WriteReg(BL_MODE, 0x001000);
// this->WriteReg(BL_TPS_CTRL, 0xFF4700);
// this->WriteReg(BL_I_FAST_RMS_CTRL, 0x1C1800);

return Status::OK();
}

StatusOr<float> BL0942PowerMeter::GetPowerW() {
return apa_;
}

StatusOr<float> BL0942PowerMeter::GetEnergyWH() {
return aea_;
}

bool BL0942PowerMeter::WriteReg(uint8_t reg, uint32_t val) {
uint8_t tx_buf[6] = {BL_WRITE | BL_ADDR,
reg,
(uint8_t) ((val >> 16) & 0xFF),
(uint8_t) ((val >> 8) & 0xFF),
(uint8_t) ((val >> 0) & 0xFF),
0};

for (int i = 0; i < 5; i++) {
tx_buf[5] += tx_buf[i];
}
tx_buf[5] = tx_buf[5] ^ 0xFF;
mgos_uart_write(uart_no_, tx_buf, 6);
mgos_uart_flush(uart_no_);
mgos_msleep(1);
return true;
}

bool BL0942PowerMeter::ReadReg(uint8_t reg, uint8_t *rx_buf, size_t len) {
bool whole_packet = (len == 23);
uint8_t tx_buf[2] = {BL_READ | BL_ADDR, reg};
mgos_uart_write(uart_no_, tx_buf, 2);
mgos_uart_flush(uart_no_);

// Delay to allow data to be available
int baud = 9600;
mgos_msleep(roundf(len * 8 / baud) * 1e3);

int read_len = mgos_uart_read(uart_no_, rx_buf, len);

uint8_t chksum =
tx_buf[0] +
(whole_packet ? 0 : tx_buf[1]); // ignore tx_buf[1] when reading packet
for (int i = 0; i < len - 1; i++) {
chksum += rx_buf[i];
// LOG(LL_INFO, ("data %i:%02X", i, rx_buf[i]));
}
chksum ^= 0xFF;

if (read_len != len || rx_buf[len - 1] != chksum) {
LOG(LL_ERROR, ("wrong checksum"));
return false;
}
return true;
}

uint32_t convert_le24(uint8_t v[3]) {
return ((uint32_t) v[2] << 16) | (uint32_t) (v[1] << 8) | v[0];
}

uint32_t convert_le16(uint8_t v[2]) {
return (uint32_t) (v[1] << 8) | v[0];
}

void BL0942PowerMeter::MeasureTimerCB() {
packet rx_buf;
static uint32_t cf_cnt = 0;

if (this->ReadReg(0xAA, (uint8_t *) &rx_buf, sizeof(rx_buf))) {
if (rx_buf.frame_header == 0x55) {
uint32_t cf = convert_le24(rx_buf.cf_cnt);
cf = (cf_cnt & 0xFF000000) | cf;
if (cf_cnt > cf) {
cf += 0x1000000;
}
cf_cnt = cf;

float wref = (3537 / (1.218 * 1.218 * 4));
float vref = (73989 / (1.218 * 4));
float iref = (305978 / (1.218));

float vo = convert_le24(rx_buf.v_rms) / vref;
float vi = convert_le24(rx_buf.i_rms) / iref;
int32_t wa_tmp = convert_le24(rx_buf.watt);
if (wa_tmp & 0x800000) {
wa_tmp |= 0xFF000000;
}
float wa = wa_tmp / wref;
float fr = 1000000.0 / (float) convert_le16(rx_buf.frequency);

apa_ = wa;
aea_ = cf / (wref * 3600 / (1638.4 * 256));

LOG(LL_INFO, ("vo: %.1f wa: %.2f i: %.2f fr: %.2f ae: %.2f", vo, wa, vi,
fr, aea_));
}
}
}

} // namespace shelly
47 changes: 47 additions & 0 deletions src/BL0942/shelly_pm_bl0942.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) Shelly-HomeKit Contributors
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "shelly_pm.hpp"

#include "mgos_timers.hpp"

namespace shelly {

class BL0942PowerMeter : public PowerMeter {
public:
BL0942PowerMeter(int id, int tx_pin, int rx_pin, int meas_time, int uart_no);
virtual ~BL0942PowerMeter();

Status Init() override;
StatusOr<float> GetPowerW() override;
StatusOr<float> GetEnergyWH() override;

private:
void MeasureTimerCB();

const int tx_pin_, rx_pin_, meas_time_, uart_no_;

float apa_ = 0; // Last active power reading, W.
float aea_ = 0; // Accumulated active energy, Wh.
//
bool ReadReg(uint8_t reg, uint8_t *rx_buf, size_t len);
bool WriteReg(uint8_t reg, uint32_t val);

mgos::Timer meas_timer_;
};

} // namespace shelly
28 changes: 19 additions & 9 deletions src/ShellyMini1PMGen3/shelly_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "shelly_hap_input.hpp"
#include "shelly_input_pin.hpp"
#include "shelly_main.hpp"
#include "shelly_pm_bl0942.hpp"
#include "shelly_sys_led_btn.hpp"
#include "shelly_temp_sensor_ntc.hpp"

Expand All @@ -35,28 +36,37 @@ void CreatePeripherals(std::vector<std::unique_ptr<Input>> *inputs,
in->Init();
inputs->emplace_back(in);

// not yet compatible
#ifdef MGOS_HAVE_ADC
sys_temp->reset(new TempSensorSDNT1608X103F3950(3, 3.3f, 10000.0f));
#endif

// std::unique_ptr<PowerMeter> pm()
std::unique_ptr<PowerMeter> pm(new BL0942PowerMeter(1, 6, 7, 1, 1));
// BL0942 GPIO6 TX GPIO7 RX
// const Status &st = pm->Init();
// if (st.ok()) {
// pms->emplace_back(std::move(pm));
// } else {
// const std::string &s = st.ToString();
// LOG(LL_ERROR, ("PM init failed: %s", s.c_str()));
// }
const Status &st = pm->Init();
if (st.ok()) {
pms->emplace_back(std::move(pm));
} else {
const std::string &s = st.ToString();
LOG(LL_ERROR, ("PM init failed: %s", s.c_str()));
}

InitSysLED(LED_GPIO, LED_ON);
InitSysBtn(BTN_GPIO, BTN_DOWN);
}

void PrintCalibrationData() {
mgos_config_factory *c = &(mgos_sys_config.factory);
LOG(LL_INFO, ("calibration.done %Q", c->calib.done));
mgos_config_scales *s = &c->calib.scales0;
LOG(LL_INFO, ("gains vs: %f cs: %f ps: %f es: %f", s->voltage_scale,
s->current_scale, s->apower_scale, s->aenergy_scale));
}

void CreateComponents(std::vector<std::unique_ptr<Component>> *comps,
std::vector<std::unique_ptr<mgos::hap::Accessory>> *accs,
HAPAccessoryServerRef *svr) {
void PrintCalibrationData();

bool gdo_mode = mgos_sys_config_get_shelly_mode() == (int) Mode::kGarageDoor;
if (gdo_mode) {
hap::CreateHAPGDO(1, FindInput(1), FindInput(2), FindOutput(1),
Expand Down
Loading