Serhioromano / vscode-st

Extension for VS Code to support Structured Text language.
https://marketplace.visualstudio.com/items?itemName=Serhioromano.vscode-st#overview
MIT License
147 stars 28 forks source link

Add option in extension configuration to change or add supported extensions #61

Closed prutonis closed 2 weeks ago

prutonis commented 4 months ago

I work on some programs for AX9 PLC and the extension of program files is .vpl . It will be nice that the extension could be configured to support highlight for different file extensions.

github-actions[bot] commented 4 months ago

Welcome!

Thank you for finding time to write an issue and make this extension better. I`ll appreciate if you find some time to rate this extension here.

I`ll get back to your ASAP.

Serhioromano commented 3 months ago

I can add this extension. Please attach example file I want to see a content. Also note you can set any file for ST highlight. In status bar select language and choose Structured text.

Serhioromano commented 1 month ago

I work on some programs for AX9 PLC and the extension of program files is .vpl . It will be nice that the extension could be configured to support highlight for different file extensions.

@prutonis Is this a simple text file? I cannot add support if I do not have example file.

prutonis commented 2 weeks ago

@Serhioromano , bellow is an example of a VPL program:

INCLUDE rtcu.inc
// Uncomment math.inc to add math library support.
INCLUDE math.inc
INCLUDE pressens.inc
INCLUDE utils.inc

#DEFINE PHONE_ADMIN_1 "01234567"
#DEFINE PHONE_ADMIN_2 "24567890"

// Structs
STRUCT_BLOCK SbMqtt
   h            : INT;
   rc           : INT;
   ccnt         : SINT:=0;
   dcnt         : SINT:=0;
   scnt         : SINT:=0;
   status       : BOOL;
   isFallback   : BOOL := FALSE;
END_STRUCT_BLOCK;

STRUCT_BLOCK SbLog
   fd          : FILE;
   lineCt      : INT;
   readPos     : DINT := 0;
END_STRUCT_BLOCK;

//  Input variables that can be configured via the configuration dialog (These are global)
VAR_INPUT
    Ain1           : INT;       | Analog input pressure readings 4-20mA 
    Switch1        : BOOL; 
    Switch2        : BOOL;
    Switch3        : BOOL;
END_VAR;

//  Output variables that can be configured via the configuration dialog (These are global)
VAR_OUTPUT
    LED            : BOOL;      | LED that shows the program scan
    Aout4          : INT;        
END_VAR;

//  These are the global variables of the program
VAR
   psns           : PressureSensor;
   logsb          : SbLog;
   clock          : clockGet;
   CFG            : ARRAY[1..16] OF STRING;
   call           : gsmIncomingCall;           // Receives incoming Voice call
   sms            : gsmIncomingSMS;            // Receives incoming SMS messages
   stack          : array [1..50] of string;
   stacki         : int := 1;
   PJS            : ParseJSON;
   keys           : ARRAY[1..K_SIZE] OF STRING := KN_CID, KN_MQ_HOST, KN_MQ_HOST_FB, KN_MQ_USER, KN_MQ_PORT, KN_SEND_INTERVAL, KN_INACT_INTERVAL;
   mqbuf          : ARRAY[1..1024] OF SINT;
   mq             : SbMqtt;
   rxd            : mqttReceive;
   netInfo        : netGetInformation;
   iface          : SINT := 1; // 1 - Mobile data, 2 - LAN
   tim3           : TON;
   inactivityTime : DINT;
END_VAR;

FUNCTION ProcessConfig;
VAR
   status         : BOOL;
   rc             : INT;
END_VAR;
   status := LoadConfig();
   IF status THEN
      Flog(msg:="Configuration loaded successfully");
   ELSE
      Flog(msg:="Not able to load configuration. Using default values.");
      status := WriteConfig();
      IF status THEN
         Flog(msg:="Configuration written successfully");
      ELSE
         Flog(msg:="Not able to write configuration.");
      END_IF;
   END_IF;
END_FUNCTION;

FUNCTION GetJsonValue : STRING;
VAR_INPUT
   key      : STRING;
END_VAR
VAR
   i        : INT;
END_VAR;
   DebugMsg(message:="Getting JSON value for key: "+ key);
   FOR i:=1 TO PJS.size DO
      DebugMsg(message:="JSON Key: "+ PJS.keys[i] + ", Value: "+ PJS.values[i]);
      IF PJS.keys[i] = key THEN
         GetJsonValue := PJS.values[i];
         RETURN;
      END_IF;
   END_FOR;
   GetJsonValue := "";
END_FUNCTION

FUNCTION GetMethod : STRING;   
   GetMethod := GetJsonValue(key:="method");
END_FUNCTION

FUNCTION ProcessIncomingJson;
VAR_INPUT
   str           : STRING;
   topic         : STRING;
END_VAR;
VAR
   i, offset, rof   : INT;
   keyIdx, l        : INT;
   cfgProcessed     : BOOL := FALSE;
   method, ofs, s   : STRING;
   status           : BOOL;
   reqId            : STRING := "";
END_VAR;
   DebugMsg(message:="Processing topic: "+ topic + ", JSON: "+ str);
// Processing topic: v1/devices/me/rpc/request/13, JSON: {"method":"getTelemetry","params":null}
   l := strLen(str:=topic);
   IF l > 26 THEN
      tim3(trig:=OFF, pt:=inactivityTime);
      tim3(trig:=ON);
      reqId := strRight(str:=topic, length:=l-26);
      DebugMsg(message:="Request ID: "+ reqId);
      SendData(msg:=dintToStr(v:=boardTimeSinceReset()), topic:="v1/devices/me/rpc/response/"+reqId);
   END_IF;

   PJS(json:=str, parentKey := "");
   IF PJS.size > 0 THEN
      method := GetJsonValue(key:="method");
      DebugMsg(message:="Method: "+ method);
      IF method = "setConfig" THEN
         Flog(msg:="Setting configuration");
         cfgProcessed:=FALSE;
         FOR i:=1 TO PJS.size DO
            keyIdx:= GetCfgIdx(str:=PJS.keys[i], pref:="params.");
            IF keyIdx > 0 THEN
               CFG[keyIdx]:= PJS.values[i];
               cfgProcessed:=TRUE;
            END_IF;
         END_FOR;
         IF cfgProcessed THEN
            Flog(msg:="Configuration processed");
            WriteConfig();
         ELSE
            Flog(msg:="Configuration not processed");
         END_IF;
      ELSIF method = "getConfig" THEN
         Flog(msg:="Getting configuration");
         SendConfig();
      ELSIF method = "reset" THEN
         Flog(msg:="Resetting device");
         boardReset();
      ELSIF method = "getTelemetry" THEN
         Flog(msg:="Getting Telemetry");
         s := GetTelemetryJson();
         SendData(msg:=s, topic:="v1/devices/me/telemetry");
      ELSE
         Flog(msg:="Unknown method");
      END_IF;
   END_IF;
END_FUNCTION;

FUNCTION GetTelemetryJson: STRING;
   psns();
   GetTelemetryJson := "{" + 
      "sns_val:" + intToStr(v:=psns.rawValue) + "," +
      "sns_current:" + floatToStr(v:=psns.current) + "," +
      "pressure:" + floatToStr(v:=psns.pressure) + "," +
      "levelcm:" + floatToStr(v:=psns.levelcm) + "," +
      "levelm:" + floatToStr(v:=psns.levelm) + "," +
      "board_temp:" + intToStr(v:=boardTemperature()) + "," +
      "board_supply:" + intToStr(v:=boardSupplyType()) + "," +
      "board_volt:" + intToStr(v:=boardSupplyVoltage()) + 
      "}";   
END_FUNCTION

FUNCTION MqttResubscribe;
VAR
   rc             : INT;
END_VAR;

   // unsubscribe to the topic
   rc := mqttUnsubscribe(handle := mq.h, topic := "v1/devices/me/rpc/request/+");
   DebugFmt(message:="MQTT unsubscribe rc=\1", v1 := rc);
   //Sleep(delay:=100);
   // subscribe to the topic
   rc := mqttSubscribe(handle := mq.h, qos := 2, topic := "v1/devices/me/rpc/request/+");
   IF rc = 0 THEN
      rxd(data := ADDR(mqbuf), maxsize := SIZEOF(mqbuf));
   END_IF;
   DebugFmt(message:="MQTT subscribe rc=\1", v1 := rc);
END_FUNCTION;

FUNCTION MqttDisconnect;
  mqttClose(handle := mq.h);
  mq.status := FALSE;      
END_FUNCTION;

FUNCTION MqttConnect;
VAR
   mqHost         : STRING;
   mqPort         : INT;
   rc             : INT;
END_VAR;

   IF mq.ccnt > 3 THEN
      mq.ccnt := 0;
      mq.isFallback := NOT mq.isFallback;
   END_IF;
   if mq.isFallback THEN
      mqHost := CFG[K_MQ_HOST_FB];
   ELSE
      mqHost := CFG[K_MQ_HOST];
   END_IF;

   mqPort := strToInt(str:=CFG[K_MQ_PORT]);
   IF mqPort = 0 THEN
      mqPort := 1883;
   END_IF;
   mq.h := mqttOpen(ip := mqHost, port := mqPort, clientid := CFG[K_CID_NAME], username:= CFG[K_MQ_USER]);
   Flog(msg:="MQTT open=" + intToStr(v:= mq.h) + " for Host=" + mqHost + ", Port=" + intToStr(v:=mqPort) + ", Username=" + CFG[K_MQ_USER]);

   IF NOT mqttConnected(handle := mq.h) THEN
      mq.rc := mqttStatus(handle := mq.h);
      Flog(msg := "MQTT connection failed, status=" + intToStr(v:=mq.rc) + ", conncnt=" + intToStr(v:=mq.ccnt));
      mq.status := FALSE;
      mq.ccnt := mq.ccnt + 1;
      mqttClose(handle := mq.h);
   ELSE 
      mq.status := TRUE;
      mq.ccnt := 0;
      mq.dcnt := 0;
      MqttResubscribe();
   END_IF;
END_FUNCTION;

FUNCTION SendData: BOOL;
VAR_INPUT
   msg           : STRING;
   topic          : STRING;
END_VAR;
VAR
   rc             : INT;
END_VAR;

   IF mq.status THEN
      strToMemory(dst := ADDR(mqbuf), str := msg, len := strLen(str := msg));
      rc := mqttPublish(
                  handle   := mq.h,
                  topic    := topic,
                  qos      := 1,
                  retained := FALSE,
                  data     := ADDR(mqbuf),
                  size     := strLen(str := msg)
                  );
      IF rc = 0 THEN         
         Flog(msg:="MqttPublished=" + msg);
         SendData:= TRUE;
      ELSE 
         DebugFmt(message := "MqttPublished=\1, msg=" + msg, v1 := rc);
         SendData:= FALSE;
         mq.dcnt:= mq.dcnt + 1;
         IF mq.dcnt > 3 THEN
            mq.dcnt:= 0;
            mqttClose(handle := mq.h);
            mq.status:= FALSE;
         END_IF;
      END_IF;
   ELSE
      MqttConnect();
      SendData:= FALSE;
   END_IF;
END_FUNCTION;

FUNCTION SendConfig;
VAR
   s1           : STRING;
   i            : INT;
   status       : BOOL;
END_VAR;

   s1 := "{$""+keys[1]+"$":$""+CFG[1]+"$"";
   FOR i:=2 TO K_SIZE DO
      s1 := strConcat(str1:=s1, str2:=",$""+keys[i]+"$":$""+CFG[i]+"$"");
   END_FOR;
   s1 := s1 + "}";
   status := SendData(msg:=s1, topic:="v1/devices/me/attributes");
   IF status THEN
      Flog(msg:="Config sent successfully");
   ELSE
      Flog(msg:="Config not sent");
   END_IF;
END_FUNCTION;

PROGRAM main;
// These are the local variables of the program block
VAR
   rc             : INT;
   time           : DINT;
   clock          : clockGet;
   tim1, tim2     : TON;
   floatStr       : STRING;
   status         : BOOL;
   s1             : STRING;
   i              : INT;
   keyIdx         : INT;
   cfgProcessed   : BOOL;
   sendPtTime     : DINT;
   mqjson         : STRING;
END_VAR;

// The next code will only be executed once after the program starts
   DebugMsg(message:=CID_NAME + ": Program initialization");
//   Sleep(delay:=30000);
   InitializeMedia();
   Flog(msg:=CID_NAME + ": Program started");
   psns();
   pmSetSpeed(speed:=0);
   pmPowerFail(bat:=TRUE);   // Set unit behavior when external power is lost.
   boardDCOut2(enable:=ON);
   ProcessConfig();

   gsmPowerLP(power:=TRUE);

   rc:=gprsOpen(); // Open the GPRS connetion
   DebugFmt(message:="gprsOpen()=\1",v1:=rc);
   WHILE NOT gprsConnected() DO  // Wait for connection to the Internet
      Flog(msg:="Waiting for GPRS connection");
      Sleep(delay:=3000);
   END_WHILE;

   time := gsmNetworkTime();            // Set current time
   IF time > 0 THEN
      clockSet(linsec := (time+7200));
   END_IF;
   clock();
   DebugFmt(message:="Current Date: \1-\2-\3", v1:=clock.Day, v2:=clock.Month, v3:=clock.Year);
   DebugFmt(message:="Current Time: \1:\2:\3", v1:=clock.Hour, v2:=clock.Minute, v3:=clock.Second);
   tim1(trig:=OFF, pt:=5000);

   sendPtTime := strToDint(str:=CFG[K_SEND_INTERVAL]); // Convert the string to integer (sendPtTime is the time interval for sending data to the server
   sendPtTime := sendPtTime * 1000; // Convert seconds to milliseconds
   IF sendPtTime = 0 THEN
      sendPtTime := 600000; // Default value
   END_IF;

   DebugFmt(message:="sendPtTime=\4", v4:=sendPtTime);

   tim2(trig:=OFF, pt:=sendPtTime);

   inactivityTime := strToDint(str:=CFG[K_INACT_INTERVAL]); // Convert the string to integer (sendPtTime is the time interval for sending data to the server
   inactivityTime := inactivityTime * 1000; // Convert seconds to milliseconds
   IF inactivityTime = 0 THEN
      inactivityTime := 1800 * 1000; // Default value
   END_IF;

   DebugFmt(message:="inactivityTime=\4", v4:=inactivityTime);

   tim3(trig:=OFF, pt:=inactivityTime);

   tim1(trig:=ON);
   tim2(trig:=ON);
   tim3(trig:=ON);

   boardWatchdog(timeout:=900); // Set the watchdog timer to 15 minutes
   boardSetFaultReset(delay:=30); // Set the delay for the board to reset after a fault

   MqttConnect();

   //SendConfig();
BEGIN
// Code from this point until END will be executed repeatedly

   // if the call is received from the specified numbers, the RTCU unit will be reset
   call();
   IF call.status > 0 THEN
      Flog(msg:=strConcat(str1:="Call received from: ", str2:=call.phonenumber));
      IF strFind(str1:=call.phonenumber, str2:=PHONE_ADMIN_1) <> 0 or strFind(str1:=call.phonenumber, str2:=PHONE_ADMIN_2) <> 0 THEN
         Flog(msg:="BOARD RESET CALL!");
         boardReset(); // Resets the RTCU Unit
      END_IF;
   END_IF;

   sms();
   IF sms.status > 0 THEN
      Flog(msg:=strConcat(str1:="Message received from: ", str2:=sms.phonenumber));
      Flog(msg:=strConcat(str1:="Message is: ", str2:=sms.message));
      IF strFind(str1:=sms.phonenumber, str2:=PHONE_ADMIN_1) <> 0 or strFind(str1:=sms.phonenumber, str2:=PHONE_ADMIN_2) <> 0 THEN
         IF strFind(str1:=sms.message, str2:="RESET") = 1 THEN
            Flog(msg:="BOARD RESET SMS!");
            boardReset(); // Resets the RTCU Unit
         END_IF;
         ProcessIncomingJson(str:=sms.message);
      END_IF;
   END_IF;

   tim1();
   tim2();
   tim3();
   IF tim1.q THEN // read the current value of the pressure sensor
      tim1(trig:=OFF);
      LED:= NOT LED;
      psns();
      tim1(trig:=ON);
   END_IF;
   IF tim2.q THEN // send the current value of the pressure sensor to the server
      tim2(trig:=OFF);
      mqjson := GetTelemetryJson();
      status := SendData(msg:=mqjson, topic:="v1/devices/me/telemetry");
      IF status THEN
         Flog(msg:="Data sent successfully");
      ELSE
         Flog(msg:="Data not sent");
      END_IF;
      tim2(trig:=ON);
   END_IF;
   IF tim3.q THEN // check the inactivity time
      tim3(trig:=OFF);
      mq.scnt := mq.scnt + 1;
      DebugFmt(message:="scnt=\1", v1:=mq.scnt);
      IF mq.scnt > 5 THEN
         Flog(msg:="Inactivity retries exceeded limit. Resetting the board.");
         boardReset(); // Resets the RTCU Unit
      ELSE
         MqttDisconnect();
         MqttConnect();
         Flog(msg:="Inactivity time reached. Resetting mqtt connection.");
      END_IF;
      tim3(trig:=ON);
   END_IF;
   rc := mqttWaitEvent(timeout := 1);
   IF rc > 0 THEN
      rxd();
      mq.scnt := 0;
      Flog(msg:="MQTT received data: "+strFromMemory(src := ADDR(mqbuf), len := rxd.size));
      ProcessIncomingJson(str:=strFromMemory(src := ADDR(mqbuf), len := rxd.size), topic := rxd.topic);
   ELSIF rc < 0 THEN //no connection, try to resubscribe
      Flog(msg:="MQTT RX error: "+intToStr(v:=rc));
      MqttResubscribe();
   END_IF;

   boardWatchdog(); // Reset the watchdog timer
END;

END_PROGRAM;
Serhioromano commented 2 weeks ago

I am adding it.

INCLUDE and #DEFINE is not quite ST syntax. Is it part of ST for AX9?

Serhioromano commented 2 weeks ago

#DEFINE PHONE_ADMIN_1 "01234567" should be something like

VAR_GLOBAL CONSTANT
    PHONE_ADMIN_1:  STRING[20] := "01234567"
END_VAR
Serhioromano commented 2 weeks ago

done

prutonis commented 2 weeks ago

I am adding it.

INCLUDE and #DEFINE is not quite ST syntax. Is it part of ST for AX9?

DEFINE and INCLUDE are preprocessing commands like in C/C++.

More information could be found on their website: https://www.logicio.com/HTML/index.html

prutonis commented 2 weeks ago

done

Great news! Thank you a lot!