Saturday, August 24, 2024

Leader Election

CREATE TABLE LeaderElection (

    Id INT PRIMARY KEY IDENTITY,

    HostName NVARCHAR(255) NOT NULL,

    Port INT NOT NULL,

    IsLeader BIT NOT NULL,

    LastHeartbeat DATETIME NOT NULL

);

 

public class LeaderElection { public int Id { get; set; } public string HostName { get; set; } public int Port { get; set; } public bool IsLeader { get; set; } public DateTime LastHeartbeat { get; set; } } 



public class LeaderElectionService

{

    private readonly ApplicationDbContext _dbContext;

    private readonly IHttpContextAccessor _httpContextAccessor;

    private string _hostName;

    private int _port;

    private Timer _timer;


    public LeaderElectionService(ApplicationDbContext dbContext, IHttpContextAccessor httpContextAccessor)

    {

        _dbContext = dbContext;

        _httpContextAccessor = httpContextAccessor;


        // Retrieve the host and port from the HTTP context or environment

        InitializeHostAndPort();

    }


    private void InitializeHostAndPort()

    {

        // Retrieve host and port from the current HTTP context, if available

        if (_httpContextAccessor.HttpContext != null)

        {

            var request = _httpContextAccessor.HttpContext.Request;

            _hostName = request.Host.Host;

            _port = request.Host.Port ?? 80; // Default to 80 if port is not specified

        }

        else

        {

            // Use fallback, such as environment variables or predefined config

            _hostName = Environment.GetEnvironmentVariable("HOSTNAME") ?? "localhost";

            _port = int.TryParse(Environment.GetEnvironmentVariable("PORT"), out int port) ? port : 5000;

        }

    }


    public void Start()

    {

        _timer = new Timer(CheckLeadership, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

    }


    private void CheckLeadership(object state)

    {

        using (var transaction = _dbContext.Database.BeginTransaction())

        {

            var existingLeader = _dbContext.LeaderElections

                .OrderByDescending(le => le.LastHeartbeat)

                .FirstOrDefault(le => le.IsLeader);


            if (existingLeader != null && (DateTime.UtcNow - existingLeader.LastHeartbeat).TotalSeconds < 10)

            {

                // Existing leader is still alive

                if (existingLeader.HostName == _hostName && existingLeader.Port == _port)

                {

                    // Update own heartbeat

                    existingLeader.LastHeartbeat = DateTime.UtcNow;

                    _dbContext.SaveChanges();

                }

            }

            else

            {

                // No leader or leader has failed, try to become leader

                var self = _dbContext.LeaderElections

                    .FirstOrDefault(le => le.HostName == _hostName && le.Port == _port);


                if (self == null)

                {

                    self = new LeaderElection

                    {

                        HostName = _hostName,

                        Port = _port,

                        IsLeader = true,

                        LastHeartbeat = DateTime.UtcNow

                    };

                    _dbContext.LeaderElections.Add(self);

                }

                else

                {

                    self.IsLeader = true;

                    self.LastHeartbeat = DateTime.UtcNow;

                }


                _dbContext.SaveChanges();

            }


            transaction.Commit();

        }

    }

}


Sunday, August 18, 2024

Distributed Task Runner

 Distributed Task Runner


Manager

startup.cs

using ManagerNode;

using Microsoft.AspNetCore.Builder;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Hosting;

using Quartz;

using Quartz.Impl;

using Quartz.Spi;


public class Startup

{

    public void ConfigureServices(IServiceCollection services)

    {

        services.AddControllers();

        services.AddSingleton<JobSchedulerService>();

        services.AddSingleton<RabbitMqJobPublisher>();


        // Register Quartz services

        services.AddSingleton<IJobFactory, SingletonJobFactory>();

        services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();


        // Register the job itself

        services.AddSingleton<DistributedQuartzJob>();


        // Register the Quartz scheduler with dependencies

        services.AddHostedService<QuartzHostedService>();

    }


    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    {

        if (env.IsDevelopment())

        {

            app.UseDeveloperExceptionPage();

        }


        app.UseRouting();

        app.UseEndpoints(endpoints =>

        {

            endpoints.MapControllers();

        });

    }

}


public class SingletonJobFactory : IJobFactory

{

    private readonly IServiceProvider _serviceProvider;


    public SingletonJobFactory(IServiceProvider serviceProvider)

    {

        _serviceProvider = serviceProvider;

    }


    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)

    {

        return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;

    }


    public void ReturnJob(IJob job) { }

}


public class QuartzHostedService : IHostedService

{

    private readonly ISchedulerFactory _schedulerFactory;

    private readonly IJobFactory _jobFactory;

    private readonly JobSchedulerService _jobSchedulerService;

    private IScheduler _scheduler;


    public QuartzHostedService(ISchedulerFactory schedulerFactory, IJobFactory jobFactory, JobSchedulerService jobSchedulerService)

    {

        _schedulerFactory = schedulerFactory;

        _jobFactory = jobFactory;

        _jobSchedulerService = jobSchedulerService;

    }


    public async Task StartAsync(CancellationToken cancellationToken)

    {

        _scheduler = await _schedulerFactory.GetScheduler(cancellationToken);

        _scheduler.JobFactory = _jobFactory;


        // If you need to start scheduling jobs on startup, do it here

        await _scheduler.Start(cancellationToken);

    }


    public async Task StopAsync(CancellationToken cancellationToken)

    {

        if (_scheduler != null)

        {

            await _scheduler.Shutdown(cancellationToken);

        }

    }

}


program.cs


public class Program

{

    public static void Main(string[] args)

    {

        CreateHostBuilder(args).Build().Run();

    }


    public static IHostBuilder CreateHostBuilder(string[] args) =>

        Host.CreateDefaultBuilder(args)

            .ConfigureWebHostDefaults(webBuilder =>

            {

                webBuilder.UseStartup<Startup>();

            });

}


JobSchedulerService.cs

using ManagerNode;

using Quartz;

using Quartz.Impl;

using RabbitMQ.Client;

using System.Text;


public class JobSchedulerService

{

    private readonly IScheduler _scheduler;


    public JobSchedulerService(ISchedulerFactory schedulerFactory)

    {

        _scheduler = schedulerFactory.GetScheduler().Result;

    }


    public async Task ScheduleJob(string jobName, string jobGroup, string triggerName, string triggerGroup, DateTimeOffset startTime)

    {

        var job = JobBuilder.Create<DistributedQuartzJob>()

            .WithIdentity(jobName, jobGroup)

            .Build();


        var trigger = TriggerBuilder.Create()

            .WithIdentity(triggerName, triggerGroup)

            .StartAt(startTime)

            .Build();


        await _scheduler.ScheduleJob(job, trigger);

    }

}


public class DistributedQuartzJob : IJob

{

    private readonly RabbitMqJobPublisher _rabbitMqJobPublisher;


    public DistributedQuartzJob(RabbitMqJobPublisher rabbitMqJobPublisher)

    {

        _rabbitMqJobPublisher = rabbitMqJobPublisher;

    }


    public Task Execute(IJobExecutionContext context)

    {

        // Job execution logic

        var message = "DistributedJob executed at " + DateTime.Now;

        Console.WriteLine(message);


        // Add job details to RabbitMQ queue

        _rabbitMqJobPublisher.PublishJob(message);


        return Task.CompletedTask;

    }

}

RabbitMqJobPublisher.cs

using Newtonsoft.Json;

using RabbitMQ.Client;

using System.Text;

using DistributedScheduler.Jobs;


namespace ManagerNode

{

    public class TypedObject

    {

        public required string Type { get; set; }

        public required object Data { get; set; }

    }


    public class RabbitMqJobPublisher

    {

        private readonly IConnection _connection;

        private readonly IModel _channel;


        public RabbitMqJobPublisher()

        {

            var factory = new ConnectionFactory() { HostName = "localhost" };

            _connection = factory.CreateConnection();

            _channel = _connection.CreateModel();


            _channel.QueueDeclare(queue: "job_queue",

                                 durable: true,

                                 exclusive: false,

                                 autoDelete: false,

                                 arguments: null);

        }


        public void PublishJob(IDistributedJob job)

        {

            var message = JsonConvert.SerializeObject(job, Formatting.Indented);

            var body = Encoding.UTF8.GetBytes(message);


            _channel.BasicPublish(exchange: "",

                                 routingKey: "job_queue",

                                 basicProperties: null,

                                 body: body);

        }


        public void PublishJob(string jobString)

        {

            var message = jobString;

            var body = Encoding.UTF8.GetBytes(message);


            _channel.BasicPublish(exchange: "",

                                 routingKey: "job_queue",

                                 basicProperties: null,

                                 body: body);

        }

    }

}


JobController.cs


using DistributedScheduler.Jobs;
using Microsoft.AspNetCore.Mvc;

namespace ManagerNode.Controllers
{
    [ApiController]
    [Route("api/manager/job")]
    public class JobController : ControllerBase
    {
        private readonly JobSchedulerService _jobSchedulerService;

        public JobController(JobSchedulerService jobSchedulerService)
        {
            _jobSchedulerService = jobSchedulerService;
        }

        [HttpPost]
        public async Task<IActionResult> SubmitJob([FromBody] DistributedJob job)
        {

            if (_jobSchedulerService is null)
                throw new ArgumentNullException(nameof(_jobSchedulerService));

            await _jobSchedulerService.ScheduleJob(job.Name, job.Name, job.Name, job.Name, new DateTimeOffset(DateTime.Now.AddMinutes(1)));

            return Ok(new { job.Id, Status = "Job submitted and published to queue successfully" });
        }
    }

}

ManagerController.cs

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/manager")]
public class ManagerController : ControllerBase
{
    [HttpGet("health")]
    public IActionResult Health()
    {
        var healthStatus = new
        {
            Status = "Healthy",
            PendingTasks = 5, // Example value
            ConnectedRunners = 10 // Example value
        };
        return Ok(healthStatus);
    }

    [HttpGet("status")]
    public IActionResult Status()
    {
        // Return detailed status, possibly with more complex data
        return Ok("Manager is operational with all systems functioning.");
    }
}

Runner

startup.cs

using Microsoft.AspNetCore.Builder;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Hosting;


public class Startup

{

    public void ConfigureServices(IServiceCollection services)

    {

        services.AddControllers();

        services.AddSingleton<JobConsumerService>(); // Register JobConsumerService

    }


    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    {

        if (env.IsDevelopment())

        {

            app.UseDeveloperExceptionPage();

        }


        app.UseRouting();

        app.UseEndpoints(endpoints =>

        {

            endpoints.MapControllers();

        });


        // Ensure JobConsumerService is instantiated when the application starts

        var jobConsumerService = app.ApplicationServices.GetService<JobConsumerService>();

    }

}


program.cs

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.Hosting;


public class Program

{

    public static void Main(string[] args)

    {

        CreateHostBuilder(args).Build().Run();

    }


    public static IHostBuilder CreateHostBuilder(string[] args) =>

        Host.CreateDefaultBuilder(args)

            .ConfigureWebHostDefaults(webBuilder =>

            {

                webBuilder.UseStartup<Startup>();

            });

}

RunnerController.cs

using Microsoft.AspNetCore.Mvc;


namespace RunnerNode.Controllers

{

    [ApiController]

    [Route("api/runner")]

    public class RunnerController : ControllerBase

    {

        [HttpGet("health")]

        public IActionResult GetHealth()

        {

            // Implement health check logic

            return Ok("Runner Node is healthy");

        }


        [HttpGet("status")]

        public IActionResult GetStatus()

        {

            // Implement status retrieval logic

            return Ok("Status: Processing jobs");

        }

    }


}

JobConsumeService.cs

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;

public class JobConsumerService
{
    private readonly IConnection _connection;
    private readonly IModel _channel;

    public JobConsumerService()
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();

        _channel.QueueDeclare(queue: "job_queue",
                             durable: true,
                             exclusive: false,
                             autoDelete: false,
                             arguments: null);

        var consumer = new EventingBasicConsumer(_channel);
        consumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            ProcessJob(message);
        };

        _channel.BasicConsume(queue: "job_queue",
                             autoAck: true, // Auto-acknowledge messages
                             consumer: consumer);
    }

    private void ProcessJob(string jobData)
    {
        try
        {
            Console.WriteLine($"Processing job: {jobData}");
            // Implement your job processing logic here
            Console.WriteLine($"Job processed successfully: {jobData}. Completed on {DateTime.Now}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error processing job: {ex.Message}");
            // Handle exception (e.g., retry logic)
        }
    }

}


Distributed Jobs Class Lib

DistributedJob.cs

public class DistributedJob : IDistributedJob
{
    public required string Id { get; set; }
    public required string Name { get; set; }
    public required string Description { get; set; }
    public required string TypeName { get; set; }

    public required string Status { get; set; }
    public required string CronSchedule { get; set; }
    public DateTime? CreatedAt { get; set; }
}

public interface IDistributedJob
{
    string Id { get; set; }
    string Name { get; set; }
    string Description { get; set; }
    string TypeName { get; set; }
    string Status { get; set; }
    string CronSchedule { get; set; }
    DateTime? CreatedAt { get; set; }
}


Run Rabbit MQ with docker. It is easier than installing in local machine

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management

Postman Collection

{
"info": {
"_postman_id": "108dded1-e54e-4999-974a-4bb8353b8da1",
"name": "Distributed Scheduler",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
"_exporter_id": "15322780"
},
"item": [
{
"name": "Manager",
"item": [
{
"name": "Job",
"item": [
{
"name": "New Request",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n    \"Id\": \"acfd5c51-fc47-4f26-8fb1-0c5253ef9e65\",\r\n    \"Name\": \"Job 1\",\r\n    \"Description\": \"Job One\",\r\n    \"status\": \"pending\",\r\n    \"TypeName\": \"DistributedJob, DistributedScheduler.Jobs\",\r\n    \"CronSchedule\": \"* * * * \"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "http://localhost:5082/api/manager/job"
},
"response": []
}
]
},
{
"name": "Health",
"request": {
"method": "GET",
"header": [],
"url": "http://localhost:5082/api/manager/health"
},
"response": []
},
{
"name": "Status",
"request": {
"method": "GET",
"header": [],
"url": "http://localhost:5082/api/manager/health"
},
"response": []
}
]
},
{
"name": "Runner",
"item": [
{
"name": "Health",
"request": {
"method": "GET",
"header": [],
"url": "http://localhost:5028/api/runner/health"
},
"response": []
},
{
"name": "Status",
"request": {
"method": "GET",
"header": [],
"url": "http://localhost:5028/api/runner/health"
},
"response": []
}
]
}
]
}

Sunday, November 5, 2023

 React Login Form


import { useForm, Controller } from 'react-hook-form';

export default function TestOne() {

    const { control, handleSubmit, formState: { errors } } = useForm();

    const onSubmit = (data) => {
        try {
            // Send the form data to the backend
            fetch('http://localhost:6080/login', {
                method: 'POST',
                mode: 'cors',
                //credentials: 'include',
                redirect: 'follow',
                //redirect: 'manual',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams(data).toString()
            }).then((response) => {
                console.log(response);
                console.log(response.text());
            })
            .catch((e) => {
                console.log(e);
            });

            // console.log(response);

            // if (response.ok) {

            //   // Handle successful login (e.g., redirect to another page)
            //   // Replace with your own logic
            // } else {
            //   // Handle login errors
            //   // Replace with your own error handling
            //   console.error('Login failed');
            // }
        } catch (error) {
            console.log(error);
        }
    };


    return (

        <>
            <h2>Login Page</h2>
            <form onSubmit={handleSubmit(onSubmit)}>
                <div>
                    <label>Username:</label>
                    <Controller
                        name="username"
                        control={control}
                        defaultValue=""
                        rules={{ required: 'Username is required' }}
                        render={({ field }) => <input {...field} />}
                    />
                    {errors.username && <p>{errors.username.message}</p>}
                </div>

                <div>
                    <label>Password:</label>
                    <Controller
                        name="password"
                        control={control}
                        defaultValue=""
                        rules={{ required: 'Password is required' }}
                        render={({ field }) => <input type="password" {...field} />}
                    />
                    {errors.password && <p>{errors.password.message}</p>}
                </div>

                <div>
                    <label>Redirect url:</label>
                    <Controller
                        name="redirectUrl"
                        control={control}
                        defaultValue="http://localhost:7080/callback"
                        rules={{ required: 'Redirect url' }}
                        render={({ field }) => <input type="text" {...field} />}
                    />
                    {errors.redirectUrl && <p>{errors.redirectUrl.message}</p>}
                </div>

                <div>
                    <label>callback url:</label>
                    <Controller
                        name="callbackUrl"
                        control={control}
                        defaultValue="http://localhost:6080/callback"
                        rules={{ required: 'Callback url' }}
                        render={({ field }) => <input type="text" {...field} />}
                    />
                    {errors.callbackUrl && <p>{errors.callbackUrl.message}</p>}
                </div>

                <button type="submit">Submit</button>
            </form>

        </>
    );

}


builder.Services.AddCors(options => { options.AddDefaultPolicy( policy => { policy.WithOrigins("*") //.WithOrigins("https://localhost:7242") //https://localhost:3000", "https://localhost:7242 .AllowAnyHeader() .AllowAnyMethod(); }); });

Wednesday, June 14, 2023

Settiing up power monitor tool for Miniature Solar Power System



Prerequisites 


Information about Current Monitor HAT

See the link below for more details about Current/Power Monitor HAT

https://www.waveshare.com/wiki/Current/Power_Monitor_HAT


Setting up Raspberry Pi

Install python3 if it is not installed. There will be a lot of resources available on the Internet.


Demo Code from Waveshare

https://www.waveshare.com/w/upload/6/69/Current-Power_Monitor_HAT_Code.7z (see link in the Information about Current Monitor HAT section if this link not working)

Download the demo code to Raspberry Pi and extract 7z file to a folder.

Change the directory to the RaspberyPi folder in the extracted folder.

Execute Demo code as follows and then you can see the power reading as follows.


If you can see above with proper reading. your hardware setup is ready.

This demo code is logging reading to on-screen only. I did a small modification to the above to write logs into a text file (tab-delimited fields)

Copy ina219.py to your preferred folder. My folder name is esps

See the changed code below.


Now you are good to go live and it will create one text file per day.


Run the modified code from your new folder.


Files will be generated as follows.




Example file



Full Python code is as follows.


import datetime
import os
import smbus
import time

# Config Register (R/W)
_REG_CONFIG                 = 0x00
# SHUNT VOLTAGE REGISTER (R)
_REG_SHUNTVOLTAGE           = 0x01

# BUS VOLTAGE REGISTER (R)
_REG_BUSVOLTAGE             = 0x02

# POWER REGISTER (R)
_REG_POWER                  = 0x03

# CURRENT REGISTER (R)
_REG_CURRENT                = 0x04

# CALIBRATION REGISTER (R/W)
_REG_CALIBRATION            = 0x05

class BusVoltageRange:
    """Constants for ``bus_voltage_range``"""
    RANGE_16V               = 0x00      # set bus voltage range to 16V
    RANGE_32V               = 0x01      # set bus voltage range to 32V (default)

class Gain:
    """Constants for ``gain``"""
    DIV_1_40MV              = 0x00      # shunt prog. gain set to  1, 40 mV range
    DIV_2_80MV              = 0x01      # shunt prog. gain set to /2, 80 mV range
    DIV_4_160MV             = 0x02      # shunt prog. gain set to /4, 160 mV range
    DIV_8_320MV             = 0x03      # shunt prog. gain set to /8, 320 mV range

class ADCResolution:
    """Constants for ``bus_adc_resolution`` or ``shunt_adc_resolution``"""
    ADCRES_9BIT_1S          = 0x00      #  9bit,   1 sample,     84us
    ADCRES_10BIT_1S         = 0x01      # 10bit,   1 sample,    148us
    ADCRES_11BIT_1S         = 0x02      # 11 bit,  1 sample,    276us
    ADCRES_12BIT_1S         = 0x03      # 12 bit,  1 sample,    532us
    ADCRES_12BIT_2S         = 0x09      # 12 bit,  2 samples,  1.06ms
    ADCRES_12BIT_4S         = 0x0A      # 12 bit,  4 samples,  2.13ms
    ADCRES_12BIT_8S         = 0x0B      # 12bit,   8 samples,  4.26ms
    ADCRES_12BIT_16S        = 0x0C      # 12bit,  16 samples,  8.51ms
    ADCRES_12BIT_32S        = 0x0D      # 12bit,  32 samples, 17.02ms
    ADCRES_12BIT_64S        = 0x0E      # 12bit,  64 samples, 34.05ms
    ADCRES_12BIT_128S       = 0x0F      # 12bit, 128 samples, 68.10ms

class Mode:
    """Constants for ``mode``"""
    POWERDOW                = 0x00      # power down
    SVOLT_TRIGGERED         = 0x01      # shunt voltage triggered
    BVOLT_TRIGGERED         = 0x02      # bus voltage triggered
    SANDBVOLT_TRIGGERED     = 0x03      # shunt and bus voltage triggered
    ADCOFF                  = 0x04      # ADC off
    SVOLT_CONTINUOUS        = 0x05      # shunt voltage continuous
    BVOLT_CONTINUOUS        = 0x06      # bus voltage continuous
    SANDBVOLT_CONTINUOUS    = 0x07      # shunt and bus voltage continuous


class INA219:
    def __init__(self, i2c_bus=1, addr=0x40):
        self.bus = smbus.SMBus(i2c_bus);
        self.addr = addr

        # Set chip to known config values to start
        self._cal_value = 0
        self._current_lsb = 0
        self._power_lsb = 0
        self.set_calibration_32V_2A()

    def read(self,address):
        data = self.bus.read_i2c_block_data(self.addr, address, 2)
        return ((data[0] * 256 ) + data[1])

    def write(self,address,data):
        temp = [0,0]
        temp[1] = data & 0xFF
        temp[0] =(data & 0xFF00) >> 8
        self.bus.write_i2c_block_data(self.addr,address,temp)

    def set_calibration_32V_2A(self):
        """Configures to INA219 to be able to measure up to 32V and 2A of current. Counter
           overflow occurs at 3.2A.
           ..note :: These calculations assume a 0.1 shunt ohm resistor is present
        """
        # By default we use a pretty huge range for the input voltage,
        # which probably isn't the most appropriate choice for system
        # that don't use a lot of power.  But all of the calculations
        # are shown below if you want to change the settings.  You will
        # also need to change any relevant register settings, such as
        # setting the VBUS_MAX to 16V instead of 32V, etc.

        # VBUS_MAX = 32V             (Assumes 32V, can also be set to 16V)
        # VSHUNT_MAX = 0.32          (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04)
        # RSHUNT = 0.1               (Resistor value in ohms)

        # 1. Determine max possible current
        # MaxPossible_I = VSHUNT_MAX / RSHUNT
        # MaxPossible_I = 3.2A

        # 2. Determine max expected current
        # MaxExpected_I = 2.0A

        # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit)
        # MinimumLSB = MaxExpected_I/32767
        # MinimumLSB = 0.000061              (61uA per bit)
        # MaximumLSB = MaxExpected_I/4096
        # MaximumLSB = 0,000488              (488uA per bit)

        # 4. Choose an LSB between the min and max values
        #    (Preferrably a roundish number close to MinLSB)
        # CurrentLSB = 0.0001 (100uA per bit)
        self._current_lsb = .1  # Current LSB = 100uA per bit

        # 5. Compute the calibration register
        # Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
        # Cal = 4096 (0x1000)

        self._cal_value = 4096

        # 6. Calculate the power LSB
        # PowerLSB = 20 * CurrentLSB
        # PowerLSB = 0.002 (2mW per bit)
        self._power_lsb = .002  # Power LSB = 2mW per bit

        # 7. Compute the maximum current and shunt voltage values before overflow
        #
        # Max_Current = Current_LSB * 32767
        # Max_Current = 3.2767A before overflow
        #
        # If Max_Current > Max_Possible_I then
        #    Max_Current_Before_Overflow = MaxPossible_I
        # Else
        #    Max_Current_Before_Overflow = Max_Current
        # End If
        #
        # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT
        # Max_ShuntVoltage = 0.32V
        #
        # If Max_ShuntVoltage >= VSHUNT_MAX
        #    Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX
        # Else
        #    Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage
        # End If

        # 8. Compute the Maximum Power
        # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX
        # MaximumPower = 3.2 * 32V
        # MaximumPower = 102.4W

        # Set Calibration register to 'Cal' calculated above
        self.write(_REG_CALIBRATION,self._cal_value)

        # Set Config register to take into account the settings above
        self.bus_voltage_range = BusVoltageRange.RANGE_32V
        self.gain = Gain.DIV_8_320MV
        self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_32S
        self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_32S
        self.mode = Mode.SANDBVOLT_CONTINUOUS
        self.config = self.bus_voltage_range << 13 | \
                      self.gain << 11 | \
                      self.bus_adc_resolution << 7 | \
                      self.shunt_adc_resolution << 3 | \
                      self.mode
        self.write(_REG_CONFIG,self.config)
       
    def getShuntVoltage_mV(self):
        self.write(_REG_CALIBRATION,self._cal_value)
        value = self.read(_REG_SHUNTVOLTAGE)
        if value > 32767:
            value -= 65535
        return value * 0.01
       
    def getBusVoltage_V(self):  
        self.write(_REG_CALIBRATION,self._cal_value)
        self.read(_REG_BUSVOLTAGE)
        return (self.read(_REG_BUSVOLTAGE) >> 3) * 0.004
       
    def getCurrent_mA(self):
        value = self.read(_REG_CURRENT)
        if value > 32767:
            value -= 65535
        return value * self._current_lsb
       
    def getPower_W(self):
        value = self.read(_REG_POWER)
        if value > 32767:
            value -= 65535
        return value * self._power_lsb    
   
    def append_lines_solar_gen_log(self, line):
        file_name = "solar_gen_{0}.log".format(datetime.datetime.now().strftime("%d_%m_%Y"))
        if os.path.isfile(file_name):
            with open(file_name, 'a') as file:
                file.write(line + '\n')
        else:
            with open(file_name, 'w') as file:
                file.write("Date_Time\tPSU_Voltage_V\tShunt_Voltage\tLoad_Voltage\tPower_W\tCurrent_A\n")
                file.write(line + '\n')
     
if __name__=='__main__':

    ina1 = INA219(addr=0x40)
    # ina2 = INA219(addr=0x41)
    # ina3 = INA219(addr=0x42)
    # ina4 = INA219(addr=0x43)
   
    while True:
        now = datetime.datetime.now()
       
        bus_voltage1 = ina1.getBusVoltage_V()             # voltage on V- (load side)
        shunt_voltage1 = ina1.getShuntVoltage_mV() / 1000 # voltage between V+ and V- across the shunt
        current1 = ina1.getCurrent_mA()                   # current in mA
        power1 = ina1.getPower_W()                        # power in watts

        # bus_voltage2 = ina2.getBusVoltage_V()             # voltage on V- (load side)
        # shunt_voltage2 = ina2.getShuntVoltage_mV() / 1000 # voltage between V+ and V- across the shunt
        # current2 = ina2.getCurrent_mA()                   # current in mA
        # power2 = ina2.getPower_W()                        # power in watts

        # bus_voltage3 = ina3.getBusVoltage_V()             # voltage on V- (load side)
        # shunt_voltage3 = ina3.getShuntVoltage_mV() / 1000 # voltage between V+ and V- across the shunt
        # current3 = ina3.getCurrent_mA()                   # current in mA
        # power3 = ina3.getPower_W()                        # power in watts

        # bus_voltage4 = ina4.getBusVoltage_V()             # voltage on V- (load side)
        # shunt_voltage4 = ina4.getShuntVoltage_mV() / 1000 # voltage between V+ and V- across the shunt
        # current4 = ina4.getCurrent_mA()                   # current in mA
        # power4 = ina4.getPower_W()                        # power in watts
       
        # INA219 measure bus voltage on the load side. So PSU voltage = bus_voltage + shunt_voltage
        # print("PSU Voltage:{:6.3f}V    Shunt Voltage:{:9.6f}V    Load Voltage:{:6.3f}V    Power:{:9.6f}W    Current:{:9.6f}A".format((bus_voltage1 + shunt_voltage1),(shunt_voltage1),(bus_voltage1),(power1),(current1/1000)))
        # print("PSU Voltage:{:6.3f}V    Shunt Voltage:{:9.6f}V    Load Voltage:{:6.3f}V    Power:{:9.6f}W    Current:{:9.6f}A".format((bus_voltage2 + shunt_voltage2),(shunt_voltage2),(bus_voltage2),(power2),(current2/1000)))
        # print("PSU Voltage:{:6.3f}V    Shunt Voltage:{:9.6f}V    Load Voltage:{:6.3f}V    Power:{:9.6f}W    Current:{:9.6f}A".format((bus_voltage3 + shunt_voltage3),(shunt_voltage3),(bus_voltage3),(power3),(current3/1000)))
        # print("PSU Voltage:{:6.3f}V    Shunt Voltage:{:9.6f}V    Load Voltage:{:6.3f}V    Power:{:9.6f}W    Current:{:9.6f}A".format((bus_voltage4 + shunt_voltage4),(shunt_voltage4),(bus_voltage4),(power4),(current3/1000)))
        print("")

        log_hader = "Date_Time\tPSU_Voltage_V\tShunt_Voltage\tLoad_Voltage\tPower_W\tCurrent_A"
        log_entry =  "{:}\t{:6.3f}\t{:9.6f}\t{:6.3f}\t{:9.6f}\t{:9.6f}".format(now.strftime("%d/%m/%Y %H:%M:%S"), (bus_voltage1 + shunt_voltage1),(shunt_voltage1),(bus_voltage1),(power1),(current1/1000))
        ina1.append_lines_solar_gen_log(log_entry)

        print("PSU Voltage:{:6.3f}V    Shunt Voltage:{:9.6f}V    Load Voltage:{:6.3f}V    Power:{:9.6f}W    Current:{:9.6f}A".format((bus_voltage1 + shunt_voltage1),(shunt_voltage1),(bus_voltage1),(power1),(current1/1000)))
       
        time.sleep(30)