Friday, June 26, 2026

I Built an AI Brain for My Home Battery

My electricity bill kept changing by the hour, so I taught a battery to outsmart it.

Electricity hasn't been one flat price for me in a while. It moves every hour. Some hours are nearly free, others are painful, and on a really volatile day the worst hour can cost eight or nine times the cheapest one.

A home battery is the obvious lever. Charge it when power is cheap, run the house off it when power is dear. Simple idea. The hard part is the timing — get it wrong and you've happily paid to store expensive electricity, which is the opposite of the point. So I handed the timing to a bit of software. A small "brain" that watches the prices, decides when to charge and when to discharge, and just gets on with it.

How it started

The first version was almost embarrassingly simple. One rule: if the price drops below a threshold, charge. If it climbs above one, discharge. A basic automation, no intelligence at all.

And honestly, it was fine. For a while. But a fixed rule is short-sighted by design. It doesn't know there's a cheaper hour coming at 3am, or that tomorrow evening's peak is the one really worth saving for, or that a hot afternoon means the air conditioning is about to push demand up. It only ever reacts to right now. Closing that gap — making it plan instead of react — is the whole reason the rest of this exists.


What it does now

Roughly once an hour it looks ahead and makes a plan for the next day and a half.

The "looking ahead" part is where the AI sits. It predicts how much electricity the house will use, and it factors in the weather, because a warm day means more cooling and more demand. Then it lines that forecast up against the coming hourly prices and figures out the cheapest way through: when to fill the battery, when to lean on it, and — importantly — always leaving some charge in reserve in case the power goes out. Then it carries the plan out itself. It'll even shut off the office air conditioner while charging, because the two were fighting over the same limited supply.


The part I'm actually proud of isn't the AI. It's that I don't change anything on the real battery on a hunch.

That caution starts before anything reaches the battery at all. The code that runs the show has its own suite of automated tests, and they run on every single change. A careless edit gets caught on my screen, not on the hardware keeping my lights on. It sounds boring. It's the boring that lets me trust the rest.

Before I touch a setting, I replay it against months of past prices and see what it would have done. That's how I settled an annoying question — was it worth upgrading my power connection? I ran it both ways. The bigger connection roughly quadrupled the savings in the simulation, so I went ahead. The numbers decided, not me.

I run these experiments for the small decisions too. Take a simple one: how hard should the battery work? Chase every last cent and it cycles more often, and every cycle wears it out a little. So I replayed a whole range of settings against the same historical prices and watched for the point where the extra savings stopped being worth it. The sweet spot kept almost all of the money while cycling the battery about a third less — far gentler on an expensive piece of hardware, for a difference I'd never have noticed on the bill.

One result genuinely surprised me. Roughly a third of the savings I could be making slips away purely because no forecast is ever perfect. That's a slightly humbling number, but a useful one — it tells me the next thing to improve is the predictions, not anything else.

Seeing what it's doing

A system that quietly spends my money while I'm not looking has to be glanceable. So honestly, most of what I built around the AI is about visibility — turning raw numbers into something I can read in a second.

The panel at the top of this post is the live view: how full the battery is, what it's doing right now, how cheap the current hour is, and today's running tally. In that screenshot it was discharging straight into the single most expensive hour of the day — which is exactly the point.

Then there's the forecast view: the AI showing its homework. How much it expects to save over the coming day and a half, split into what the bill would have been with no battery versus the optimized plan, with a running total that climbs slot by slot.


But a forecast is only worth trusting if you check it against reality. So a separate view keeps score on how good the predictions actually were — predicted household usage laid over what really happened, and an error trend over the month. That scoreboard is where the "a third of the savings lost to imperfect forecasts" figure comes from: I can literally watch the gap between what was possible and what the forecast managed to capture. It's also what tells me forecasting — not anything else — is the next thing worth improving.


And then the boring-but-essential one: an operational view that answers a single question — is everything actually working? When did it last finish a planning cycle? Is the battery's control responding, or has it gone quiet? Does what the system thinks it commanded match what the battery is actually doing? Has anything errored out? If any of those drifts, an alert fires long before I'd spot it on a bill. That last one matters more than it sounds. The line between a fun weekend experiment and something you let run unattended is whether it can tell you — loudly — the moment it stops behaving.

Asking the AI what's going on

Dashboards are great when you're actually looking at them. The part that still makes me grin, though, is that I can just ask.

I run a personal AI assistant at home, built on the open-source Hermes agent, and I gave it a dedicated little helper whose only job is to watch the optimizer and explain it like a human would. I can ask "what's the battery doing right now?" or "how much did we save today?" and it reads the very same live numbers the dashboards use and answers in a sentence.

The crucial word there is read-only. The assistant can see everything and change nothing. The optimizer stays the one and only thing allowed to touch the battery — because the quickest way to wreck a careful system is to let two things give orders to the same hardware. So the AI observes, summarizes, and flags anything odd — a stale cycle, a control surface gone quiet, savings slipping negative — but it never reaches for the controls itself.


Where it is today

It's live. It runs by itself, quietly shaving money off the bill, and it gets a little sharper every week as it learns from more data. Not bad for something that started as a single if-statement.


this blog was mostly written with the help of AI

Friday, June 04, 2021

DevSecOps: Running a React Single Page App with minimal privilege in Kubernetes

 Dockerfile

FROM node:16-alpine3.11 as build
WORKDIR /app

ENV PATH /app/node_modules/.bin:$PATH

COPY Moneta.Frontend.Web/package.json ./
COPY Moneta.Frontend.Web/package-lock.json ./
RUN npm install --silent

COPY Moneta.Frontend.Web/ ./

RUN yarn build

FROM nginx:latest
COPY --from=build /app/build /usr/share/nginx/html

COPY Moneta.Frontend.Web/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY Moneta.Frontend.Web/nginx/nginx.conf /etc/nginx/nginx.conf

RUN chown -R nobody:nogroup /usr/share/nginx/html && chmod -R 755 /usr/share/nginx/html && \
        chown -R nobody:nogroup /var/cache/nginx && \
        chown -R nobody:nogroup /var/log/nginx && \
        chown -R nobody:nogroup /etc/nginx/conf.d

RUN touch /var/run/nginx.pid && \
        chown -R nobody:nogroup /var/run/nginx.pid

EXPOSE 8080

CMD ["nginx", "-g", "daemon off;"]

nginx/default.conf

server {
  listen 8080;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

nginx/nginx.conf

worker_processes  1;
           
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}


k8s deployment

podSecurityContext
  runAsUser65534  #nobody
  fsGroup65534    #nogroup
securityContext
  capabilities:
    drop:
    - ALL
    add
    - "NET_ADMIN"
  readOnlyRootFilesystemfalse
  runAsNonRoottrue
  runAsUser65534 # run as the nobody user

Thursday, April 22, 2021

Fix Warning ResponseCookies - The cookie '.AspNetCore.OpenIdConnect.Nonce.xyz' has set 'SameSite=None' and must also set 'Secure'

            
In Startup.cs add the following snippet in Configure

 public void Configure(IApplicationBuilder appIWebHostEnvironment env)
        {
            //...

            app.UseCookiePolicy(new CookiePolicyOptions
            {
                Secure = CookieSecurePolicy.Always,
                MinimumSameSitePolicy = SameSiteMode.None
            });

            //...
        }

Saturday, April 17, 2021

DevSecOps: Runing ASP .NET Core Applications with minimal privileges in Kubernetes

 


Configure podSecurityContext

Configure the pod to run as nobody/nogroup user as follows:

podSecurityContext
  runAsUser65534
  fsGroup65534



Configure SecurityContext

Configure security context to run with minimal possible privileges:

securityContext
  capabilities:
    drop:
    - ALL
    add
    - "NET_ADMIN"
  readOnlyRootFilesystemfalse
  runAsNonRoottrue
  runAsUser65534 # run as the nobody/nogroup user



Run on non standard port

Since we do not have permission to run ports lower tan 1024 (normally assigned by adding capability NET_BIND_SERVICE but this requires root privileges) we have to configure ASP .Net Core to listen to a port above 1024.

env:
nameASPNETCORE_URLS
    valuehttp://+:8080



Tuesday, April 13, 2021

Zero trust architecture with Istio

Disabling access to services outside the mesh

The following command will restrict all outbound traffic to services defined in the service registry as well as those defined through ServiceEntries:

istioctl install --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY. 

Enabling access to an URL outside the mesh

apiVersionnetworking.istio.io/v1beta1
kindServiceEntry
metadata:
  namemoneta-egress
spec:
  hosts:
  - '*.microsoft.com'
  - '*.microsoftonline.com'
  - '*.windows.net'
  locationMESH_EXTERNAL
  ports:
  - namehttps
    number443
    protocolHTTPS
  resolutionNONE

Set up the namespace to be secure by default

Enable mTLS in strict mode for a specific namespace

apiVersion"security.istio.io/v1beta1"
kind"PeerAuthentication"
metadata:
  name"default"
spec:
  mtls:
    modeSTRICT


Apply ALLOW NOTHING policy

apiVersionsecurity.istio.io/v1beta1
kindAuthorizationPolicy
metadata:
  nameallow-nothing
spec:
  {}

Creating allow rules for the different components

Create allow rule for the frontend

apiVersionsecurity.istio.io/v1beta1
kindAuthorizationPolicy
metadata:
  namefrontend
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "web.name" . }}
  actionALLOW
  rules:
   - {}

Create allow rule for accounts service

apiVersionsecurity.istio.io/v1beta1
kindAuthorizationPolicy
metadata:
  name: {{ include "accounts.name" . }}
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "accounts.name" . }}
  actionALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/moneta/sa/frontend-web"]
    to:
    - operation:
        paths: ["/accounts/*"]

check policies applied 

istioctl x authz check $(kubectl get pods -l app=mssql -n moneta -o jsonpath="{.items[0].metadata.name}").moneta

Thursday, March 11, 2021

Cloud-native Architecture

Definition:
Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.


These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil.


The Cloud Native Computing Foundation seeks to drive adoption of this paradigm by fostering and sustaining an ecosystem of open source, vendor-neutral projects. We democratize state-of-the-art patterns to make these innovations accessible for everyone.


ref: https://github.com/cncf/foundation/blob/master/charter.md

Traits of a cloud-native architecture:
  • Self-healing
  • cost efficient
  • easy to update 
Principles
  1. Design for automation
  2. Favor stateless over statefull applications
  3. Favor Managed Services
  4. Practice defense in depth
  5. Evolutionary Architecture

ref: https://cloud.google.com/blog/products/application-development/5-principles-for-cloud-native-architecture-what-it-is-and-how-to-master-it


Sunday, August 25, 2019

Micro8ks on windows


Running Micro8ks on a windows machine
  • Install Multipass: https://multipass.run/#install
  • From Powershell:
    • multipass launch -n microk8s-vm -m 4g -d 40g ubuntu
    • multipass list
    • multipass exec microk8s-vm  -- sudo snap install microk8s --classic
    • multipass connect microk8s-vm
References:

Tuesday, January 29, 2019

Check GDPR Compliancy in SQL Server with Dynamic Masking and Encryption

SELECT S.name AS schema_name,
       T.name AS table_name,
       C.name AS column_name,
       TY.name AS type_name,
       COALESCE(IT.value, N'') AS information_type,
       COALESCE(SL.value, N'') AS sensitivity_label,
          COALESCE(mc.is_masked, '0') as IsMasked,
          ISNULL(c.encryption_type, 0) as IsEncrypted
FROM sys.schemas AS S
    JOIN sys.tables AS T
        ON T.schema_id = S.schema_id
    JOIN sys.columns AS C
        ON C.object_id = T.object_id
    JOIN sys.types AS TY
        ON TY.user_type_id = C.user_type_id
    LEFT OUTER JOIN sys.extended_properties AS IT
        ON IT.major_id = C.object_id
           AND IT.minor_id = C.column_id
           AND IT.name = 'sys_information_type_name'
    LEFT OUTER JOIN sys.extended_properties AS SL
        ON SL.major_id = C.object_id
           AND SL.minor_id = C.column_id
           AND SL.name = 'sys_sensitivity_label_name'
       LEFT OUTER JOIN sys.masked_columns as mc
             ON mc.object_id = t.object_id
             AND mc.column_id = c.column_id
where it.value is not null
ORDER BY S.name,
         T.name,
         C.name;

Wednesday, May 10, 2017

Why using { } even for single line if else statemens in C#


If programmers at Apple had simply followed a couple of the rules in the Embedded C Coding Standard, they could have prevented the very serious `Gotofail` SSL bug from entering the iOS and OS X operating systems. Here’s a look at the programming mistakes involved and the easy-to-follow coding standard rules that could have easily prevent the bug.
Source: http://embeddedgurus.com/barr-code/2014/03/apples-gotofail-ssl-security-bug-was-easily-preventable/


Wednesday, June 01, 2016

Converting unix epoch to datetime in streaming analytics

sample input:
    {
        "Key": "Dryer_Sensor",
        "State": "0.8",
        "Timestamp": "1464782405968",
        "Site": "Bir57",
        "EventProcessedUtcTime": "2016-06-01T13:24:49.4517020Z",
        "PartitionId": 0,
        "EventEnqueuedUtcTime": "2016-06-01T12:00:06.4520000Z",
        "IoTHub": {
            "MessageId": "7e6d0379d85046f295c0cdaeaabe25d8",
            "CorrelationId": null,
            "ConnectionDeviceId": "openhab",
            "ConnectionDeviceGenerationId": "635957210185596241",
            "EnqueuedTime": "0001-01-01T00:00:00.0000000",
            "StreamId": null
        }
   }

 query: DATEADD(millisecond, CAST([Timestamp] as bigint), '1970-01-01T00:00:00Z') as timeFromString

this will result in  '1970-01-03T07:09:18.032Z' which is wrong!

you can correct this by modifying the input so its strips the quotes from the epoch


    {
        "Key": "Dryer_Sensor",
        "State": "0.8",
        "Timestamp": "1464782405968",
        "epochtime": 1464782405968,
        "Site": "Bir57",
        "EventProcessedUtcTime": "2016-06-01T13:24:49.4517020Z",
        "PartitionId": 0,
        "EventEnqueuedUtcTime": "2016-06-01T12:00:06.4520000Z",
        "IoTHub": {
            "MessageId": "7e6d0379d85046f295c0cdaeaabe25d8",
            "CorrelationId": null,
            "ConnectionDeviceId": "openhab",
            "ConnectionDeviceGenerationId": "635957210185596241",
            "EnqueuedTime": "0001-01-01T00:00:00.0000000",
            "StreamId": null
        }
   }

and modifying the query so it omits the cast (where the bug resides):  DATEADD(millisecond, epochtime, '1970-01-01T00:00:00Z') as time

this will result in the expected result: 2016-06-01T12:00:05.968Z


Friday, April 08, 2016

Registering a device in Azure IoT Hub


Using Node.js

npm install -g iothub-explorer@latest
iothub-explorer <connection string> create MyDevice

source

Using Device Explorer

See: azure iot sdk on github

Friday, February 26, 2016

Installing Z-Wave Stick Gen 5 on Windows IoT Core


I am currently fiddling around with Windows 10 IoT Core on a raspberry Pi. I wanted to use Z-Wave to be able to control a Fibaro Wall Plug (http://www.fibaro.com/uk/the-fibaro-system/wall-plug)

But I could not find the Z-Stick that was on the supported hardware list of windows iot core

so I bought a Z-Wave Aeon Labs Z-Stick USB Controller - Gen5.

the following steps will explain how to get it working for windows iot core

Step 1
Download the drivers here: http://aeotec.com/z-wave-usb-stick/1358-z-wave-drivers.html

step 2
Connect to tou you raspberry e.g. \\freyr\c$\Data\Users\Administrator\Documents
and unzip and copy the drivers

step 3
connect using powershell

[freyr]: PS C:\Data\Users\Administrator\Documents>

freyr]: PS C:\Data\Users\Administrator\Documents> dir


    Directory: C:\Data\Users\Administrator\Documents


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        8/24/2015   8:34 PM           8424 uzb.cat
-a----        8/24/2015   8:34 PM            710 uzb.inf

[freyr]: PS C:\Data\Users\Administrator\Documents> devcon dp_add .\uzb.inf

....

Driver package 'oem0.inf' added.
[freyr]: PS C:\Data\Users\Administrator\Documents>

[freyr]: PS C:\Data\Users\Administrator\Documents> devcon status usb*

...

USB\VID_0658&PID_0200\5&3753427A&0&4
    Name: USB Serial Device
    Driver is running.

...

Thursday, March 05, 2015

About CQS, CQRS and Event Sourcing

Seems to be a lot of confusion going on about these terms. The below extract form Greg Young explains it all:
Many people have been getting confused over what CQRS is. They look at CQRS as being an architecture; it is not. CQRS is a very simple pattern that enables many opportunities for architecture that may otherwise not exist. CQRS is not eventual consistency, it is not eventing, it is not messaging, it is not having separated models for reading and writing, nor is it using event sourcing. I want to take a few paragraphs to describe first exactly what CQRS is and then how it relates to other patterns.

Reference: http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/


Thursday, May 15, 2014

What is the difference between IaaS, Paas and SaaS?

Software as a Service (SaaS)

This has been around as long as the internet exists. These are pieces of software that you can subscribe to, like for instance Gmail, outlook.com, office 365 but as well as Adobe's creative cloud for instance. The main characteristics of this model is that you always are up to date with the latest version. If you take the example of outlook.com (Hotmail), Microsoft will not ask you to update it, you cannot rollback to a previous version. This off course will require from the provider that they always have to ensure continuity of their service. Since I as a customer would not appreciate it that I would not be able to read the emails in my inbox I received or sent 5 years ago.


Platform as a Service (PaaS)

In Microsoft azure these are all the features Microsoft makes available to you the only thing you need to worry about is get your code on there and Microsoft will keep all these features up to date to the latest versions. You have little or no Control over the Operation System or the underlying Infrastructure. In return off course you get certain things in return. For instance in Azure Websites You can have zero downtime scaling, high availability, and failover. Cloud Services provide you with more control in configuring the underlying infrastructure but that's it, you cannot install additional components on these features like a SQL server, you get an OS, an IIS and that's it, there is however some control in the version of the underlying OS and network (expose additional endpoints, deploy on a virtual network, etc). 


Infrastructure as a Service (IaaS)

These are basically virtual machine, the customer is responsible to monitor and maintain the OS and install and maintain the necessary features on these Virtual Machines like e.g. IIS, MSMQ, …
It's all matter of what you want to do yourself, are willing to do yourself



Scott Hanselman provides a very good analogy with regards to this: http://www.hanselman.com/blog/IntroducingWindowsAzureWebJobs.aspx