Skip to content

Commit

Permalink
mvp
Browse files Browse the repository at this point in the history
  • Loading branch information
maximousblk committed Jan 16, 2024
0 parents commit 296d963
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/build.yml
@@ -0,0 +1,40 @@
name: Build

on:
push: { branches: [main] }

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Install Zig
uses: goto-bus-stop/setup-zig@v2

- name: Build
run: zig build -Doptimize=ReleaseSafe -Dtarget=aarch64-linux-gnu --summary all

- name: Archive
run: |
mkdir -p ./upsmon-aaarch64-linux-gnu
cp ./zig-out/bin/upsmon ./upsmon-aaarch64-linux-gnu
cp ./README.md ./upsmon-aaarch64-linux-gnu
cp ./LICENSE ./upsmon-aaarch64-linux-gnu
tar -czvf upsmon-aaarch64-linux-gnu.tar.gz upsmon-aaarch64-linux-gnu
- name: Release
uses: ncipollo/release-action@v1
with:
tag: canary
name: Canary build
makeLatest: true
prerelease: false
allowUpdates: true
replacesArtifacts: true
omitBody: true
artifacts: "upsmon-aaarch64-linux-gnu.tar.gz"
token: ${{ secrets.GITHUB_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
zig-cache
zig-out
.vscode
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Maximous Black <maximousblk@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
29 changes: 29 additions & 0 deletions README.md
@@ -0,0 +1,29 @@
# UPSmon

A simple UPS monitor for [UPS HAT (B) for Raspberry Pi](https://www.waveshare.com/product/ups-hat-b.htm).

## Installation

You can download the latest release from the [releases page](https://github.com/maximousblk/upsmon/releases) or build it yourself:tm:.

### Requirements

- [Zig](https://ziglang.org/)
- Make sure you have I2C enabled on your Raspberry Pi

### Clone this repository

```bash
git clone https://github.com/maximousblk/upsmon.git
```

### Install

```bash
cd upsmon
zig build install -Doptimize=ReleaseSafe -p ~/.local # will install to ~/.local/bin/upsmon
```

## License

This project is licensed under the [MIT License](LICENSE).
22 changes: 22 additions & 0 deletions build.zig
@@ -0,0 +1,22 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const exe = b.addExecutable(.{
.name = "upsmon",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});

b.installArtifact(exe);

const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);

const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
240 changes: 240 additions & 0 deletions src/main.zig
@@ -0,0 +1,240 @@
const std = @import("std");

/// A simple monitor for INA219 \
/// Based on https://github.com/adafruit/Adafruit_INA219 \
/// Datasheet: http://www.ti.com/lit/ds/symlink/ina219.pdf
const INA219 = struct {
/// I2C Device file handle
file: std.fs.File,
/// I2C Address
addr: u8,

current_lsb: f32,
power_lsb: f32,

const BusVoltageRange = enum(u32) {
/// Set Bus Voltage range to 16v
RANGE_16V = 0x00,
/// Set Bus Voltage range to 32v (default)
RANGE_32V = 0x01,
};

const Gain = enum(u32) {
/// Shunt prog. gain set to 1, 40 mV range
DIV_1_40MV = 0x00,
/// Shunt prog. gain set to /2, 80 mV range
DIV_2_80MV = 0x01,
/// shunt prog. gain set to /4, 160 mV range
DIV_4_160MV = 0x02,
/// shunt prog. gain set to /8, 320 mV range
DIV_8_320MV = 0x03,
};

const AdcResolution = enum(u32) {
/// 9 bit, 1 sample, 84us
ADCRES_9BIT_1S = 0x00,
/// 10 bit, 1 sample, 148us
ADCRES_10BIT_1S = 0x01,
/// 11 bit, 1 sample, 276us
ADCRES_11BIT_1S = 0x02,
/// 12 bit, 1 sample, 532us
ADCRES_12BIT_1S = 0x03,
/// 12 bit, 2 samples, 1.06ms
ADCRES_12BIT_2S = 0x09,
/// 12 bit, 4 samples, 2.13ms
ADCRES_12BIT_4S = 0x0A,
/// 12 bit, 8 samples, 4.26ms
ADCRES_12BIT_8S = 0x0B,
/// 12 bit, 16 samples, 8.51ms
ADCRES_12BIT_16S = 0x0C,
/// 12 bit, 32 samples, 17.02ms
ADCRES_12BIT_32S = 0x0D,
/// 12 bit, 64 samples, 34.05ms
ADCRES_12BIT_64S = 0x0E,
/// 12 bit, 128 samples, 68.10ms
ADCRES_12BIT_128S = 0x0F,
};

const Mode = enum(u32) {
/// power down
POWERDOWN = 0x00,
/// shunt voltage triggered
SVOLT_TRIGGERED = 0x01,
/// bus voltage triggered
BVOLT_TRIGGERED = 0x02,
/// shunt and bus voltage triggered
SANDBVOLT_TRIGGERED = 0x03,
/// ADC off
ADCOFF = 0x04,
/// shunt voltage continuous
SVOLT_CONTINUOUS = 0x05,
/// bus voltage continuous
BVOLT_CONTINUOUS = 0x06,
/// shunt and bus voltage continuous
SANDBVOLT_CONTINUOUS = 0x07,
};

const Register = enum(u8) {
/// Config Register (R/W)
CONFIG = 0x00,
/// Shunt Voltage Register (R)
SHUNTVOLTAGE = 0x01,
/// Bus Voltage Register (R)
BUSVOLTAGE = 0x02,
/// Power Register (R)
POWER = 0x03,
/// Current Register (R)
CURRENT = 0x04,
/// Calibration Register (R/W)
CALIBRATION = 0x05,
};

const I2C_SLAVE = 0x0703;

pub fn init(i2c_bus: u8, i2c_addr: u8) !INA219 {
var new: INA219 = undefined;

new.addr = i2c_addr;

var buf: [15]u8 = undefined;
const absolute_path = try std.fmt.bufPrint(&buf, "/dev/i2c-{}", .{i2c_bus});

new.file = try std.fs.openFileAbsolute(absolute_path, .{ .mode = .read_write });
errdefer new.file.close();

if (std.os.linux.ioctl(new.file.handle, I2C_SLAVE, i2c_addr) < 0) return error.DeviceNotAvailable;

// Calibrate

const RSHUNT = 0.1; // Shunt resistor value in ohms

// Determine max expected current
const MaxExpectedI = 2.0;

// Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit)
const MinimumLSB = MaxExpectedI / 32_767.0; // 0.000_061 (61uA per bit)
const MaximumLSB = MaxExpectedI / 4_096.0; // 0.000_488 (488uA per bit)

// Choose an LSB between the min and max values
// (Preferrably a roundish number close to MinimumLSB)
const CurrentLSB = (MaximumLSB - MinimumLSB) / 4.0; // = 0.000100 (100uA per bit)
new.current_lsb = CurrentLSB * 1_000; // Current LSB = 100uA per bit

// Compute the calibration register
// Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) = 4096 (0x1000)
const Cal: u16 = @trunc(0.04096 / (CurrentLSB * RSHUNT));

try new.write(Register.CALIBRATION, Cal);

// Calculate the power LSB
// PowerLSB = 20 * CurrentLSB = 0.002 = 0.002 (2mW per bit)
new.power_lsb = 20 * CurrentLSB;

// Set Config register as per above settings
const config: u16 = @intFromEnum(BusVoltageRange.RANGE_32V) << 13 |
@intFromEnum(Gain.DIV_8_320MV) << 11 |
@intFromEnum(AdcResolution.ADCRES_12BIT_32S) << 7 |
@intFromEnum(AdcResolution.ADCRES_12BIT_32S) << 3 |
@intFromEnum(Mode.SANDBVOLT_CONTINUOUS);

try new.write(Register.CONFIG, config);

return new;
}

fn deinit(self: *INA219) void {
self.file.close();
}

/// Read 2 bytes ([2]u8) from the specified register and return it as a word (u16)
pub fn read(self: *INA219, register: Register) !u16 {
const written = try self.file.write(&.{@intFromEnum(register)});
std.debug.assert(written == 1);

var buf: [2]u8 = undefined;
const readen = try self.file.read(&buf);
std.debug.assert(readen == buf.len);

return (@as(u16, buf[0]) << 8) | @as(u16, buf[1]);
}

/// Write a word (u16) to the specified register as 2 bytes ([2]u8)
fn write(self: *INA219, register: Register, data: u16) !void {
var buf: [3]u8 = .{
@intFromEnum(register),
@truncate(data >> 8),
@truncate(data),
};

const written = try self.file.write(&buf);

std.debug.assert(written == buf.len);
}

/// Returns shunt voltage in mV
fn getShuntVoltage(self: *INA219) !f64 {
var value: f64 = @floatFromInt(try self.read(Register.SHUNTVOLTAGE));
if (value > 32767) value -= 65535; // recover signage

return value * 0.01;
}

/// Returns bus voltage in V
fn getBusVoltage(self: *INA219) !f64 {
var value: f64 = @floatFromInt(try self.read(Register.BUSVOLTAGE) >> 3);
if (value > 32767) value -= 65535; // recover signage

return value * 0.004;
}

/// Returns current value in mA
fn getCurrent(self: *INA219) !f64 {
var value: f64 = @floatFromInt(try self.read(Register.CURRENT));
if (value > 32767) value -= 65535; // recover signage

return value * self.current_lsb;
}

/// Returns power value in W
fn getPower(self: *INA219) !f64 {
var value: f64 = @floatFromInt(try self.read(Register.POWER));
if (value > 32767) value -= 65535; // recover signage

return value * self.power_lsb;
}
};

const vt100 = struct {
pub const clear = "\x1b[2K\r";
pub const up = "\x1b[A";
};

pub fn main() !void {
const stdout = std.io.getStdOut().writer();

var ina219 = try INA219.init(0x01, 0x42); // i2c bus 1, address 0x42
defer ina219.deinit();

while (true) {
const bus_voltage = try ina219.getBusVoltage();
try stdout.print(vt100.clear ++ "Bus Voltage: {d: >7.2} V\n", .{bus_voltage});

const current = try ina219.getCurrent();
try stdout.print(vt100.clear ++ "Current: {d: >11.2} mA\n", .{current});

const power = try ina219.getPower();
try stdout.print(vt100.clear ++ "Power: {d: >13.2} W\n", .{power});

const battery = b: {
var b = (bus_voltage - 6) / 2.15 * 100;
if (b > 100) b = 100;
if (b < 0) b = 0;
break :b b;
};
try stdout.print(vt100.clear ++ "Battery: {d: >11.2} %\n", .{battery});

std.time.sleep(200 * std.time.ns_per_ms); // sleep for 200ms

try stdout.print(vt100.up ** 4, .{}); // go up 4 lines
}
}

2 comments on commit 296d963

@SamiQu
Copy link

@SamiQu SamiQu commented on 296d963 May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

@SamiQu
Copy link

@SamiQu SamiQu commented on 296d963 May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

Please sign in to comment.