Clicky

20210406

ESP + NTP to DST

This ESP code pulls the date from NTP and calculates if today is DST. The DST algorithm is based on this post.

The output will look something like:

09:38:57.832 -> *** Connecting to SSID: {SSID}
09:38:57.832 -> ...
09:38:57.979 -> *** WiFi connected.
09:38:57.979 -> IP address: 192.168.x.111
09:38:58.032 -> DST: 1

//--- Libraries...
  //--- WiFi + NTP...
  #include <ESP8266WiFi.h>
  #include <NTPClient.h>
  #include <WiFiUdp.h> 

  //--- Date to epoch...
  #include <TimeLib.h>


//--- WiFi and network...
  const char* ssid     = "{SSID}";
  const char* password = "{password}";


//--- NTP...
  WiFiUDP ntpUDP;
  const int CETtoGMToffset = 3600; //--- Central European Time (CET) offset to GMT...
  NTPClient timeClient(ntpUDP"time.kriss.re.kr",CETtoGMToffset,60000);


//--- setup() ----------------------------------------------------------------------------
void setup(){
  

  Serial.begin(74880); //--- Prevent garbled output, show ESP8266 boot output...
  delay(500);
  Serial.println();


//--- Start WiFi...
  pinMode(LED_BUILTIN, OUTPUT);

  Serial.println("*** Connecting to SSID: " + String(ssid));
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssidpassword);

  while (WiFi.status() != WL_CONNECTED) {

    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    Serial.print(".");
    delay(50);

  }

  digitalWrite(LED_BUILTIN,HIGH);
  Serial.println("\r\n*** WiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
//--- WiFi connection established...


// Initialize a NTPClient to get time from NTP server...
  timeClient.begin();

//--- Is today in DST?
  Serial.println("DST: " + String(nowInDST()));

}    


//--- loop() ----------------------------------------------------------------------------

void loop(){
    
}


boolean nowInDST()
{

  boolean isInDST = false;

  //--- Retrieve epoch from NTP server...  
  timeClient.update();  
  unsigned long epochTime = timeClient.getEpochTime();
  
  //--- Destil current year...  
  struct tm *ptm = gmtime ((time_t *)&epochTime); 
  int y = ptm->tm_year+1900;

  //--- Calculate epoch values for start and end of DST for this year...
  unsigned long firstDSTepoch = toEpoch(y,3lastSundayOfMonth(y,3) ,2,0,0); //--- Start of CET DST in epoch...
  unsigned long lastDSTepoch  = toEpoch(y,10,lastSundayOfMonth(y,10),2,0,0); //--- End of CET DST in epoch...

  //--- Check epoch if in DST...  
  if (epochTime >= firstDSTepoch && epochTime < lastDSTepoch
  {
    isInDST = true;
  }
  
  return isInDST;

}


//--- Calculate the date of the last Sunday in a given month...
int lastSundayOfMonth(int yint M)
{
  //--- Take care of leap years...
  int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
  if (y % 4  == 0 && y % 100 != 0) {daysInMonth[1= 29;}

  //--- Last day of month M to epoch...
  unsigned long epoch = toEpoch(y,M,daysInMonth[M-1],0,0,0);

  //--- Day of week, based on ISO8601...
  int dayOfWeek = ((epoch / 86400+ 4% 7;

  //--- Calculate date of last Sunday...
  int lastSundayOfMonth = daysInMonth[M-1- dayOfWeek;
  return lastSundayOfMonth;
  
}

//--- Converts a date to epoch (TimeLib.h)...
unsigned long toEpoch(int ybyte M , byte dbyte hbyte mbyte s)
{
  
  tmElements_t tmSet;
  tmSet.Year = y - 1970;
  tmSet.Month = M;
  tmSet.Day = d;
  tmSet.Hour = h;
  tmSet.Minute = m;
  tmSet.Second = s;
  return makeTime(tmSet);
  
}

20210402

Arduino/ESP function: inEuropeanDST

There are many other, most likely better, functions to calculate if a given date/time is in European Daylight Saving Time. But the function below is simpler to understand because most calculations are done in-line. Only the "toEpoch" function is used from the "TimeLib.h" library.

This function calculates the epoch at 2AM of the last Sunday of March (start of EU DST) and the epoch at 2AM on the last Sunday of October (end of EU DST) and checks if the given date is in between (summertime) or not (wintertime).

#include <TimeLib.h>

void setup()
{
  Serial.begin(74880);
  delay(1000);
  Serial.println();

  //--- Begin of EU DST in 2021...
  Serial.println(inDST(2021,3,28,1,59,59));
  Serial.println(inDST(2021,3,28,2,0,0));

  //--- End of EU DST in 2021...
  Serial.println(inDST(2021,10,31,1,59,59));
  Serial.println(inDST(2021,10,31,2,0,0));
  
}

void loop()
{
}

boolean inDST(int yint Mint dint hint mint s)
{

  boolean isInDST = false;
  
  unsigned long parsedDate    = toEpoch(yMdhms);                   //--- Parsed date to epoch...
  unsigned long firstDSTepoch = toEpoch(y,3lastSundayOfMonth(y,3) ,2,0,0); //--- Start of DST in epoch...
  unsigned long lastDSTepoch  = toEpoch(y,10,lastSundayOfMonth(y,10),2,0,0); //--- End of DST in epoch...
  
  if (parsedDate >= firstDSTepoch && parsedDate < lastDSTepoch
  {
    isInDST = true;
  }
  
  return isInDST;

}


//--- Calculate the day of the last Sunday of a given month...
int lastSundayOfMonth(int yint M)
{
  //--- Take care of leap years...
  int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
  if (y % 4  == 0 && y % 100 != 0) {daysInMonth[1= 29;}

  //--- Last day of month M to epoch...
  unsigned long epoch = toEpoch(y,M,daysInMonth[M-1],0,0,0);

  //--- Day of week, based on ISO8601...
  int dayOfWeek = ((epoch / 86400+ 4% 7;

  //--- Calculate date of last Sunday...
  return (daysInMonth[M-1- dayOfWeek);
  
}

//--- Converts a date to epoch (TimeLib.h)...
unsigned long toEpoch(int ybyte M , byte dbyte hbyte mbyte s)
{
  
  tmElements_t tmSet;
  tmSet.Year = y - 1970;
  tmSet.Month = M;
  tmSet.Day = d;
  tmSet.Hour = h;
  tmSet.Minute = m;
  tmSet.Second = s;
  return makeTime(tmSet);
  
}

 The output shows the second before and after the change to/from DST in 2021:

07:16:31.652 ->
07:16:31.652 ->  ets Jan  8 2013,rst cause:2, boot mode:(3,6)
07:16:31.652 ->
07:16:31.652 -> load 0x4010f000, len 3584, room 16
07:16:31.652 -> tail 0
07:16:31.652 -> chksum 0xb0
07:16:31.652 -> csum 0xb0
07:16:31.652 -> v2843a5ac
07:16:31.652 -> ~ld
07:16:32.755 ->
07:16:32.755 -> 0
07:16:32.755 -> 1
07:16:32.755 -> 1
07:16:32.755 -> 0







20210204

Arduino/ESP Split function

I created the next string "Split" function for Arduino/ESP boards. The performance is OK (25ms/Split)


void setup() {
 
  Serial.begin(115200);
  delay(1000);

  Serial.println("*** Start...");

  String s = "1612248989;Random string with SPACES;192.168.2.2;192.168.3.3;64;16;ABCDE2345678987BCEF";
  Serial.println(s);


  Serial.println(Split(s,";",2));
 
  Serial.println("*** End.");
   
}

void loop() {
  // put your main code here, to run repeatedly:
}


String Split(String aString, String Delimiter, int Index) {
 
  String bString ="", fragment = "";
  int delimiterIndex, i = 0;
 
  bString = aString;  
    
  do {
    i++;
    delimiterIndex = bString.indexOf(Delimiter);
    if (i == Index) {
        fragment = bString.substring(0,delimiterIndex);
    }
    bString = bString.substring(delimiterIndex+1);
    bString.trim();   
  } while (delimiterIndex != -1);
  return fragment;

Result:

18:24:44.626 -> *** Start...
18:24:44.626 -> 1612248989;Random string with SPACES;192.168.2.2;192.168.3.3;64;16;ABCDE2345678987BCEF
18:24:44.626 -> Random string with SPACES
18:24:44.626 -> *** End.
 

20210127

Base85 encode/decode, embedded in Powershell

 The next code embbeds Base85 C#  encode/decode in Powershell.

 ---8< -----------------------------

$base85lib =
@"
using System;
using System.Text;
using System.IO;
using System.Linq;

namespace BaseN {
    /// <summary>
    /// C# implementation of ASCII85 encoding.
    /// Based on C code from http://www.stillhq.com/cgi-bin/cvsweb/ascii85/
    /// </summary>
    /// <remarks>
    /// Jeff Atwood
    /// http://www.codinghorror.com/blog/archives/000410.html
    /// Source code cloned from: https://github.com/coding-horror/ascii85
    /// Modified by Anton Krouglov: EncodeFile and DecodeFile static methods added; namespace added
    /// </remarks>
    public class Ascii85
    {
        /// <summary>
        /// Prefix mark that identifies an encoded ASCII85 string, traditionally '<~'
        /// </summary>
        public string PrefixMark = "<~";
        /// <summary>
        /// Suffix mark that identifies an encoded ASCII85 string, traditionally '~>'
        /// </summary>
        public string SuffixMark = "~>";
        /// <summary>
        /// Maximum line length for encoded ASCII85 string;
        /// set to zero for one unbroken line.
        /// </summary>
        public int LineLength = 75;
        /// <summary>
        /// Add the Prefix and Suffix marks when encoding, and enforce their presence for decoding
        /// </summary>
        public bool EnforceMarks = true;

        private const int _asciiOffset = 33;
        private readonly byte[] _encodedBlock = new byte[5];
        private readonly byte[] _decodedBlock = new byte[4];
        private uint _tuple = 0;
        private int _linePos = 0;

        private readonly uint[] pow85 = { 85*85*85*85, 85*85*85, 85*85, 85, 1 };

        /// <summary>
        /// Decodes an ASCII85 encoded string into the original binary data
        /// </summary>
        /// <param name="s">ASCII85 encoded string</param>
        /// <returns>byte array of decoded binary data</returns>
        public byte[] Decode(string s)
        {
            if (EnforceMarks)
            {
                if (!s.StartsWith(PrefixMark) | !s.EndsWith(SuffixMark))
                {
                    throw new Exception("ASCII85 encoded data should begin with '" + PrefixMark +
                        "' and end with '" + SuffixMark + "'");
                }
            }

            // strip prefix and suffix if present
            if (s.StartsWith(PrefixMark))
            {
                s = s.Substring(PrefixMark.Length);
            }
            if (s.EndsWith(SuffixMark))
            {
                s = s.Substring(0, s.Length - SuffixMark.Length);
            }

            MemoryStream ms = new MemoryStream();
            int count = 0;
            bool processChar = false;

            foreach (char c in s)
            {
                switch (c)
                {
                    case 'z':
                        if (count != 0)
                        {
                            throw new Exception("The character 'z' is invalid inside an ASCII85 block.");
                        }
                        _decodedBlock[0] = 0;
                        _decodedBlock[1] = 0;
                        _decodedBlock[2] = 0;
                        _decodedBlock[3] = 0;
                        ms.Write(_decodedBlock, 0, _decodedBlock.Length);
                        processChar = false;
                        break;
                    case '\n': case '\r': case '\t': case '\0': case '\f': case '\b':
                        processChar = false;
                        break;
                    default:
                        if (c < '!' || c > 'u')
                        {
                            throw new Exception("Bad character '" + c + "' found. ASCII85 only allows characters '!' to 'u'.");
                        }
                        processChar = true;
                        break;
                }

                if (processChar)
                {
                    _tuple += ((uint)(c - _asciiOffset) * pow85[count]);
                    count++;
                    if (count == _encodedBlock.Length)
                    {                   
                        DecodeBlock();
                        ms.Write(_decodedBlock, 0, _decodedBlock.Length);
                        _tuple = 0;
                        count = 0;
                    }                           
                }
            }

            // if we have some bytes left over at the end..
            if (count != 0)
            {
                if (count == 1)
                {
                    throw new Exception("The last block of ASCII85 data cannot be a single byte.");
                }
                count--;
                _tuple += pow85[count];
                DecodeBlock(count);
                for (int i = 0; i < count; i++)
                {
                    ms.WriteByte(_decodedBlock[i]);
                }
            }

            return ms.ToArray();
        }

        /// <summary>
        /// Encodes binary data into a plaintext ASCII85 format string
        /// </summary>
        /// <param name="ba">binary data to encode</param>
        /// <returns>ASCII85 encoded string</returns>
        public string Encode(byte[] ba)
        {
            StringBuilder sb = new StringBuilder((int)(ba.Length * (_encodedBlock.Length/_decodedBlock.Length)));
            _linePos = 0;

            if (EnforceMarks)
            {
                AppendString(sb, PrefixMark);
            }

            int count = 0;
            _tuple = 0;
            foreach (byte b in ba)
            {
                if (count >= _decodedBlock.Length - 1)
                {
                    _tuple |= b;
                    if (_tuple == 0)
                    {
                        AppendChar(sb, 'z');
                    }
                    else
                    {
                        EncodeBlock(sb);
                    }
                    _tuple = 0;
                    count = 0;
                }
                else
                {
                    _tuple |= (uint)(b << (24 - (count * 8)));
                    count++;
                }
            }

            // if we have some bytes left over at the end..
            if (count > 0)
            {
                EncodeBlock(count + 1, sb);
            }

            if (EnforceMarks)
            {
                AppendString(sb, SuffixMark);  
            }
            return sb.ToString();
        }

        private void EncodeBlock(StringBuilder sb)
        {
            EncodeBlock(_encodedBlock.Length, sb);
        }

        private void EncodeBlock(int count, StringBuilder sb)
        {
            for (int i = _encodedBlock.Length - 1; i >= 0; i--)
            {
                _encodedBlock[i] = (byte)((_tuple % 85) + _asciiOffset);
                _tuple /= 85;
            }

            for (int i = 0; i < count; i++)
            {
                char c = (char)_encodedBlock[i];
                AppendChar(sb, c);
            }

        }

        private void DecodeBlock()
        {
            DecodeBlock(_decodedBlock.Length);
        }

        private void DecodeBlock(int bytes)
        {
            for (int i = 0; i < bytes; i++)
            {
                _decodedBlock[i] = (byte)(_tuple >> 24 - (i * 8));  
            }
        }

        private void AppendString(StringBuilder sb, string s)
        {
            if (LineLength > 0 && (_linePos + s.Length > LineLength))
            {
                _linePos = 0;
                sb.Append('\n');
            }
            else
            {
                _linePos += s.Length;
            }
            sb.Append(s);
        }

        private void AppendChar(StringBuilder sb, char c)
        {
            sb.Append(c);
            _linePos++;
            if (LineLength > 0 && (_linePos >= LineLength))
            {
                _linePos = 0;
                sb.Append('\n');
            }
        }

        /// <summary>
        /// Encodes file to base85
        /// </summary>
        /// <param name="inFileName">path to source file</param>
        /// <param name="outFileName">path to result file</param>
        /// <param name="doReverse">reverse base85 straing; default - false</param>
        public static void EncodeFile(string inFileName, string outFileName, bool doReverse = false)
        {
            var encoder = new Ascii85 { EnforceMarks = false, LineLength = 0 };

            // read the file
            byte[] ba;
            using (var streamReader = new FileStream(inFileName, FileMode.Open))
            {
                ba = new byte[streamReader.Length];
                streamReader.Read(ba, 0, (int)streamReader.Length);
                streamReader.Close();
            }

            // encode it
            var encodedString = encoder.Encode(ba);
            //Console.WriteLine("file encoded in string of length " + encodedString.Length);

            var encodedBytes = Encoding.ASCII.GetBytes(encodedString);
            if (doReverse) encodedBytes = encodedBytes.AsEnumerable().Reverse().ToArray();

            // write the file
            using (var streamWriter = new FileStream(outFileName, FileMode.OpenOrCreate))
            {
                streamWriter.Write(encodedBytes, 0, encodedBytes.Length);
                streamWriter.Close();
            }
        }


        /// <summary>
        /// Decodes file from base85
        /// </summary>
        /// <param name="inFileName">path to source file</param>
        /// <param name="outFileName">path to result file</param>
        /// <param name="doReverse">reverse base85 straing; default - false</param>
        public static void DecodeFile(string inFileName, string outFileName, bool doReverse = false)
        {
            var encoder = new Ascii85 { EnforceMarks = false, LineLength = 0 };

            // read the file
            byte[] ba;
            using (var streamReader = new FileStream(inFileName, FileMode.Open))
            {
                ba = new byte[streamReader.Length];
                streamReader.Read(ba, 0, (int)streamReader.Length);
                streamReader.Close();
            }

            if (doReverse) ba = ba.AsEnumerable().Reverse().ToArray();

            var encodedString = Encoding.ASCII.GetString(ba);
        
            // decode it
            var decoded = encoder.Decode(encodedString);
            //Console.WriteLine("decoded file length " + decoded.Length);

            // write the file
            using (var streamWriter = new FileStream(outFileName, FileMode.OpenOrCreate))
            {
                streamWriter.Write(decoded, 0, decoded.Length);
                streamWriter.Close();
            }
        }
    }
}
"@

$ErrorActionPreference = 'Stop'
Add-Type -TypeDefinition $base85lib
[BaseN.Ascii85]::EncodeFile('.\test.lnk', '.\test.lnk.base85')
[BaseN.Ascii85]::DecodeFile('.\test.lnk.base85', '.\test.lnk.result');

 

20201219

CCleaner update script

I am a longtime fan of CCleaner (former CrapCleaner). There is a "free" and a paid version. One difference between free and paid is an automated update of the application. In the newer versions there is an auto update in the free version, but that will show you nag screens.

The next Powershell script checks for a new version on the CCleaner website and when a newer version is available, it will download and install. You run the script manually or by Scheduled Task.


$ccleanserVersion = Get-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object DisplayName -eq CCleaner | Select -ExpandProperty "DisplayVersion"
write-host "*** Currently installed version: $ccleanserVersion"
$ver = $ccleanserVersion.replace(".","")

$latestVersion = Invoke-WebRequest -UseBasicParsing "https://www.ccleaner.com/ccleaner/download/standard" | Select -ExpandProperty "RawContent"
$versionMatch = $latestVersion -Match "https://download.ccleaner.com/ccsetup$ver.exe"

if (!$versionMatch) {
    write-host "*** Download and install new CCleaner app..."

    $latestVersion = $latestVersion -Match "https://download.ccleaner.com/ccsetup\d{3}.exe"
    $url = $Matches[0]   
    $intallerFile = "c:\temp\ccsetup.exe"

    Import-Module BitsTransfer
    Start-BitsTransfer -Source $url -Destination $intallerFile
    
    write-Output "*** Installing CCleaner..."
    & "C:\Windows\system32\cmd.exe" "/c" "start" "/wait" "$intallerFile" "/S"

    $ccleanserVersion = Get-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object DisplayName -eq CCleaner | Select -ExpandProperty "DisplayVersion"
    write-host "*** New version: $ccleanserVersion"   
    
}
    ELSE
{
    write-Output "*** No newer version available."
}

The update looks like this:


20201202

Tweakers paywall configuration

Update: 7-Apr-2021 b

Maybe you are aware that Tweakers will deploy a paywall for "Premium" articles. For those who are not interested in T.net links to paywalled articles, you may configure this rule in your favorite content filter:

!Tweakers Plus articles
tweakers.net##.plus
tweakers.net##tr:has(td:has(div.plus))

! Email subscription option on the front page, in the news overview
tweakers.net##.top.frontpage.mainColumn > .darkBlock

! Job offers on the front page
tweakers.net##.bottom.frontpage.secondColumn > .darkBlock

! Advertisements in news articles
tweakers.net##div#layout div#contentArea > div.wrap > div:nth-child(1):has(img[referrerpolicy="unsafe-url"])
tweakers.net##div#top > div#entity > div:nth-child(1) > div:nth-child(1)

! Requests for feedback in news articles
tweakers.net##.usabilla-survey.usabilla-trigger

! Delayed advertisements loaded through JavaScript
@@||tweakers.net^$ghide

This will result in showing "public" links only. Make sure you do not suppress adds, because that is not allowed!

Real Time Web Analytics