Connecting to Ness M1 panel via IP

Discussion in 'Pascal Logic Code Examples' started by Ashley, Nov 11, 2015.

  1. Ashley

    Ashley

    Joined:
    Dec 1, 2005
    Messages:
    1,524
    Likes Received:
    173
    Location:
    Adelaide, Australia
    This routine allows a Colour Touch screen or Wiser to communicate with a Ness M1 panel via IP. It currently keeps track on Zone statuses, Output changes, and Arm state change. Note it is currently Read-only and send no information to the M1.

    Note that it contains CONSTANTS, TYPES, GLOBAL VARIABLES, PROCEDURES and ONE MODULE,
    so make sure you get them in the right area :)

    Whenever a zone changes state, procedure m1ZoneStateChange is called
    Whenever an output changes, procedure m1OutputChange is called
    Whenever the armed status changes procedure m1ArmState is called
    Whenever the M! goes online/offline, procedure m1StatusChange is called

    This first lot of code does not affect any CBus group addresses.
    The second additional code will map M1 zone to a dedicated CBus lighting application, and filter PIR messages to avoid flooding the bus.
    It just replaces the blank m1ZoneStateChange procedure.

    Code:
    {Constants}
    
    //************** CHANGE THESE TO MATCH YOUR M1 IP ADDRESS AND MAXIMUM ZONE NUMBER *******************
    m1IPAddress = '192.168.1.98';
    m1ZoneMax = 64;           // Highest M1 zone number
    
    {Types}
    
    Tm1CmdStr = array[0..250] of char;
    
    {Global Variables}
    
    m1tcp: Tm1cmdStr;
    m1ZoneStates: array[1..m1ZoneMax] of boolean; //False = normal (closed), True = violated (open)
    m1ZoneDefs: array[0..m1ZoneMax] of char; // Char arrays have length in index 0!
    
    
    m1Timeout: integer;  // Number of seconds since last packet received
    m1ConCount: integer; // Number of scans since connect request
    m1Online: boolean;   // Status of M1. Off-line=false, On-line=true
    m1armMode: integer;  // 0:disarmed,1:armed away,2:armed stay,3:arrmed stay instant,4:armed night
                         // 5:armed night instant,5:armed vacation
    m1armState: integer; // 0:not ready,1:ready to arm,2:ready to force arm,3:armed, exit timer running,4:armed fully
                         // 5:force armed with zone violated,6:armed with bypass
    m1alarm: integer;    // 0: no alarm,1:Entry delay active,2:alarm abort delay active,3:fire alarm,4:medical alarm,
                         // 5: police alarm,6: burglar alarm.
    m1Armed: boolean;    // True if system armed in any mode
    m1LastArmedMode: integer; // Last armed mode. When disarmed, tells us what mode we are exiting
    
    {Initialisation}
    // ********* CHANGE THIS TO MATHC YOUR M1 CONFIGURATION ********************
    // M1 Zone definitions
    //  0=unused, 1=door, 2 = PIR, 3 = Fire Alarm
    // Mapped to CBus application
    // Zone group  1               2               3               4               '
    m1ZoneDefs := '3333222000000000002220002111211100000101111111111111111111111100';
    
    
    
    {Procedure m1ZoneStateChange}
    {
      This routine is called wherenver an M1 zone changes state
      Input is M1 zone number, zone type and new state.
      Zone type 1: door/window, 2: PIR, 3: Fire alarm
      State: False = sealed (closed), True = violated (open).
    
      Add code here to perform user specific actions in zone state changes
    }
    procedure m1ZoneStateChange(zoneNo, zoneType: integer; newState: boolean);
    begin
    
    end;
    
    {Procedure m1OutputChange}
    {
      This routine is call whenever an M1 output changes state
      Add code here to perform user specific actions.
    
      outputNo is m1 output number in range 1-208
      outputState = false for Off and true for On
    }
    procedure m1OutputChange(outputNo: integer; outputState: boolean);
    begin
      
    end;
    
    {Procedure m1ArmStateChanged}
    {
      This routine is called whenever the M1 arm state change.
      Global variable m1ArmState gives new state.
      See Global definitions for values
      this routine currently just sets 3 scenes depending upon arm state}
    procedure m1ArmStateChanged;
    begin
      if (m1armState > 3) and not m1Armed then
      begin
        m1LastArmedMode := m1ArmMode;
        m1Armed := true;
        if (m1ArmMode = 4) or (m1ArmMode = 5) then
          SetScene("Armed Night")
        else 
          SetScene("Armed Away");
      end
      else if (m1armState = 0) and m1Armed then
      begin
        m1armed := false;
        if (m1LastArmedMode <> 4) and ((time < sunrise) or (time > sunset)) then
          SetScene("Disarmed");
      end;
    end;
    
    {Procedure M1StatusChange}
    {
      This routine is called whenever the M1 changes state. i.e. goes from on-line to off-line or
      vice-versa. It can be used to enunciate the state on the touchscreen.
      
      Currently it changes ONLINE/OFFLINE text on Security Page, and
      colour of M1 label on Home page
      }
    procedure M1StatusChange(newState: boolean);
    begin
      m1Online := newState;
      //
      // Change code here to show status on touchscreen
      // We have 2 text fields ('Online' in green and 'Offline' in red) as we change their visibility.
      // Also M1 label on home screen that changes colour
      //
      setCompBooleanProp("SECURITY", "M1Online", "Visible", m1Online);
      setCompBooleanProp("SECURITY", "M1Offline", "Visible", not m1Online);
      if m1Online then
        setCompIntegerProp("HOME", "M1Label", "Font Colour Active", clGreen)
      else
        setCompIntegerProp("HOME", "M1Label", "Font Colour Active", clRed);
    end;
    
    
    {Procedure Process M1Command}
    {
      Process a command from the M1 Security panel.
      Currently supports Zone status changes, Output status changes and Arm status changes
    }
    procedure ProcessM1Command(var cmd: Tm1CmdStr);
    var
      m1Pcmd, m1Scmd: char;
      m1ZoneNo: integer;
      m1ZoneState: boolean;
      m1zoneType: integer;
      m1OutputNo: integer;
      m1OutputState: boolean;
    begin
    //  writeln(cmd);
      if length(cmd) > 4 then
      begin
        m1Pcmd := cmd[3]; // Primary command
        m1Scmd := cmd[4]; // Secondary command
        if m1Pcmd = 'Z' then // Zone message
        begin
          if m1Scmd = 'S' then // State of all zones.
          begin
            for m1ZoneNo := 1 to m1ZoneMax do
            begin
              m1ZoneState := cmd[m1ZoneNo+4] <> '2'; // Extract current state (2=Normal);
              m1zoneType := ord(m1ZoneDefs[m1ZoneNo]) - ord('0'); // Get zone type
              if m1ZoneType > 0 then // 0 = not used
              begin
                if m1ZoneState <> m1ZoneStates[m1ZoneNo] then //Changed
                begin
                  m1ZoneStates[m1ZoneNo] := m1ZoneState; // Save new state
                  m1ZoneStateChange(m1ZoneNo, m1zoneType, m1zoneState); // Call user routine
                end;
              end
            end
          end // ZS
          else if m1Scmd = 'C' then // Single zone change
          begin
            m1ZoneNo := (ord(cmd[5])-$30) * 100 + (ord(cmd[6])-$30) * 10 + ord(cmd[7])-$30;
            m1ZoneState := cmd[8] <> '2';
            m1ZoneStates[m1ZoneNo] := m1ZoneState; // Save new state
            m1zoneType := ord(m1ZoneDefs[m1ZoneNo]) - ord('0'); // Get zone type
            m1ZoneStateChange(m1ZoneNo, m1zoneType, m1zoneState);
          end;
        end  // Z
        else if m1Pcmd = 'C' then // Outputs
        begin
          if m1Scmd = 'C' then // Output change
          begin
            m1OutputNo := (ord(cmd[5])-$30) * 100 + (ord(cmd[6])-$30) * 10 + ord(cmd[7])-$30;
            m1OutputState := cmd[8] = '1';
            m1OutputChange(m1OutputNo, m1OutputState);
          end
        end
        else if m1Pcmd = 'A' then //  Arm status. We only care about partition 1 here.
        begin
          if m1Scmd = 'S' then
          begin
            m1ArmMode := ord(m1tcp[5]) - ord('0');
            m1ArmState := ord(m1tcp[13]) - ord('0');
            m1alarm := ord(m1tcp[21]) - ord('0');
            m1ArmStateChanged;
          end;  
        end;
      end;
    end;
    
    {Main Module M1}
    
    {Read status info from Ness M1 alarm panel via TCP}
    m1Timeout := m1Timeout + 1;
    if m1Timeout = 300 then // 300 x 200mS scans = 60s = 1minute
    begin
      M1StatusChange(false); // Gone offline
      closeCLientSocket(1);
    end
    else if clientSocketError(1) <> 0 then
    begin
      LogMessage('M1 Socket Error '); 
      CloseClientSocket(1);
      M1StatusChange(false); // M1 has come on-line
      delay(2);
    end;
    If not ClientSocketConnected(1) then
    begin
      if M1ConCount = 0 then OpenClientSocket(1,M1IPAddr,2101);
      m1ConCount := m1ConCount + 1;
      if m1ConCount > 50 then
      begin
        logMessage('Can''t connect to M1');
        CloseClientSocket(1);
        m1ConCount := 0;
      end;
    end
    else
    begin
      m1ConCount := 0;
      repeat
        ReadClientSocket(1, m1tcp, #13#10);
        if m1tcp <> '' then
        begin
          if not m1Online then M1StatusChange(true); // M1 has come on-line
          m1Timeout := 0;
          processM1Command(m1tcp);
        end;
      until m1tcp = '';
    end;

    Additional code to mirror M1 zones in a dedicated Cbus lighting application

    Code:
    {Constants}
    
    // ******** CHANGE THIS TO SET YOUR CBUS APPLICATION TO MIRROR ZONE STATE CHANGES *********
    m1ZoneApplication = 60;   // CBus application zones are mapped to.
    
    {Procedure m1ZoneStateChange}
    
    {
      This routine is called wherenver an M1 zone changes state
      Input is M1 zone number, zone type and new state.
      Zone type 1: door/window, 2: PIR, 3: Fire alarm
      State: False = sealed (closed), True = violated (open).
    
      Add code here to perform user specific actions in zone state changes
    }
    procedure m1ZoneStateChange(zoneNo, zoneType: integer; newState: boolean);
    begin
      // For reeds and smoke detectors we can set the group address
      //	directly sonce they don't change very often
      // Since PIR's can trigger every second or so this could lead tolots of traffic,
      //   so we filter them by pulsing the group address for 15 seconds when the PUR is triggered.
      // Another PIR trigger within that time will just retrigger the timer.
      // We should be able to ignore the PIR going going secure because the GA will
      // time out after 15 seconds, but if some other device controls the GA the
      // timer will be cancelled and the GA could be stuck on. So whenever the PIR
      //  goes secured, we check if a timer is running, and if not, clear the GA
      // 
      if zoneType in [1,3] then // Window/Door/Smoke
        SetCBusState("Local", "M1 Zones", ZoneNo, newState)
      else if (zoneType = 2) then  // PIR triggered.
      begin
        if newState then
          PulseCBusLevel("Local", "M1 Zones", ZoneNo, 100%, "0s", "0:00:15", 0%);
        else if getCbusState("Local", "M1 Zones", ZoneNo) and (getCbusTimer("Local", "M1 Zones", ZoneNo) = -1) then
            SetCBusState("Local", "M1 Zones", ZoneNo, OFF)
     end;
    end;
     
    Last edited by a moderator: Nov 11, 2015
    Ashley, Nov 11, 2015
    #1
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.