Step by step guide: Visonic Powermax Pro integration

In this subforum you can show projects you have made, or you are busy with. Please create your own topic.

Moderator: leecollings

Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Step by step guide: Visonic Powermax Pro integration

Post by Holland »

It took quit some time to get this done, but finally success! My Visonic Powermax Pro alarm system can now communicate via Raspberry pi to Domoticz, it can read it's status, but it also possible to send commands ; arm / disarm

http://www.domoticaforum.eu/viewtopic.p ... 52&start=0 was an important source for information.

Also a warning. I have not integrated Domoticz alarm panel functionality yet. Therefore presetting the alarm system code on your system is a security risk. Make sure that you WLAN and your raspi are secure!

This guide with follow the below steps;

1. Hardware install
2. Test connection
3. Installing pmax software
4. Config Domoticz

-----------------------------
1. Hardware install

To connect the raspberry use a usb serial module; https://microkloon.nl/nl/gereedschap/11 ... ch340.html
I used a module with a ch340 chip, since I could not make it work with 2303 chip. As you can see in the above link, the price is just EUR 5.
The Powermax Pro has a port (2x5) labeled PC, see pictures below. This is the port to use, since it’s voltage is 3,3v and that is the voltage that usb-serial port can handle. Don’t use the dual serial port which is an optional component of the Powermax pro series. The serial port has a voltage of 12v, connecting the serial usb module to this port will fry the module immediatly :)
See below the nummering of the 2x5 port of the panel. 3 is ground, 5 TxD for the panel, 7 is RxD fort the panel. Connect ground (3) to ground on the ch340 module, TxD (5) to RxD of the ch340, RxD (7) to TxD of the ch340 module.

1 3 5 7 9
2 4 6 8 10

2. Test connection

To test the connection I made it myself a bit easier by using this Windows application; Visonic (Powermax) LV Interface ; http://www.domoticaforum.eu/viewtopic.php?f=68&t=11129
The developer, bartbakels, developed this application to let the Visonic alarm communicate with Homeseer. But you don’t need Homeseer to actually use the Visonic (Powermax) LV Interface.
Install the Visonic interface (currently version 1.0.4) http://www.promedes.nl/Downloads/Visoni ... V1.0.4.zip, to the Visonic panel and start up the Visonic interface driver. You should be able to arm and disarm the panel via this application. If that works continue to step 3. If it does not work, stop here, check your cabling!

3. Install Pmax software and other things that are required. I'm using Domoticz image version 4834, so it;s bases on Jessie!

a. First install a relevant library by issuing this:

Code: Select all

sudo apt-get install libconfig-dev
b. Compile and install the xPL Hub by doing the following; The Hub is used to exchange xPl messages between the Pmax application and Apache2.

Code: Select all

$ wget http://www.xpl4java.org/xPL4Linux/downloads/xPLLib.tgz
$ tar xzvf xPLLib.tgz
$ cd xPLLib
$ sudo make install
$ cd examples
$ make
$ sudo cp xPL_Hub /usr/local/bin
$ sudo chmod a+rx /usr/local/bin/xPL_Hub
c. change the following file (/etc/rc.local) to let the hub start at reboot. Make sure that you insert the below before the line; exit 0

Code: Select all

/usr/local/bin/./xPL_Hub &
reboot the rasp pi, and after reboot, check by using "top" to see if xPL_Hub is in the list

d. Install the pmaxd application. Pmaxd is developed by viknet, see http://www.domoticaforum.eu/viewtopic.p ... 52&start=0

Code: Select all

git clone https://github.com/viknet365/pmaxd.git
Given that Pmaxd is not developed for the Raspberry, a couple of changes are required in the Makefile, to make sure that the pmaxd source compiles

First jump to the pmaxd directory ;

Code: Select all

cd /pmaxd
Replace the contents of Makefile by the code below; Credits should go to, ayasystems, see http://www.domoticaforum.eu/viewtopic.p ... 120#p70430

Code: Select all

# build helloworld executable when user executes "make"
CFLAGS          += -I $(SRCDIR)/FLU $(OPTIMIZE) $(DEBUG) -fsigned-char
LDFLAGS         += -fsigned-char
CC              = $(CROSS_COMPILE)gcc
all: pmaxd xplsendjson jsongetxplstate testpmaxd

install: pmaxd xplsendjson jsongetxplstate testpmaxd
	cp -f pmaxd /usr/bin/pmaxd
	cp -f testpmaxd /usr/testpmaxd
	cp -f xplsendjson.cgi /usr/lib/cgi-bin/xplsendjson.cgi
	cp -f jsongetxplstate.cgi /usr/lib/cgi-bin/jsongetxplstate.cgi

pmaxd: pmaxd.o
	$(CC) $(LDFLAGS) pmaxd.o -o pmaxd -lxPL -lconfig
pmaxd.o: pmaxd.c
	$(CC) $(CFLAGS) -c pmaxd.c
xplsendjson: xplsendjson.o
	$(CC) $(LDFLAGS) xplsendjson.o -o xplsendjson.cgi -lxPL
xplsendjson.o: xplsendjson.c
	$(CC) $(CFLAGS) -c xplsendjson.c
jsongetxplstate: jsongetxplstate.o
	$(CC) $(LDFLAGS) jsongetxplstate.o -o jsongetxplstate.cgi -lxPL
jsongetxplstate.o: jsongetxplstate.c
	$(CC) $(CFLAGS) -c jsongetxplstate.c
testpmaxd: testpmaxd.o
	$(CC) $(LDFLAGS) testpmaxd.o -o testpmaxd -lxPL -lconfig
testpmaxd.o: testpmaxd.c
	$(CC) $(CFLAGS) -c testpmaxd.c

# remove object files and executable when user executes "make clean"
clean:
	rm *.o pmaxd testpmaxd xplsendjson.cgi jsongetxplstate.cgi
Issue the command;

Code: Select all

make
You should see something similar as below

Code: Select all

gcc -I /FLU   -fsigned-char -c pmaxd.c
pmaxd.c:299:6: warning: conflicting types for ‘serialHandler’
 void serialHandler() {
      ^
In file included from pmaxd.c:23:0:
pmaxd-xpl.c:44:7: note: previous implicit declaration of ‘serialHandler’ was here
       serialHandler();
       ^
pmaxd.c:335:6: warning: conflicting types for ‘packetManager’
 void packetManager(struct PlinkBuffer  * commandBuffer) {
      ^
pmaxd.c:327:9: note: previous implicit declaration of ‘packetManager’ was here
         packetManager(&commandBuffer);
         ^
gcc -fsigned-char pmaxd.o -o pmaxd -lxPL -lconfig
gcc -I /FLU   -fsigned-char -c xplsendjson.c
gcc -fsigned-char xplsendjson.o -o xplsendjson.cgi -lxPL
gcc -I /FLU   -fsigned-char -c jsongetxplstate.c
gcc -fsigned-char jsongetxplstate.o -o jsongetxplstate.cgi -lxPL
gcc -I /FLU   -fsigned-char -c testpmaxd.c
gcc -fsigned-char testpmaxd.o -o testpmaxd -lxPL -lconfig
copy the pmaxd config file to the /etc map;

Code: Select all

sudo cp pmaxd.conf /etc
The contents of the pmaxd.conf should resemble the below;

Code: Select all

  # authenticator
    usercode=0x1234;
    device = ["/dev/ttyUSB1", "/dev/ttyUSB0"]; // Use more than 2
    zonename = ["Woonkamer", "Eetkamer", "2e Etage", "Slaapkamer"]; // Use more than 2
    restartscript = "/etc/restart.sh";
    packet_timeout = 2000;
Change the above dummy usercode (1234) to your own usercode

Time to test pmaxd

Run pmaxd by issuing the following; you should see something similar as below

./pmaxd -fvvvvvvvv

Code: Select all

pi@raspberrypi:/home/pi/pmaxd$ ./pmaxd -fvvvvvvv
 NOTICE: [Tue Mar 22 23:10:25 2016 initLog:0058]Logging initialized
 NOTICE: [Tue Mar 22 23:10:25 2016 initLog:0059]Verbose level: 7
 NOTICE: [Tue Mar 22 23:10:25 2016 main:0412]Program started by User x
 INFO: [Tue Mar 22 23:10:25 2016 main:0414]setting SID
 NOTICE: [Tue Mar 22 23:10:25 2016 main:0446]Starting......
 INFO: [Tue Mar 22 23:10:25 2016 initSerialPort:0069]there are 2 device in your config file
 INFO: [Tue Mar 22 23:10:25 2016 initSerialPort:0082]opening /dev/ttyUSB0
I have 4 zone:
 NOTICE: [Tue Mar 22 23:10:25 2016 PmaxInit:0162]zone: 0, name: Woonkamer
 NOTICE: [Tue Mar 22 23:10:25 2016 PmaxInit:0162]zone: 1, name: Slaapkamer
 NOTICE: [Tue Mar 22 23:10:25 2016 PmaxInit:0162]zone: 2, name: 2e Etage
 NOTICE: [Tue Mar 22 23:10:25 2016 PmaxInit:0162]zone: 3, name: Eetkamer
 DEBUG: [Tue Mar 22 23:10:25 2016 sendBuffer:0171]Sending the following buffer to serial TTY
 DEBUG: [Tue Mar 22 23:10:25 2016 logBuffer:0154]BufferSize: 12
 DEBUG: [Tue Mar 22 23:10:25 2016 logBuffer:0155]Buffer: A2 00 00 00 00 00 00 00 00 00 00 43
 DEBUG: [Tue Mar 22 23:10:25 2016 calculChecksum:0164]checksum: 001A
 DEBUG: [Tue Mar 22 23:10:25 2016 sendBuffer:0180]result of serial write:: 15
 DEBUG: [Tue Mar 22 23:10:25 2016 sendBuffer:0171]Sending the following buffer to serial TTY
 DEBUG: [Tue Mar 22 23:10:25 2016 logBuffer:0154]BufferSize: 12
 DEBUG: [Tue Mar 22 23:10:25 2016 logBuffer:0155]Buffer: AB 06 00 00 00 00 00 00 00 00 00 43
 DEBUG: [Tue Mar 22 23:10:25 2016 calculChecksum:0164]checksum: 000B
 DEBUG: [Tue Mar 22 23:10:25 2016 sendBuffer:0180]result of serial write:: 15
 DEBUG: [Tue Mar 22 23:10:25 2016 main:0453]Sarting main loop....
 DEBUG: [Tue Mar 22 23:10:26 2016 packetManager:0336]Timeout while waiting packet: assumig packet is complete......
 DEBUG: [Tue Mar 22 23:10:26 2016 calculChecksum:0164]checksum: 00EF
 ERR: [Tue Mar 22 23:10:26 2016 deFormatBuffer:0200]checksum NOK calculated:00EF in packet:0000
 ERR: [Tue Mar 22 23:10:26 2016 packetManager:0356]Packet not correctly formated
 DEBUG: [Tue Mar 22 23:10:26 2016 logBuffer:0154]BufferSize: 44
 ERR: [Tue Mar 22 23:10:26 2016 logBuffer:0155]Buffer: A5 0D 01 00 00 00 00 00 00 00 00 43 09 0A 0D 02 43 BA 0A 0D A5 0D 01 00 00 00 00 00 00 00 00 43 09 0A 0D 02 43 BA 0A 0D A5 0D 01 00
 DEBUG: [Tue Mar 22 23:10:26 2016 packetManager:0363]End of packet treatment
 DEBUG: [Tue Mar 22 23:10:40 2016 packetManager:0336]Timeout while waiting packet: assumig packet is complete......
 DEBUG: [Tue Mar 22 23:10:40 2016 calculChecksum:0164]checksum: 0009
 DEBUG: [Tue Mar 22 23:10:40 2016 deFormatBuffer:0196[b]]checksum OK[/b]
 DEBUG: [Tue Mar 22 23:10:40 2016 packetManager:0338]Packet received
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0154]BufferSize: 12
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0155]Buffer: A5 0D 01 00 00 00 00 00 00 00 00 43
 INFO: [Tue Mar 22 23:10:40 2016 packetManager:0350]Packet not recognized
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0154]BufferSize: 12
 INFO: [Tue Mar 22 23:10:40 2016 logBuffer:0155]Buffer: A5 0D 01 00 00 00 00 00 00 00 00 43
 DEBUG: [Tue Mar 22 23:10:40 2016 sendBuffer:0171]Sending the following buffer to serial TTY
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0154]BufferSize: 2
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0155]Buffer: 02 43
 DEBUG: [Tue Mar 22 23:10:40 2016 calculChecksum:0164]checksum: 00BA
 DEBUG: [Tue Mar 22 23:10:40 2016 sendBuffer:0180]result of serial write:: 5
 DEBUG: [Tue Mar 22 23:10:40 2016 packetManager:0363]End of packet treatment
 DEBUG: [Tue Mar 22 23:10:40 2016 packetManager:0336]Timeout while waiting packet: assumig packet is complete......
 DEBUG: [Tue Mar 22 23:10:40 2016 calculChecksum:0164]checksum: 0008
 DEBUG: [Tue Mar 22 23:10:40 2016 deFormatBuffer:0196][b]checksum OK[/b]
 DEBUG: [Tue Mar 22 23:10:40 2016 packetManager:0338]Packet received
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0154]BufferSize: 12
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0155]Buffer: A5 0D 02 00 00 00 00 00 00 00 00 43
 DEBUG: [Tue Mar 22 23:10:40 2016 findCommand:0226]Command find !!!!
 DEBUG: [Tue Mar 22 23:10:40 2016 sendBuffer:0171]Sending the following buffer to serial TTY
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0154]BufferSize: 2
 DEBUG: [Tue Mar 22 23:10:40 2016 logBuffer:0155]Buffer: 02 43
 DEBUG: [Tue Mar 22 23:10:40 2016 calculChecksum:0164]checksum: 00BA
 DEBUG: [Tue Mar 22 23:10:40 2016 sendBuffer:0180]result of serial write:: 5
 INFO: [Tue Mar 22 23:10:40 2016 PmaxStatusUpdateZoneBat:0406]Status Update : Zone state/Battery
I have boldfaced the checksum OK. That;s the relevant part of the above log. If you see Checksum Not OK, adjust the packet_timeout value in pmaxd.conf to e.g 3000, etc.

If you have also the checksum OK as above, you can do the following to arm and disarm the alarmsystem;

a
for Arm
d
for disarm
g
to get the log

The command is case sensitive.

e. Next, install Apache2

Since the pmaxd application needs to communicate with a webserver to run a few cgi scripts, Apache 2 is required.

Code: Select all

sudo apt-get install apache2 -y
From you home dir do this;

Code: Select all

cd pmaxd/www
ls -l
sudo cp *.* /var/www/html
cd ..
ls -l
sudo cp *.cgi /usr/lib/cgi-bin
Create a cgi-file with the following content just to make sure that the Apache2 is working as expected;

Code: Select all

#!/bin/bash

echo -e "Content-type: text/html\n\n"

echo "<h1>Hello World</h1>"

Code: Select all

cd /usr/lib/cgi-bin
sudo nano hello.cgi
And make the cgi file executable by doing this;

Code: Select all

sudo chmod +x /usr/lib/cgi-bin/hello.cgi
cgi is not enabled by default on an Apache2 server. To enable the cgi, do the following;

Code: Select all

sudo ln -s /etc/apache2/mods-available/cgi.load /etc/apache2/mods-enabled/cgi.load
sudo service apache2 restart
Time to test the hello script

Load the cgi file by loading the following url in your browser;

Code: Select all

http://ip/cgi-bin/hello.cgi
This should show Hello World in capital letters.

If that works you can try the following;

Change the following file (/etc/rc.local) to let the pmaxd start at reboot. Make sure that you insert the below before the line; exit 0

Code: Select all

/home/pi/pmaxd/./pmaxd &
reboot the rasp pi, and after reboot, check by using "top" to see if both xPL_Hub and pmaxd are visible in the list.

After reboot reload the following url in your browser

Code: Select all

http://ip/cgi-bin/jsongetxplstate.cgi
That should return the following json string;

{ "status":"disarmed","pmaxstatus":"disarmed","readytoarm":"","sensor":[{"zone":"Woonkamer","type":"perimeter","alert":"false","armed":"false","tamper":"false","low-battery":"false","alarm":"false"},{"zone":"Slaapkamer","type":"perimeter","alert":"false","armed":"false","tamper":"false","low-battery":"false","alarm":"false"},{"zone":"2e Etage","type":"perimeter","alert":"false","armed":"false","tamper":"false","low-battery":"false","alarm":"false"}]}

Also you can load the following website to arm and disarm the alarm. It's something to play around with. I won;t use this website, given that I want to integrate things in Domoticz;

Code: Select all

http://ip/alarm.html
4. Config Domoticz

a. Create a dummy switch with the name Visonic Alarm Status. Then copy past the below in a script_device_etc.lua script

Code: Select all

json = (loadfile "/home/pi/domoticz/scripts/lua/JSON.lua")()

local visonic=assert(io.popen('curl http://ip/cgi-bin/jsongetxplstate.cgi'))
local status = visonic:read('*all')
visonic:close()
local jsonStatus = json:decode(status)

local visonic_woonkamer = 'Visonic Alarm Status'

print("Eerste JSON stappen met LUA")
print(status)

powermaxstatus = jsonStatus['pmaxstatus']
	
commandArray = {}

print(powermaxstatus)


if (powermaxstatus == "disarmed" or powermaxstatus == "armed" or powermaxstatus == "armed-home" or powermaxstatus == "armed-away") then
	if powermaxstatus == "disarmed"  then
		print('<font color="blue">Visonic: Het alarm staat uit</font>')
		commandArray[visonic_woonkamer]='Off'
	end	
	if (powermaxstatus == "armed" or powermaxstatus == "armed-home"or powermaxstatus == "armed-away")  then
		print('<font color="red">Visonic: Het alarm is ingeschakeld!</font>')
		commandArray[visonic_woonkamer]='On'
	end
end
	
return commandArray

b. Create a dummy selector switch. Hide the off switch

Create 3 sh script with the following names, and contents. By the way, I have copied xPLSend from the /home/pi/xPLLib/examples directory to another raspiberry pi (the main server) to /home/pi/xPL directory. xPLSend will broadcast an xPL message accross the network, which the xPL Hub on the other pi will receive.

Level 10, called: Arm_Away
script:///home/pi/domoticz/scripts/visonic_alarm_arm_away.sh

Code: Select all

#!/bin/bash

/home/pi/xPL/xPLSend -c security.basic command=arm-away

Level 20, called: Arm_Home
script:///home/pi/domoticz/scripts/visonic_alarm_arm_home.sh

Code: Select all

#!/bin/bash

/home/pi/xPL/xPLSend -c security.basic command=arm-home
Level 30, called: Disarm
script:///home/pi/domoticz/scripts/visonic_alarm_disarm.sh

Code: Select all

#!/bin/bash

/home/pi/xPL/xPLSend -c security.basic command=disarm


That's all. Good luck
Attachments
IMG_0150 1.jpg
IMG_0150 1.jpg (398.59 KiB) Viewed 21275 times
IMG_0149 1.jpg
IMG_0149 1.jpg (316.97 KiB) Viewed 21275 times
IMG_0153 1.jpg
IMG_0153 1.jpg (283.59 KiB) Viewed 21277 times
Last edited by Holland on Sunday 29 May 2016 21:49, edited 41 times in total.
User avatar
Minglarn
Posts: 214
Joined: Friday 21 August 2015 19:27
Target OS: Raspberry Pi / ODroid
Domoticz version: v3.8153
Location: Stockholm / Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Minglarn »

Wounder if this is applicable for the whole PowerMax series?
Anyhow, waiting for your HOW-TO guide... =)
Good work!
When you eliminate the impossible, whatever remains, however improbable, must be the truth.” -Spock in Star Trek VI
Botap
Posts: 5
Joined: Wednesday 06 January 2016 12:19
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Botap »

Hi,

This looks great and I am really looking forward to the step by step guide on this!

thanks
Bart
Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Holland »

I have finished the guide.

@Mingglarn, it's likely that is will also work on other Powermax series. Do keep in mind, that the PC port layout is probably different
Botap
Posts: 5
Joined: Wednesday 06 January 2016 12:19
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Botap »

Hi Holland,

Thanks for the step by step guide and I managed to get this to work.
But I notice that the "script_device_etc.lua" is now executed very often because everytime a device status changes it will be executed
Not sure if there are other options for keeping the status of the alarm up to date?

Thanks
Bart
Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Holland »

@Botap. You are rather quick, well done! Just wondering, was it straight forward using this guide, or did you stumble into several issues?

I did encounter the same problem, so I solved it by changing the script from a device to a time script; so it's now called script_time_visonic_status.lua

In addition I made a few changes to the script, which ensures that other scripts can read the status of the alarm, see below. Don't forget to make a uservariable called VisonicStatus with a string value.

Code: Select all

json = (loadfile "/home/pi/domoticz/scripts/lua/JSON.lua")()

local visonic=assert(io.popen('curl http://ip/cgi-bin/jsongetxplstate.cgi'))
local status = visonic:read('*all')
visonic:close()
local jsonStatus = json:decode(status)

local visonic_woonkamer = 'Visonic Alarm Status'

--print("Eerste JSON stappen met LUA")
print(status)

powermaxstatus = jsonStatus['pmaxstatus']
	
commandArray = {}

print(powermaxstatus)


if (powermaxstatus == "disarmed" or powermaxstatus == "armed" or powermaxstatus == "armed-home" or powermaxstatus == "armed-away") then
	if powermaxstatus == "disarmed"  then
		print('<font color="blue">Visonic: Het alarm staat uit</font>')
		commandArray[visonic_woonkamer]='Off'
		if (uservariables["VisonicStatus"] == "ARMED") then
			commandArray['Variable:VisonicStatus']='DISARMED'
			print("Uservar VisonicStatus updated to DISARMED")
		end
	end	
	if (powermaxstatus == "armed" or powermaxstatus == "armed-home"or powermaxstatus == "armed-away")  then
		print('<font color="red">Visonic: Het alarm is ingeschakeld!</font>')
		commandArray[visonic_woonkamer]='On'
		if (uservariables["VisonicStatus"] == "DISARMED") then
			commandArray['Variable:VisonicStatus']='ARMED'
			print("Uservar VisonicStatus updated to ARMED")
		end
	end
end
	
return commandArray 
Botap
Posts: 5
Joined: Wednesday 06 January 2016 12:19
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Botap »

@Holland, the guide was pretty clear so that was great!
Only issue I have is that for some reason the pxamd program will not start during boot time.
I added both the xPL_Hub and the pmaxd lines to the rc.local file.
The xPL_Hub starts everytime when I reboot the pi, but the pmaxd doesn't start for some reason.
So for now I start it manually as I am still testing, but I also noticed that the pmaxd program stops after a few hours.
So that is something I need to work out.
With regards to the script, I will test with the time based version but if I understand it correctly it will only read the status every min.
Do you think it is possible that the pmaxd generate commands based on status changes?
Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Holland »

Have you checked if the executable bit is set. Also, just to be sure, recheck my post, because the pmaxd path is different from xplhub

Have you just indeed used script_time.... because a 1 minute interval is fine for me. E.g I someone switches on the alarm the whole house is turn off; lights, sonos players, central heating, tv's etc.

I haven't encountered the instability problem yet, not sure if I can help you there.

About the pmaxd status changes; pmaxd does generates commands, but then via xplsendjson.cgi (you can find that one in; /usr/lib/cgi-bin

Also look to the source of /var/www/html/alarm.html

In alarm.html xplsendjson.cgi is called, but alarm.html is quite tricky to understand for me as someone with no programming knowledge.
ayasystems
Posts: 66
Joined: Tuesday 19 April 2016 23:37
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by ayasystems »

Hi to all

I have done an Android app that talks with pmaxd cgi over Internet

Let my know if you like it

https://play.google.com/store/apps/deta ... larmviknet

I also add a new feature to the original code. When a event is luanched you can send an email with bash script.

Best regards and thanks for the mention in your post!!
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

anyone have VisonicDriver installer?
The link in first post doesn't work
http://www.promedes.nl/Downloads/Visoni ... V1.0.4.zip
Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Holland »

I tried the link again, it works.

Also keep track of this threat, http://www.domoticaforum.eu/viewtopic.p ... 9&start=60

There is a more recent version there.
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

Thanks, I tried the link now and it seems to work.
It tried the link yesterday from different computers and Internet but never worked.
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

Thanks a lot for this guide.

i have just implement it to a Powermax Complete

i am very new to Linux and have no program skills.

I was using "sudo" in front of each command, just to be sure
Problems i had:
Format of code for Pmaxd, Makefile didnt have TAB format, so I had to delete all spaces and use TAB instead in front of each "cp" and "$"
3 c, I had to

Code: Select all

cd pmaxd
before

Code: Select all

sudo cp pmaxd.conf /etc
3 d, I had to

Code: Select all

sudo chmod + /usr/lib/cgi-bin/hello.cgi
before my test worked

Problems I still have:
4 a, seems to be that JSON.lua doesnt work in my Domoticz, I get this error in LOG

Code: Select all

[string "json = (loadfile "/home/pi/domoticz/scripts/l..."]:1: attempt to call a nil value
anyone know what this mean?
I have check that the filename is 100% correct and it is there. I had to create the JSON.lua myself it wasnt included in my Domoticz-image.
Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Holland »

Thanks. Actually, although I like to share these kind of things, it was also useful for me today. There was an issue, and I could solve it within minutes by using my own guide :lol:

Thanks Flopp for pointing to the errata in the guide. I will adjust it accordingly.

About the make file. Indeed, I had the same formatting issue as you described, I was hoping that using the "code code/" option on this board that problem would be solved. Apparently not. Don't know hpw to deal with that, but inserting a few tabs is indeed the solution.

3c and 3d: I will adjust the guide for that.
Although the change executable permission command should be: sudo chmod +x /usr/lib/cgi-bin/hello.cgi

4a. The error means, most likely, that the file can't be found

That's indeed correct, you have to create it yourself. I found it on the Domoticz wiki.

Please find below my version of JSON.lua and make sure it's placed in the same directory as you other lua scripts. /home/pi/domoticz/scripts/lua

Code: Select all

-- -*- coding: utf-8 -*-
--
-- Simple JSON encoding and decoding in pure Lua.
--
-- Copyright 2010-2014 Jeffrey Friedl
-- http://regex.info/blog/
--
-- Latest version: http://regex.info/blog/lua/json
--
-- This code is released under a Creative Commons CC-BY "Attribution" License:
-- http://creativecommons.org/licenses/by/3.0/deed.en_US
--
-- It can be used for any purpose so long as the copyright notice above,
-- the web-page links above, and the 'AUTHOR_NOTE' string below are
-- maintained. Enjoy.
--
local VERSION = 20141223.14 -- version history at end of file
local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-"
 
--
-- The 'AUTHOR_NOTE' variable exists so that information about the source
-- of the package is maintained even in compiled versions. It's also
-- included in OBJDEF below mostly to quiet warnings about unused variables.
--
local OBJDEF = {
   VERSION      = VERSION,
   AUTHOR_NOTE  = AUTHOR_NOTE,
}
 
 
--
-- Simple JSON encoding and decoding in pure Lua.
-- http://www.json.org/
--
--
--   JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
--
--   local lua_value = JSON:decode(raw_json_text)
--
--   local raw_json_text    = JSON:encode(lua_table_or_value)
--   local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
--
--
--
-- DECODING (from a JSON string to a Lua table)
--
--
--   JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
--
--   local lua_value = JSON:decode(raw_json_text)
--
--   If the JSON text is for an object or an array, e.g.
--     { "what": "books", "count": 3 }
--   or
--     [ "Larry", "Curly", "Moe" ]
--
--   the result is a Lua table, e.g.
--     { what = "books", count = 3 }
--   or
--     { "Larry", "Curly", "Moe" }
--
--
--   The encode and decode routines accept an optional second argument,
--   "etc", which is not used during encoding or decoding, but upon error
--   is passed along to error handlers. It can be of any type (including nil).
--
--
--
-- ERROR HANDLING
--
--   With most errors during decoding, this code calls
--
--      JSON:onDecodeError(message, text, location, etc)
--
--   with a message about the error, and if known, the JSON text being
--   parsed and the byte count where the problem was discovered. You can
--   replace the default JSON:onDecodeError() with your own function.
--
--   The default onDecodeError() merely augments the message with data
--   about the text and the location if known (and if a second 'etc'
--   argument had been provided to decode(), its value is tacked onto the
--   message as well), and then calls JSON.assert(), which itself defaults
--   to Lua's built-in assert(), and can also be overridden.
--
--   For example, in an Adobe Lightroom plugin, you might use something like
--
--          function JSON:onDecodeError(message, text, location, etc)
--             LrErrors.throwUserError("Internal Error: invalid JSON data")
--          end
--
--   or even just
--
--          function JSON.assert(message)
--             LrErrors.throwUserError("Internal Error: " .. message)
--          end
--
--   If JSON:decode() is passed a nil, this is called instead:
--
--      JSON:onDecodeOfNilError(message, nil, nil, etc)
--
--   and if JSON:decode() is passed HTML instead of JSON, this is called:
--
--      JSON:onDecodeOfHTMLError(message, text, nil, etc)
--
--   The use of the fourth 'etc' argument allows stronger coordination
--   between decoding and error reporting, especially when you provide your
--   own error-handling routines. Continuing with the the Adobe Lightroom
--   plugin example:
--
--          function JSON:onDecodeError(message, text, location, etc)
--             local note = "Internal Error: invalid JSON data"
--             if type(etc) = 'table' and etc.photo then
--                note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName')
--             end
--             LrErrors.throwUserError(note)
--          end
--
--            :
--            :
--
--          for i, photo in ipairs(photosToProcess) do
--               :             
--               :             
--               local data = JSON:decode(someJsonText, { photo = photo })
--               :             
--               :             
--          end
--
--
--
--
--
-- DECODING AND STRICT TYPES
--
--   Because both JSON objects and JSON arrays are converted to Lua tables,
--   it's not normally possible to tell which original JSON type a
--   particular Lua table was derived from, or guarantee decode-encode
--   round-trip equivalency.
--
--   However, if you enable strictTypes, e.g.
--
--      JSON = assert(loadfile "JSON.lua")() --load the routines
--      JSON.strictTypes = true
--
--   then the Lua table resulting from the decoding of a JSON object or
--   JSON array is marked via Lua metatable, so that when re-encoded with
--   JSON:encode() it ends up as the appropriate JSON type.
--
--   (This is not the default because other routines may not work well with
--   tables that have a metatable set, for example, Lightroom API calls.)
--
--
-- ENCODING (from a lua table to a JSON string)
--
--   JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
--
--   local raw_json_text    = JSON:encode(lua_table_or_value)
--   local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
--   local custom_pretty    = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "|  ", align_keys = false })
--
--   On error during encoding, this code calls:
--
--     JSON:onEncodeError(message, etc)
--
--   which you can override in your local JSON object.
--
--   The 'etc' in the error call is the second argument to encode()
--   and encode_pretty(), or nil if it wasn't provided.
--
--
-- PRETTY-PRINTING
--
--   An optional third argument, a table of options, allows a bit of
--   configuration about how the encoding takes place:
--
--     pretty = JSON:encode(val, etc, {
--                                       pretty = true,      -- if false, no other options matter
--                                       indent = "   ",     -- this provides for a three-space indent per nesting level
--                                       align_keys = false, -- see below
--                                     })
--
--   encode() and encode_pretty() are identical except that encode_pretty()
--   provides a default options table if none given in the call:
--
--       { pretty = true, align_keys = false, indent = "  " }
--
--   For example, if
--
--      JSON:encode(data)
--
--   produces:
--
--      {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11}
--
--   then
--
--      JSON:encode_pretty(data)
--
--   produces:
--
--      {
--        "city": "Kyoto",
--        "climate": {
--          "avg_temp": 16,
--          "humidity": "high",
--          "snowfall": "minimal"
--        },
--        "country": "Japan",
--        "wards": 11
--      }
--
--   The following three lines return identical results:
--       JSON:encode_pretty(data)
--       JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = "  " })
--       JSON:encode       (data, nil, { pretty = true, align_keys = false, indent = "  " })
--
--   An example of setting your own indent string:
--
--     JSON:encode_pretty(data, nil, { pretty = true, indent = "|    " })
--
--   produces:
--
--      {
--      |    "city": "Kyoto",
--      |    "climate": {
--      |    |    "avg_temp": 16,
--      |    |    "humidity": "high",
--      |    |    "snowfall": "minimal"
--      |    },
--      |    "country": "Japan",
--      |    "wards": 11
--      }
--
--   An example of setting align_keys to true:
--
--     JSON:encode_pretty(data, nil, { pretty = true, indent = "  ", align_keys = true })
--  
--   produces:
--   
--      {
--           "city": "Kyoto",
--        "climate": {
--                     "avg_temp": 16,
--                     "humidity": "high",
--                     "snowfall": "minimal"
--                   },
--        "country": "Japan",
--          "wards": 11
--      }
--
--   which I must admit is kinda ugly, sorry. This was the default for
--   encode_pretty() prior to version 20141223.14.
--
--
--  AMBIGUOUS SITUATIONS DURING THE ENCODING
--
--   During the encode, if a Lua table being encoded contains both string
--   and numeric keys, it fits neither JSON's idea of an object, nor its
--   idea of an array. To get around this, when any string key exists (or
--   when non-positive numeric keys exist), numeric keys are converted to
--   strings.
--
--   For example, 
--     JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
--   produces the JSON object
--     {"1":"one","2":"two","3":"three","SOMESTRING":"some string"}
--
--   To prohibit this conversion and instead make it an error condition, set
--      JSON.noKeyConversion = true
--
 
 
 
 
--
-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT
--
--    assert
--    onDecodeError
--    onDecodeOfNilError
--    onDecodeOfHTMLError
--    onEncodeError
--
--  If you want to create a separate Lua JSON object with its own error handlers,
--  you can reload JSON.lua or use the :new() method.
--
---------------------------------------------------------------------------
 
local default_pretty_indent  = "  "
local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent }
 
local isArray  = { __tostring = function() return "JSON array"  end }    isArray.__index  = isArray
local isObject = { __tostring = function() return "JSON object" end }    isObject.__index = isObject
 
 
function OBJDEF:newArray(tbl)
   return setmetatable(tbl or {}, isArray)
end
 
function OBJDEF:newObject(tbl)
   return setmetatable(tbl or {}, isObject)
end
 
local function unicode_codepoint_as_utf8(codepoint)
   --
   -- codepoint is a number
   --
   if codepoint <= 127 then
      return string.char(codepoint)
 
   elseif codepoint <= 2047 then
      --
      -- 110yyyxx 10xxxxxx         <-- useful notation from http://en.wikipedia.org/wiki/Utf8
      --
      local highpart = math.floor(codepoint / 0x40)
      local lowpart  = codepoint - (0x40 * highpart)
      return string.char(0xC0 + highpart,
                         0x80 + lowpart)
 
   elseif codepoint <= 65535 then
      --
      -- 1110yyyy 10yyyyxx 10xxxxxx
      --
      local highpart  = math.floor(codepoint / 0x1000)
      local remainder = codepoint - 0x1000 * highpart
      local midpart   = math.floor(remainder / 0x40)
      local lowpart   = remainder - 0x40 * midpart
 
      highpart = 0xE0 + highpart
      midpart  = 0x80 + midpart
      lowpart  = 0x80 + lowpart
 
      --
      -- Check for an invalid character (thanks Andy R. at Adobe).
      -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
      --
      if ( highpart == 0xE0 and midpart < 0xA0 ) or
         ( highpart == 0xED and midpart > 0x9F ) or
         ( highpart == 0xF0 and midpart < 0x90 ) or
         ( highpart == 0xF4 and midpart > 0x8F )
      then
         return "?"
      else
         return string.char(highpart,
                            midpart,
                            lowpart)
      end
 
   else
      --
      -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
      --
      local highpart  = math.floor(codepoint / 0x40000)
      local remainder = codepoint - 0x40000 * highpart
      local midA      = math.floor(remainder / 0x1000)
      remainder       = remainder - 0x1000 * midA
      local midB      = math.floor(remainder / 0x40)
      local lowpart   = remainder - 0x40 * midB
 
      return string.char(0xF0 + highpart,
                         0x80 + midA,
                         0x80 + midB,
                         0x80 + lowpart)
   end
end
 
function OBJDEF:onDecodeError(message, text, location, etc)
   if text then
      if location then
         message = string.format("%s at char %d of: %s", message, location, text)
      else
         message = string.format("%s: %s", message, text)
      end
   end
 
   if etc ~= nil then
      message = message .. " (" .. OBJDEF:encode(etc) .. ")"
   end
 
   if self.assert then
      self.assert(false, message)
   else
      assert(false, message)
   end
end
 
OBJDEF.onDecodeOfNilError  = OBJDEF.onDecodeError
OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError
 
function OBJDEF:onEncodeError(message, etc)
   if etc ~= nil then
      message = message .. " (" .. OBJDEF:encode(etc) .. ")"
   end
 
   if self.assert then
      self.assert(false, message)
   else
      assert(false, message)
   end
end
 
local function grok_number(self, text, start, etc)
   --
   -- Grab the integer part
   --
   local integer_part = text:match('^-?[1-9]%d*', start)
                     or text:match("^-?0",        start)
 
   if not integer_part then
      self:onDecodeError("expected number", text, start, etc)
   end
 
   local i = start + integer_part:len()
 
   --
   -- Grab an optional decimal part
   --
   local decimal_part = text:match('^%.%d+', i) or ""
 
   i = i + decimal_part:len()
 
   --
   -- Grab an optional exponential part
   --
   local exponent_part = text:match('^[eE][-+]?%d+', i) or ""
 
   i = i + exponent_part:len()
 
   local full_number_text = integer_part .. decimal_part .. exponent_part
   local as_number = tonumber(full_number_text)
 
   if not as_number then
      self:onDecodeError("bad number", text, start, etc)
   end
 
   return as_number, i
end
 
 
local function grok_string(self, text, start, etc)
 
   if text:sub(start,start) ~= '"' then
      self:onDecodeError("expected string's opening quote", text, start, etc)
   end
 
   local i = start + 1 -- +1 to bypass the initial quote
   local text_len = text:len()
   local VALUE = ""
   while i <= text_len do
      local c = text:sub(i,i)
      if c == '"' then
         return VALUE, i + 1
      end
      if c ~= '\\' then
         VALUE = VALUE .. c
         i = i + 1
      elseif text:match('^\\b', i) then
         VALUE = VALUE .. "\b"
         i = i + 2
      elseif text:match('^\\f', i) then
         VALUE = VALUE .. "\f"
         i = i + 2
      elseif text:match('^\\n', i) then
         VALUE = VALUE .. "\n"
         i = i + 2
      elseif text:match('^\\r', i) then
         VALUE = VALUE .. "\r"
         i = i + 2
      elseif text:match('^\\t', i) then
         VALUE = VALUE .. "\t"
         i = i + 2
      else
         local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
         if hex then
            i = i + 6 -- bypass what we just read
 
            -- We have a Unicode codepoint. It could be standalone, or if in the proper range and
            -- followed by another in a specific range, it'll be a two-code surrogate pair.
            local codepoint = tonumber(hex, 16)
            if codepoint >= 0xD800 and codepoint <= 0xDBFF then
               -- it's a hi surrogate... see whether we have a following low
               local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
               if lo_surrogate then
                  i = i + 6 -- bypass the low surrogate we just read
                  codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)
               else
                  -- not a proper low, so we'll just leave the first codepoint as is and spit it out.
               end
            end
            VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)
 
         else
 
            -- just pass through what's escaped
            VALUE = VALUE .. text:match('^\\(.)', i)
            i = i + 2
         end
      end
   end
 
   self:onDecodeError("unclosed string", text, start, etc)
end
 
local function skip_whitespace(text, start)
 
   local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2
   if match_end then
      return match_end + 1
   else
      return start
   end
end
 
local grok_one -- assigned later
 
local function grok_object(self, text, start, etc)
   if text:sub(start,start) ~= '{' then
      self:onDecodeError("expected '{'", text, start, etc)
   end
 
   local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'
 
   local VALUE = self.strictTypes and self:newObject { } or { }
 
   if text:sub(i,i) == '}' then
      return VALUE, i + 1
   end
   local text_len = text:len()
   while i <= text_len do
      local key, new_i = grok_string(self, text, i, etc)
 
      i = skip_whitespace(text, new_i)
 
      if text:sub(i, i) ~= ':' then
         self:onDecodeError("expected colon", text, i, etc)
      end
 
      i = skip_whitespace(text, i + 1)
 
      local new_val, new_i = grok_one(self, text, i)
 
      VALUE[key] = new_val
 
      --
      -- Expect now either '}' to end things, or a ',' to allow us to continue.
      --
      i = skip_whitespace(text, new_i)
 
      local c = text:sub(i,i)
 
      if c == '}' then
         return VALUE, i + 1
      end
 
      if text:sub(i, i) ~= ',' then
         self:onDecodeError("expected comma or '}'", text, i, etc)
      end
 
      i = skip_whitespace(text, i + 1)
   end
 
   self:onDecodeError("unclosed '{'", text, start, etc)
end
 
local function grok_array(self, text, start, etc)
   if text:sub(start,start) ~= '[' then
      self:onDecodeError("expected '['", text, start, etc)
   end
 
   local i = skip_whitespace(text, start + 1) -- +1 to skip the '['
   local VALUE = self.strictTypes and self:newArray { } or { }
   if text:sub(i,i) == ']' then
      return VALUE, i + 1
   end
 
   local VALUE_INDEX = 1
 
   local text_len = text:len()
   while i <= text_len do
      local val, new_i = grok_one(self, text, i)
 
      -- can't table.insert(VALUE, val) here because it's a no-op if val is nil
      VALUE[VALUE_INDEX] = val
      VALUE_INDEX = VALUE_INDEX + 1
 
      i = skip_whitespace(text, new_i)
 
      --
      -- Expect now either ']' to end things, or a ',' to allow us to continue.
      --
      local c = text:sub(i,i)
      if c == ']' then
         return VALUE, i + 1
      end
      if text:sub(i, i) ~= ',' then
         self:onDecodeError("expected comma or '['", text, i, etc)
      end
      i = skip_whitespace(text, i + 1)
   end
   self:onDecodeError("unclosed '['", text, start, etc)
end
 
 
grok_one = function(self, text, start, etc)
   -- Skip any whitespace
   start = skip_whitespace(text, start)
 
   if start > text:len() then
      self:onDecodeError("unexpected end of string", text, nil, etc)
   end
 
   if text:find('^"', start) then
      return grok_string(self, text, start, etc)
 
   elseif text:find('^[-0123456789 ]', start) then
      return grok_number(self, text, start, etc)
 
   elseif text:find('^%{', start) then
      return grok_object(self, text, start, etc)
 
   elseif text:find('^%[', start) then
      return grok_array(self, text, start, etc)
 
   elseif text:find('^true', start) then
      return true, start + 4
 
   elseif text:find('^false', start) then
      return false, start + 5
 
   elseif text:find('^null', start) then
      return nil, start + 4
 
   else
      self:onDecodeError("can't parse JSON", text, start, etc)
   end
end
 
function OBJDEF:decode(text, etc)
   if type(self) ~= 'table' or self.__index ~= OBJDEF then
      OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc)
   end
 
   if text == nil then
      self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc)
   elseif type(text) ~= 'string' then
      self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc)
   end
 
   if text:match('^%s*$') then
      return nil
   end
 
   if text:match('^%s*<') then
      -- Can't be JSON... we'll assume it's HTML
      self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc)
   end
 
   --
   -- Ensure that it's not UTF-32 or UTF-16.
   -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),
   -- but this package can't handle them.
   --
   if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then
      self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc)
   end
 
   local success, value = pcall(grok_one, self, text, 1, etc)
 
   if success then
      return value
   else
      -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert.
      if self.assert then
         self.assert(false, value)
      else
         assert(false, value)
      end
      -- and if we're still here, return a nil and throw the error message on as a second arg
      return nil, value
   end
end
 
local function backslash_replacement_function(c)
   if c == "\n" then
      return "\\n"
   elseif c == "\r" then
      return "\\r"
   elseif c == "\t" then
      return "\\t"
   elseif c == "\b" then
      return "\\b"
   elseif c == "\f" then
      return "\\f"
   elseif c == '"' then
      return '\\"'
   elseif c == '\\' then
      return '\\\\'
   else
      return string.format("\\u%04x", c:byte())
   end
end
 
local chars_to_be_escaped_in_JSON_string
   = '['
   ..    '"'    -- class sub-pattern to match a double quote
   ..    '%\\'  -- class sub-pattern to match a backslash
   ..    '%z'   -- class sub-pattern to match a null
   ..    '\001' .. '-' .. '\031' -- class sub-pattern to match control characters
   .. ']'
 
local function json_string_literal(value)
   local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)
   return '"' .. newval .. '"'
end
 
local function object_or_array(self, T, etc)
   --
   -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON
   -- object. If there are only numbers, it's a JSON array.
   --
   -- If we'll be converting to a JSON object, we'll want to sort the keys so that the
   -- end result is deterministic.
   --
   local string_keys = { }
   local number_keys = { }
   local number_keys_must_be_strings = false
   local maximum_number_key
 
   for key in pairs(T) do
      if type(key) == 'string' then
         table.insert(string_keys, key)
      elseif type(key) == 'number' then
         table.insert(number_keys, key)
         if key <= 0 or key >= math.huge then
            number_keys_must_be_strings = true
         elseif not maximum_number_key or key > maximum_number_key then
            maximum_number_key = key
         end
      else
         self:onEncodeError("can't encode table with a key of type " .. type(key), etc)
      end
   end
 
   if #string_keys == 0 and not number_keys_must_be_strings then
      --
      -- An empty table, or a numeric-only array
      --
      if #number_keys > 0 then
         return nil, maximum_number_key -- an array
      elseif tostring(T) == "JSON array" then
         return nil
      elseif tostring(T) == "JSON object" then
         return { }
      else
         -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects
         return nil
      end
   end
 
   table.sort(string_keys)
 
   local map
   if #number_keys > 0 then
      --
      -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array
      -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.
      --
 
      if self.noKeyConversion then
         self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc)
      end
 
      --
      -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings
      --
      map = { }
      for key, val in pairs(T) do
         map[key] = val
      end
 
      table.sort(number_keys)
 
      --
      -- Throw numeric keys in there as strings
      --
      for _, number_key in ipairs(number_keys) do
         local string_key = tostring(number_key)
         if map[string_key] == nil then
            table.insert(string_keys , string_key)
            map[string_key] = T[number_key]
         else
            self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc)
         end
      end
   end
 
   return string_keys, nil, map
end
 
--
-- Encode
--
-- 'options' is nil, or a table with possible keys:
--    pretty            -- if true, return a pretty-printed version
--    indent            -- a string (usually of spaces) used to indent each nested level
--    align_keys        -- if true, align all the keys when formatting a table
--
local encode_value -- must predeclare because it calls itself
function encode_value(self, value, parents, etc, options, indent)
 
   if value == nil then
      return 'null'
 
   elseif type(value) == 'string' then
      return json_string_literal(value)
 
   elseif type(value) == 'number' then
      if value ~= value then
         --
         -- NaN (Not a Number).
         -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.
         --
         return "null"
      elseif value >= math.huge then
         --
         -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should
         -- really be a package option. Note: at least with some implementations, positive infinity
         -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is.
         -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">="
         -- case first.
         --
         return "1e+9999"
      elseif value <= -math.huge then
         --
         -- Negative infinity.
         -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.
         --
         return "-1e+9999"
      else
         return tostring(value)
      end
 
   elseif type(value) == 'boolean' then
      return tostring(value)
 
   elseif type(value) ~= 'table' then
      self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc)
 
   else
      --
      -- A table to be converted to either a JSON object or array.
      --
      local T = value
 
      if type(options) ~= 'table' then
         options = {}
      end
      if type(indent) ~= 'string' then
         indent = ""
      end
 
      if parents[T] then
         self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
      else
         parents[T] = true
      end
 
      local result_value
 
      local object_keys, maximum_number_key, map = object_or_array(self, T, etc)
      if maximum_number_key then
         --
         -- An array...
         --
         local ITEMS = { }
         for i = 1, maximum_number_key do
            table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent))
         end
 
         if options.pretty then
            result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
         else
            result_value = "["  .. table.concat(ITEMS, ",")  .. "]"
         end
 
      elseif object_keys then
         --
         -- An object
         --
         local TT = map or T
 
         if options.pretty then
 
            local KEYS = { }
            local max_key_length = 0
            for _, key in ipairs(object_keys) do
               local encoded = encode_value(self, tostring(key), parents, etc, options, indent)
               if options.align_keys then
                  max_key_length = math.max(max_key_length, #encoded)
               end
               table.insert(KEYS, encoded)
            end
            local key_indent = indent .. tostring(options.indent or "")
            local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and "  " or "")
            local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s"
 
            local COMBINED_PARTS = { }
            for i, key in ipairs(object_keys) do
               local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)
               table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
            end
            result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
 
         else
 
            local PARTS = { }
            for _, key in ipairs(object_keys) do
               local encoded_val = encode_value(self, TT[key],       parents, etc, options, indent)
               local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent)
               table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
            end
            result_value = "{" .. table.concat(PARTS, ",") .. "}"
 
         end
      else
         --
         -- An empty array/object... we'll treat it as an array, though it should really be an option
         --
         result_value = "[]"
      end
 
      parents[T] = false
      return result_value
   end
end
 
 
function OBJDEF:encode(value, etc, options)
   if type(self) ~= 'table' or self.__index ~= OBJDEF then
      OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
   end
   return encode_value(self, value, {}, etc, options or nil)
end
 
function OBJDEF:encode_pretty(value, etc, options)
   if type(self) ~= 'table' or self.__index ~= OBJDEF then
      OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
   end
   return encode_value(self, value, {}, etc, options or default_pretty_options)
end
 
function OBJDEF.__tostring()
   return "JSON encode/decode package"
end
 
OBJDEF.__index = OBJDEF
 
function OBJDEF:new(args)
   local new = { }
 
   if args then
      for key, val in pairs(args) do
         new[key] = val
      end
   end
 
   return setmetatable(new, OBJDEF)
end
 
return OBJDEF:new()
 
--
-- Version history:
--
--   20141223.14   The encode_pretty() routine produced fine results for small datasets, but isn't really
--                 appropriate for anything large, so with help from Alex Aulbach I've made the encode routines
--                 more flexible, and changed the default encode_pretty() to be more generally useful.
--
--                 Added a third 'options' argument to the encode() and encode_pretty() routines, to control
--                 how the encoding takes place.
--
--                 Updated docs to add assert() call to the loadfile() line, just as good practice so that
--                 if there is a problem loading JSON.lua, the appropriate error message will percolate up.
--
--   20140920.13   Put back (in a way that doesn't cause warnings about unused variables) the author string,
--                 so that the source of the package, and its version number, are visible in compiled copies.
--
--   20140911.12   Minor lua cleanup.
--                 Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'.
--                 (Thanks to SmugMug's David Parry for these.)
--
--   20140418.11   JSON nulls embedded within an array were being ignored, such that
--                     ["1",null,null,null,null,null,"seven"],
--                 would return
--                     {1,"seven"}
--                 It's now fixed to properly return
--                     {1, nil, nil, nil, nil, nil, "seven"}
--                 Thanks to "haddock" for catching the error.
--
--   20140116.10   The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up.
--
--   20131118.9    Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2",
--                 and this caused some problems.
--
--   20131031.8    Unified the code for encode() and encode_pretty(); they had been stupidly separate,
--                 and had of course diverged (encode_pretty didn't get the fixes that encode got, so
--                 sometimes produced incorrect results; thanks to Mattie for the heads up).
--
--                 Handle encoding tables with non-positive numeric keys (unlikely, but possible).
--
--                 If a table has both numeric and string keys, or its numeric keys are inappropriate
--                 (such as being non-positive or infinite), the numeric keys are turned into
--                 string keys appropriate for a JSON object. So, as before,
--                         JSON:encode({ "one", "two", "three" })
--                 produces the array
--                         ["one","two","three"]
--                 but now something with mixed key types like
--                         JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
--                 instead of throwing an error produces an object:
--                         {"1":"one","2":"two","3":"three","SOMESTRING":"some string"}
--
--                 To maintain the prior throw-an-error semantics, set
--                      JSON.noKeyConversion = true
--                 
--   20131004.7    Release under a Creative Commons CC-BY license, which I should have done from day one, sorry.
--
--   20130120.6    Comment update: added a link to the specific page on my blog where this code can
--                 be found, so that folks who come across the code outside of my blog can find updates
--                 more easily.
--
--   20111207.5    Added support for the 'etc' arguments, for better error reporting.
--
--   20110731.4    More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.
--
--   20110730.3    Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:
--
--                   * When encoding lua for JSON, Sparse numeric arrays are now handled by
--                     spitting out full arrays, such that
--                        JSON:encode({"one", "two", [10] = "ten"})
--                     returns
--                        ["one","two",null,null,null,null,null,null,null,"ten"]
--
--                     In 20100810.2 and earlier, only up to the first non-null value would have been retained.
--
--                   * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999".
--                     Version 20100810.2 and earlier created invalid JSON in both cases.
--
--                   * Unicode surrogate pairs are now detected when decoding JSON.
--
--   20100810.2    added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding
--
--   20100731.1    initial public release
--
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

Yeah it is now working with JSON.lua.

I don't know what I did, I copy JSON.lua(which I had been doing before) from my PC to RPi and it started to work
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

Anyone know how to read other status from Visonic, like low battery, door is open with JSON to an variable?
Last edited by Flopp on Wednesday 04 May 2016 8:10, edited 1 time in total.
Holland
Posts: 179
Joined: Friday 12 July 2013 13:53
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta Ch
Location: The Netherlands
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Holland »

To read out Visonic sensors I use the RFXtrx 868. Best to check with Bert Weijenberg.

But also check the JSON string in the first post; it shows probably all the info that you require
But for that to work properly, you need to define your zones in the pmaxd.conf file..

{ "status":"disarmed","pmaxstatus":"disarmed","readytoarm":"","sensor":[{"zone":"Woonkamer","type":"perimeter","alert":"false","armed":"false","tamper":"false","low-battery":"false","alarm":"false"},{"zone":"Slaapkamer","type":"perimeter","alert":"false","armed":"false","tamper":"false","low-battery":"false","alarm":"false"},{"zone":"2e Etage","type":"perimeter","alert":"false","armed":"false","tamper":"false","low-battery":"false","alarm":"false"}]}
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Re: Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

OK, will read about RFXtrx 868.

I have setup pmaxd.conf, but I want to use JSON.lua to read out the status of each zone, same way that you read out the status of Visonic(disarm/armed...)

EDIT: i changed my post above that I want to use JSON to read battery status etc.
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

Anyone who knows LUA/bash and Java maybe this can be a help to create a LUA or bash script to be used with Domoticz and read out status of each device that is connected to Visonic

Code: Select all

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript">

var pmaxstatus;


var myJSONXplObject = {
    "msgtype" : "xpl-cmnd",
    "msgschemaclass" : "security",
    "msgschematype" : "basic",
    "namevaluelist" : [{"name" : "command", "value":"arm-home" }]
  };
 
function armhome()  {
	 myJSONXplObject.namevaluelist=[{"name" : "command", "value":"arm-home" }];
  $.get("cgi-bin/xplsendjson.cgi",{xplpacket: JSON.stringify(myJSONXplObject)});
  setTimeout(function() {refreshstat();},250);
  
}

function armaway()  {
  myJSONXplObject.namevaluelist=[ {"name" : "command", "value":"arm-away"}];
  $.get("cgi-bin/xplsendjson.cgi",{xplpacket: JSON.stringify(myJSONXplObject)});
 setTimeout(function() {refreshstat();},250);
}

function disarm() {
	myJSONXplObject.namevaluelist=[{"name" : "command", "value":"disarm"}];
  $.get("cgi-bin/xplsendjson.cgi",{xplpacket: JSON.stringify(myJSONXplObject)});
  setTimeout(function() {refreshstat();},250);
}

function refreshstat()  {
  $.get("cgi-bin/jsongetxplstate.cgi",
    function(data){
    
    for (i=1;i<32;i++)
    {
      $('.sensor:eq(1)').remove();
    }
       
    pmaxstatus = $.parseJSON(data);
    
    $('.sensor .interior').hide(); 
    $('.sensor .perimeter').hide();
    $('.sensor .24hour').hide();
    $('.sensor .low-battery').hide();
    $('.sensor .tamper').hide();    
    $('.sensor .true').hide();
    $('.sensor .false').hide();
 
    $('#armed-away').hide();
    $('#armed-home').hide();
    $('#arming-home').hide();
    $('#arming-away').hide();
    $('#disarmed').hide();
    
     
  for (i=0;i<pmaxstatus.sensor.length;i++)
  {
    $('.sensor:last').attr('id',"zone"+i);
    $('.sensor:last').clone(true).insertAfter('.sensor:last');          
  }
  $('.sensor:last').remove();
    
  $('#'+pmaxstatus.pmaxstatus).show();   
    
  for (i=0;i<pmaxstatus.sensor.length;i++)
  { 
    $('#zone'+i+' .zone').text(pmaxstatus.sensor[i].zone);
    $('#zone'+i+' .'+pmaxstatus.sensor[i].alert+'.'+pmaxstatus.sensor[i].type ).show();
    if (pmaxstatus.sensor[i].tamper=="true")     $('#zone'+i+' .tamper').show(); 
    if (pmaxstatus.sensor[i].lowbattery=="true") $('#zone'+i+' .low-battery').show();               
  }  
    }
  );
}
Last edited by Flopp on Thursday 05 May 2016 23:54, edited 1 time in total.
Flopp
Posts: 279
Joined: Sunday 03 January 2016 14:55
Target OS: -
Domoticz version:
Location: Sweden
Contact:

Step by step guide: Visonic Powermax Pro integration

Post by Flopp »

This is what the above code will generateImage
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest