Velbus Time Sync - currently using velbus-tcp snap

I have a raspberry pi3b in the loft connected to my main Velbus setup.
The pi also has an ethernet connection to my LAN.
It’s main purpose is to allow me to run Velbuslink remotely, from my desktop PC and that works fine.

I have a few switches (VMB8PBU) that have simple time programs to turn things like towel rails on and off so these modules need to know the correct time.
Yesterday our faulty electricity meter was replaced and this meant power had to be disconnected for a short while, so I shut down the Pi. Afterwards I restarted the pi and confirm I can connect to the Velbus OK.

Today things did not turn on as expected… I queried the Velbus time at 7am and my VMB8PBU reported that the time was 10:30
It looks like the velbus-tcp is not setting the Velbus time. I thought I had configured velbus-tcp to sync time at 3am every day.

Am I missing something in velbus-tcp ? Or is there a better way to do this? A simple velbus module with a good RTC for example?

My Velbus-tcp setup is :-

pi@Pi3B:~ $ sudo snap get velbus-tcp -d
{
“logging”: {
“output”: “stream”,
“type”: “info”
},
“ntp”: {
“enabled”: true,
“synctime”: “03:00”
},
“serial”: {
“autodiscover”: true,
“port”: “/dev/ttyAMA0”
},
“tcp”: {
“auth”: false,
“authkey”: “”,
“cert”: “/var/snap/velbus-tcp/common/certificate.pem”,
“host”: “0.0.0.0”,
“pk”: “/var/snap/velbus-tcp/common/privkey.pem”,
“port”: 27015,
“relay”: true,
“ssl”: false
}
}
pi@Pi3B:~ $

I have bad news – I don’t think there is any code in velbustcp that would sync time anymore. The only reference to ntp and synctime that remain are in the example configuration file. NTP related code appears to have been removed in [SIG-1272] Restructure project (#7) · velbus/python-velbustcp@3b8e60f · GitHub.

In principle this makes sense to me as for the most part various home automation systems have support for time synchronization too that’s more flexible and reliable.

Velbus-tcp does update the bus time, from the Linux OS time

Does it really though?

I know the documentation says so but its behaviour here does not seem to be consistent with that.

I think Nagisa may be right… lots of files got deleted at rev 1272, including, it looks like, all the NTP (network time) stuff. I would not have spotted that. It would not have occurred to me to check beyond the docs and look at the code history.

I can see the point about home automation systems… and Signum.
But I am firmly of the belief that one of Velbus’ big strengths is that it does NOT rely on a supervising home automation system.

Much as I am interested in HomeAssistant etc. I personally cannot rely on a computer+OS+apps as part of this and I need another simpler way to make time consistent across my Velbus.

I can bodge it (with a DIY Arduino and RTC in a box) but I am supposed to be trying to do this without custom solutions. I note that the old VMB4PD can do this but relying on that seems a bit wrong too (the clock drifts quite a lot and the battery backup lifespan is rather short) . Maybe I just need a reliable Velbus module with a good RTC.

Time for @jeroends to come back into the spotlight then.

VelTimer is the way forward


@jeroends

What have you done with the Veltimer code?
I can’t find it :grey_question:

Stuart,
I’ll have to look deep in the catacombes to find it. The server hosting those scripts had a HD crash after those years, so I hope I’ll have a backup laying around somewhere and I’l put it on gitub.

1 Like

I thought I would give Velserv another go but it is still not working right for me.

So I disconnected velbus-tcp’s interfaces with snap disconnect.
Then tried a fresh build+install of velserv on the Pi3b connected to my VMBRSUSB.

Afterwards VelbusLink can connect to it on port 6000 OK but when I scan the bus it seems to miss some of my Velbus modules. The Navigator pane display after a scan looks like this…

It scans much faster than velbus-tcp. I wonder if that is a sign of something going wrong.
Also note that it is not missing the odd module here and there. It is instead missing whole sequential sets of replies.

I do not yet know if this is a sign that the scan commands are not being sent, or that velserv is not seeing the replies.
Velbuslink does not wait around for replies to scan commands. It just sends all 254 possible scan commands (00 to FD in hexadecimal) one after the other with 60ms gaps between; and the scan replies are handled asynchronously as they arrive back. Velbus link scans ID’s 0 to 9, then 10 to 19, then 20 to 29… and then goes back to scan 0A to 0F, 1A to 1F, 2A to 2F etc.
I don’t see any obvious pattern to the missing scan results in my case.

It is also not consistent… every scan misses different modules. These two screenshots are both of the next scan I tried (I just scrolled the window, 2nd image is not a third scan).

I should also add, this connection is all wired ethernet to the Pi3B. Nothing in this test is connected by WiFi. This connection goes from my Windows Desktop PC, to a TP-Link 16-way Gb switch, to another 24-way TP-Link switch in the loft, then to the Pi3B.
There is nothing else running on the Pi3B. I have a Putty SSH connection open to it for a terminal but I am not running anything there either… it is just sat at a shell prompt after running Velserv (sudo ./velserv -d /dev/ttyACM0 -p 6000)


I cannot reproduce this on my test rig (4 modules plus USB interface).
If I’m going to try examining logs and modifications to Velserv then I will need to test this on the live Velbus.

We don’t know much about limitations in how the modules respond… eg. does a module that is scanned expect to find clear bus space to reply within a certain timeframe? (which there would certainly normally be due to the 60ms gaps that VelbusLink leaves between scan queries).
Does queried module just give up and drop the response if it does not see clear bus space within some ms?

And do those 60ms gaps still get preserved when the queries go via Velserv?

I have a logic analyser but but it may well be easier just to try some velserv mods and see what happens. I shall try to make time to look at this over the next few days.

1 Like

May I suggest a conversation with @jeroends first

Thanks. Not sure how active he is on Velserv these days… a lot can happen in 6 years (last commit date on github) :slight_smile:

But I have just DM’d him to ask for suggestions.

1 Like

If you ever end up exploring implementing this yourself, following is the code I use for synchronizing time in node-red (should be relatively rewritable to anything else with minimum effort):

const now = new Date();
const millisecondOffset = 1000;
const now_subminute_millis = now.getSeconds() * 1000 + now.getMilliseconds();
function mod(n, m) {
  return ((n % m) + m) % m;
}
const delay = mod(60000 - millisecondOffset - now_subminute_millis, 60000);

now.setMilliseconds(now.getMilliseconds() + delay + millisecondOffset);


const minutes = now.getMinutes();
const hours = now.getHours();
const weekday = mod(now.getDay() - 1, 7);
node.send({
  payload: {
    priority: "low",
    rtr: false,
    address: 0x00,
    body: Buffer.from([0xD8, weekday, hours, minutes]),
  },
  delay: delay,
});

const day = now.getDate();
const month = now.getMonth() + 1;
const year = now.getFullYear();
node.send({
  payload: {
    priority: "low",
    rtr: false,
    address: 0x00,
    body: Buffer.from([0xB7, day, month, year >> 8, year & 0xff]),
  },
  delay: delay,
});

const stdTimezoneOffset = (d) => {
    var jan = new Date(d.getFullYear(), 0, 1);
    var jul = new Date(d.getFullYear(), 6, 1);
    return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
};

const isDST = now.getTimezoneOffset() < stdTimezoneOffset(now);
node.send({
  payload: {
    priority: "low",
    rtr: false,
    address: 0x00,
    body: Buffer.from([0xAF, isDST ? 1 : 0]),
  },
  delay: delay,
});

This produces three messages with time, date and dst setting payloads to be put onto a bus after a specific delay (necessary in order to get more than half-a-minute accuracy for the time.) Can be run whenever at arbitrary interval. You’ll need to convert the payload objects to fully encoded messages yourself, though it mostly putting bytes into right places and a checksum calculation. And yes, you’ll need a source of time, but that would’ve been a necessity with velbus-tcp too.

2 Likes

Hi

I found these in my archive

Remove the .CSV file extensions to restore to correct type

ReadMe.txt.csv (839 Bytes)
timer.c.csv (5.5 KB)
VelservTimer.zip.csv (2.6 KB)


Readme.txt content

correct compile instruction :-    gcc -Wall -o veltimeupdate timer.c -lpthread

Usage: ./veltimupdate [-apmhV]

sudo ./veltimeupdate -p 6000 -m 0

-a --address HOST IP address or hostname where to connect to as client
default is 127.0.0.1


-p --port PORT port where to connect
default is 3788


-m --mode MODE mode = 0 --> single update and exit (default mode)
mode = 1 --> update at 12h AM/PM in background


-h --help print this help and exit


-V --version print version information and exit


So you can use it as standalone (mode = 1), in this mode the velbus system will be synced with the computer system time. Or you can use it with crontab when in Mode 0.

In Mode 0, everytime the program starts it will sync Velbus with the system time of the PC.

timer.c content

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>   
#include <pthread.h>  
#include <unistd.h> 
#include <sys/time.h>
#include <getopt.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>


#define VERSION "0.2"
#define IP "127.0.0.1"

char *IP_ADDRESS = IP;
unsigned int PORT = 3788;
int sock;
int i_hour,i_min,i_sec,i_day;
unsigned int mode = 0;
  
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;



/////////////////////////
//	Time values thread
/////////////////////////

void *time_values()
{
	char outstr[200];
	time_t td;
	struct tm *tmp;
	int loop = 1;

	while(loop)
	{
		td = time(NULL);
		tmp = localtime(&td);

		strftime(outstr, sizeof(outstr), "%H", tmp);
		i_hour = atoi(outstr);
	
		strftime(outstr, sizeof(outstr), "%M", tmp);
		i_min = atoi(outstr);

		strftime(outstr, sizeof(outstr), "%S", tmp);
		i_sec = atoi(outstr);

		strftime(outstr, sizeof(outstr), "%u", tmp);
		i_day = atoi(outstr) - 1;

		usleep(100000);
	
		if (mode == 0)
		{
			loop = 0;
		}
	}
	return 0;
}

////////////////////////////////////////////////
//	Velbus functions
////////////////////////////////////////////////

unsigned char checksum(unsigned char*lpData, int nSize)
{
unsigned char c = 0;
int k = 0;

for(; k<(nSize-2); ++k)
	c += lpData[k];
return (-c);
}

int send_pakket(unsigned char prio, unsigned char rtr, unsigned char lenght, unsigned char addres, unsigned char*lpData)
{
	int k;
	unsigned char dest[30];
	dest[0]=0xf;
	dest[1]=prio;
	dest[2]=addres;
	dest[3]=(0xf0 & rtr) | (0xf & lenght);
	for (k=0; k<lenght; k++)
	{
		dest[4+k]=lpData[k];
	}
	dest[lenght+4]=checksum(dest,(lenght + 6));
	dest[lenght+5]=0x4;

	send(sock,dest,(lenght+6), 0);
	return 0;
}

int send_time ()
{
	unsigned char prio, rtr, lenght, addres;
	unsigned char message[8];
	
	struct hostent *host;
	struct sockaddr_in server_addr;
	
	host = gethostbyname(IP_ADDRESS);
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
	{
		fprintf(stderr,"Velbus time updater: error creating client socket\n");
		return 1;
	}

	server_addr.sin_family = AF_INET;     
	server_addr.sin_port = htons(PORT);   
	server_addr.sin_addr = *((struct in_addr *)host->h_addr);
	bzero(&(server_addr.sin_zero),8); 

	if (connect(sock, (struct sockaddr *)&server_addr,sizeof(struct sockaddr)) == -1) 
	{
		fprintf(stderr,"Velbus time updater: error connecting client to server\n");
		return 1;
	}
	
	addres = 0x00;
	message[0] = 0xD8;
	message[1] = i_day;
	message[2] = i_hour;
	message[3] = i_min;
	lenght = 0x4;
	rtr = 0x00;
	prio = 0xfb;
	send_pakket(prio, rtr, lenght, addres, message);
	
	close(sock);
	return 0;
}

////////////////////////////////
//	Program options control
////////////////////////////////

static void version (FILE *f, int i)
{
    fprintf(f, "Velbus time updater %s\n",VERSION);
	fprintf(f, "Copyright (C) 2017  Jeroen De Schepper\n");
    exit (i);
}

static void usage (FILE *f, char **argv, int i)
{
	fprintf(f,"Usage: %s [-apmhV]\n",argv[0]);
	fprintf(f,"\n");
	fprintf(f,"-a --address HOST       IP address or hostname where to connect to as client\n");
	fprintf(f,"                            default is 127.0.0.1\n");
	fprintf(f,"-p --port PORT          port where to connect\n");
	fprintf(f,"                            default is 3788\n");
	fprintf(f,"-m --mode MODE          mode = 0 --> single update and exit (default mode)\n");
	fprintf(f,"                        mode = 1 --> update at 12h AM/PM in background\n");
	fprintf(f,"-h --help               print this help and exit\n");
	fprintf(f,"-V --version            print version information and exit\n");
	fprintf(f,"\n");
    exit (i);
}


static struct option long_options[] = {
  { "address", required_argument, NULL, 'a'},
  { "port", required_argument, NULL, 'p' },
  { "mode", required_argument, NULL, 'm' },
  { "help", no_argument, NULL, 'h' },
  { "version", no_argument, NULL, 'V'},
  { NULL, 0, NULL, 0 } };
 

static void parse_params(int argc, char **argv)
{
    while(1) {
	int c = getopt_long (argc, argv, "a:p:m:hV", long_options, NULL);
	if (c == -1)
	    break;
	switch(c) {
		case 'a':
		IP_ADDRESS = strdup (optarg);
		break;
		case 'p':
		PORT = atoi(strdup (optarg));
		break;
		case 'm':
		mode = atoi(strdup (optarg));
		break;
	    case 'h':
		usage (stdout, argv, 0);
	    case 'V':
		version (stdout, 0);
	    exit (0);
		case '?':
		usage (stdout, argv, 0);
	}
    }
}


/////////////
//	main
/////////////

int main(int argc, char **argv)
{
	pthread_t thread1;
	int  iret1=0;
    pid_t pid, sid;
	
	parse_params (argc, argv);
	
    if (optind > argc) 
	{
		usage (stderr, argv, 1);
    }

	
    if (mode) 
	{
        pid = fork();
        if (pid < 0) 
		{
			exit(EXIT_FAILURE);
        }

        if (pid > 0) 
		{
			exit(EXIT_SUCCESS);
        }

        umask(0);

        sid = setsid();
        if (sid < 0) 
		{
            exit(EXIT_FAILURE);
        }

        if ((chdir("/")) < 0) 
		{
            exit(EXIT_FAILURE);
        }

        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
	}

	iret1 = pthread_create( &thread1, NULL, time_values, NULL);
	
	sleep(1);
	send_time();
	
	while(mode)
	{
		if ((i_hour == 12 || i_hour == 0) && i_min == 0 && i_sec == 0)
		{
			send_time();	
		}
		sleep(1);
	}

	pthread_join( thread1, NULL);	

   return 0;
}
1 Like

Thanks guys.

Was thinking I might have to just write it… so something like that would be the way to go.
I initially thought it would be easiest to just add it to VelServ… since the connection politeness has already been done there.

BUT I am puzzled by this missing scan-response thing when I try to use VelServ.

I do have a VMB#RLYN emulator running OK… both on Windows, and on Arduino and STM32. VelbusLink recognises it OK although I do not have all the timer modes working yet. I was playing with making a simple Velbus “script” platform (running Lua or Micropython)… So I could add it to that… but I am supposed to be avoiding custom solutions for the main setup:-/

In any case, I now have this VelServ “anomaly” bothering me… and I would really like to know what causes it.

Sorted!

I had Velserv verbose logging enabled to see what was going on. The extra time taken by the logging was causing the listen queue to fill up and when that happened it discarded connection requests for further scans.

Increasing the queue length from 10 to 20 sorted it.
The problem also goes away if you don’t enable logging.

1 Like

Interesting

How is that done?

Is it in the C code?

(This is fascinating, but beyond my scope)

Yes, was just a small change to the C code.

And I have added a -t switch to enable velbus time-sync too that causes it to sync at first start of velserv and again at 3:30am every day.

It is running right now in my loft. All working fine. No more clock troubles.

So now I am a happy Velserv user!
Thankyou Jeroends for making the code available on github.

I doubt anyone else needs this but if anyone wants it just let me know. The change can of course go back into github if necessary too.

That’s great news

I’m glad it’s working for you.

Yes please, I’d love to give it a try.

Oh… so that’s why I saw some .csv files earlier :slight_smile:
I’ll just email the zip to you.

1 Like

Indeed.

It’s a strange limit.