sunnuntai, 8. syyskuu 2019

Examining 433 MHz sensor signal with SX1278/Arduino

WX station 433 MHz OOK remote sensor, channel change signal.
Arduino Due & Dragino 433 MHz Lora Shield (Semtech SX1278).

due-sx1278.jpg

remsens.jpg

ser-ouput.jpg

#include <SPI.h>

#define SERIAL_SPEED        38400
#define FREQ                433.983
#define BPS                 1800
#define STATE_SLEEP         0
#define STATE_STANDBY       1
#define STATE_FSRX          4
#define STATE_RX            5
#define S_DELAY             10
#define L_DELAY             100
#define SX_FREQ             "Frequency "
#define SX_VERSION          "Version code of the chip "
#define SX_RX               "RX..."
#define RCS_RX              "Received "
#define RCS_SEP             " / "
#define RCS_UNKNOWN_ENC     "Unknown encoding"
#define RCS_BIT             "bit "
#define RCS_PROTOCOL        "Protocol: "
#define CH_FILTER_BW        B00001100       // 25 kHz

#define DATA_PIN            7

#define RegFifo             0x00
#define RegOpMode           0x01
#define RegBitrateMsb       0x02
#define RegBitrateLsb       0x03
#define RegFrfMsb           0x06
#define RegFrfMid           0x07
#define RegFrfLsb           0x08
#define RegPaRamp           0x0a
#define RegRssiConfig       0x0e
#define RegRssiThresh       0x10
#define RegRxBw             0x12
#define RegOokPeak          0x14
#define RegPreambleDetect   0x1f
#define RegSyncConfig       0x27
#define RegPacketConfig1    0x30
#define RegPacketConfig2    0x31
#define RegPayloadLength    0x32
#define RegIrqFlags2        0x3f
#define RegDioMapping1      0x40
#define RegVersion          0x42

#define SX_OSC_FREQ         32000000
#define SX_RESET            9
#define SX_RESET_DELAY_H    5
#define SX_RESET_DELAY_L    100
#define SPI_SS              10
#define SPI_RW_DELAY        50

volatile uint32_t pulse_len = 0;

void setup() {
  Serial.begin(SERIAL_SPEED);
  if (initSX()) {
    pinMode(DATA_PIN, INPUT);
    Serial.print(F(SX_VERSION));
    Serial.print(readSPI(RegVersion));
    Serial.println();
    delay(S_DELAY);
    startFSK();
    // Set OOK
    uint8_t b = readSPI(RegOpMode);
    bitClear(b, 6);
    bitSet(b, 5);
    writeSPI(RegOpMode, b);

    // Channel filter bandwidth control
    writeSPI(RegRxBw, CH_FILTER_BW);

    setFreq(FREQ);
    Serial.print(F(SX_FREQ));
    Serial.println(FREQ, DEC);
    setBps(BPS);

    // Bits 7-6: AutoRestartRxMode, 01  On, without waiting for the PLL to re-lock
    // Bit 4: Enables the Sync word generation and detection: 0 =>  Off, 1 =>  On
    // Bit 5: Sets the polarity of the Preamble. 0  0xAA, 1  0x55
    // Bits 2-0: Size of the Sync word (SyncSize + 1)
    b = readSPI(RegSyncConfig);
    bitClear(b, 7);
    bitSet(b, 6);
    bitClear(b, 4);
    bitClear(b, 5);
    writeSPI(RegSyncConfig, b);

    // Bits 6-5: Defines DC-free encoding/decoding performed: 00->none, 01->manchester, 10->whitening
    // Bit 7: packet format, 0 = fixed length, 1 = variable length
    // Bit 4: crc calc/check, 0 = off, 1 = on
    // Bits 2-1: Defines address based filtering in Rx: 00  None (Off)
    // Bit 3: Defines the behavior of the packet handler when CRC check fails:
    // 0 => Clear FIFO and restart new packet reception. No PayloadReady interrupt issued.
    // 1 =>  Do not clear FIFO. PayloadReady interrupt issued.
    // Bit 0: Selects the CRC and whitening algorithms:
    // 0  CCITT CRC implementation with standard whitening
    // 1  IBM CRC implementation with alternate whitening
    writeSPI(RegPacketConfig1, 0);

    // Data processing mode: 0 => Continuous mode, 1 => Packet mode
    b = readSPI(RegPacketConfig2);
    bitClear(b, 6);
    writeSPI(RegPacketConfig2, b);
    
    // Set DIO mapping
    b = readSPI(RegDioMapping1);
    bitClear(b, 5);
    bitClear(b, 4);
    bitClear(b, 3);
    bitClear(b, 2);
    writeSPI(RegDioMapping1, b);

    // Bit 5: enables the Bit Synchronizer:
    // 0 -> bit sync disabled (not possible in packet mode), 1 -> bit sync enabled
    b = readSPI(RegOokPeak);
    bitClear(b, 5);
    writeSPI(RegOokPeak, b);

    // RegPreambleDetect (0x1f). Enables Preamble detector when set to 1.
    // The AGC settings supersede this bit during the startup / AGC phase.
    // Bit 7: 0 -> turned off, 1 -> turned on
    // Bits 6-5: Number of Preamble bytes to detect to trigger an interrupt.
    // 00  1 byte, 10  3 bytes, 01  2 bytes
    b = readSPI(RegPreambleDetect);
    bitClear(b, 7);
    writeSPI(RegPreambleDetect, b);

    clearFifoAndFlags();

    setState(STATE_FSRX);
    delay(L_DELAY);
    setState(STATE_RX);
    delay(S_DELAY);
    Serial.println(F(SX_RX));
    attachInterrupt(digitalPinToInterrupt(DATA_PIN), isr, CHANGE);
  }
}

void loop() {
  if ((pulse_len > 500) && (pulse_len < 3000)) {
    Serial.println(pulse_len);
  }
}

void isr() {
  static uint32_t last_irq_ti = 0;
  uint32_t t = micros();
  pulse_len = t - last_irq_ti;
  last_irq_ti = t;
}

void clearFifoAndFlags() {
  // Flag(s) and FIFO are cleared when this bit is set
  uint8_t b = readSPI(RegIrqFlags2);
  bitSet(b, 4);
  writeSPI(RegIrqFlags2, b);
  delay(S_DELAY);
}

void setBps(uint16_t bps) {
  uint16_t baudRate = SX_OSC_FREQ / bps;
  writeSPI(RegBitrateMsb, baudRate >> 8);
  writeSPI(RegBitrateLsb, baudRate & 0xff);
}

void setFreq(float f)
{
  uint32_t lf = (f * 1000000) / 61.035;
  writeSPI(RegFrfMsb, (lf >> 16) & 0xff);
  writeSPI(RegFrfMid, (lf >> 8) & 0xff);
  writeSPI(RegFrfLsb, lf & 0xff);
}

void setState(uint8_t s) {
  uint8_t b = readSPI(RegOpMode);
  b = b | s;
  writeSPI(RegOpMode, b);
}

void startFSK()
{
  setState(STATE_SLEEP);
  delay(S_DELAY);
  uint8_t b = readSPI(RegOpMode);
  bitClear(b, 7);
  writeSPI(RegOpMode, b);
}

bool initSX() {
  pinMode(SX_RESET, OUTPUT);
  resetSX();
  initSPI();
  if (readSPI(RegVersion) == 0) {
    return false;
  }
  setState(STATE_STANDBY);
  return true;
}

void resetSX() {
  digitalWrite(SX_RESET, LOW);
  delayMicroseconds(SX_RESET_DELAY_L);
  digitalWrite(SX_RESET, HIGH);
  delay(SX_RESET_DELAY_H);
}

uint8_t readSPI(uint8_t addr) {
  digitalWrite(SPI_SS, LOW);
  SPI.transfer(addr);
  delayMicroseconds(SPI_RW_DELAY);
  uint8_t v = SPI.transfer(0x00);
  digitalWrite(SPI_SS, HIGH);
  return v;
}

void writeSPI(uint8_t addr, uint8_t v) {
  digitalWrite(SPI_SS, LOW);
  SPI.transfer(addr | 0x80);
  delayMicroseconds(SPI_RW_DELAY);
  SPI.transfer(v);
  digitalWrite(SPI_SS, HIGH);
}

void initSPI()
{
  digitalWrite(SPI_SS, HIGH);
  pinMode(SPI_SS, OUTPUT);  
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
}

torstai, 29. elokuu 2019

SDR# Uniden scanner react tune

by using Uniden GLG remote command (many Uniden DMA scanners feature GLG remote protocol command) and SDRSharp net remote plugin https://github.com/EarToEarOak/SDRSharp-Net-Remote

sdrnetrmt.jpg

Uniden SQL closed
uniden-glg-sql-close.jpg

SQL open. SDR# tuned to frequency and mode reported by GLG command.
uniden-glg-sql-open.jpg

C# code. VS project: https://github.com/OH1GIU-P/Uniden-react-tune

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Web.Script.Serialization;
using System.Threading;
using System.Net.Sockets;
using System.IO;
using System.IO.Ports;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        string dataRX = string.Empty;
        string freq = string.Empty;
        bool rx = false;
        bool tcpConnect = false;
        const string APP_TITLE = "SDR# Uniden react tune";
        const string IP = "127.0.0.1";
        const string UNIDEN_CMD = "GLG\r";
        const string GLG_SQL_CLOSED = "GLG,,,,,,,,,";
        const string GLG_SQL_CLOSE_MSG = "GLG...";
        const string NO_COM_PORT_MSG = "No serial ports found";
        const string SQL_OPEN = "1";
        const string FM = "FM";
        const string NFM = "NFM";
        const string WFMB = "WFMB";
        const string WFM = "WFM";
        const int GLG_MSG_MIN_LEN = 16;
        const int GLG_FREQ = 1;
        const int GLG_MODE = 2;
        const int GLG_SQL = 8;
        const int GLG_LEN = 10;

        TcpClient tcpClient = new TcpClient();

        DetectorType _detectorType;
        Freq _freq;
        //Freq _centerFreq;

        private delegate void SetTextDeleg(string text);

        public Form1()
        {
            InitializeComponent();
            _detectorType = new DetectorType
            {
                Command = "Set",
                Method = "DetectorType",
                Value = ""
            };
            _freq = new Freq
            {
                Command = "Set",
                Method = "Frequency",
                Value = 0
            };
            //_centerFreq = new Freq
            //{
            //    Command = "Set",
            //    Method = "CenterFrequency",
            //    Value = 0
            //};

// serialPort1 speed is 38400
            string[] ports = SerialPort.GetPortNames();
            if (ports.Length > 0)
            {
                serialPort1.PortName = ports[0];
            }
            else
            {
                buttonStart.Enabled = false;
                buttonStop.Enabled = false;
                textBoxScr.Text = NO_COM_PORT_MSG;
            }
        }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            dataRX = dataRX + serialPort1.ReadExisting();
            if (rx)
            {
                if ((dataRX.Contains('\r')) && (!dataRX.Contains(GLG_SQL_CLOSED)))
                {
                    this.BeginInvoke(new SetTextDeleg(ser_DataReceived), new object[] { dataRX });
                }
                if (dataRX.Contains(GLG_SQL_CLOSED + "\r"))
                {
                    this.BeginInvoke(new SetTextDeleg(ser_DataReceived), new object[] { GLG_SQL_CLOSE_MSG });
                }
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                rx = false;
                serialPort1.DiscardOutBuffer();
                serialPort1.DiscardInBuffer();
                serialPort1.DataReceived -= serialPort1_DataReceived;
            }
        }

        private void ser_DataReceived(string data)
        {
            textBoxScr.Text = string.Empty;
            if (rx)
            {
                textBoxScr.Text = data;
                if (data.Length > GLG_MSG_MIN_LEN)
                {
                    textBoxScr.Text = string.Empty;
                    string[] lines = data.Replace(" ", "").Split(',');
                    foreach (var line in lines)
                    {
                        if (line.Trim().Length > 0)
                        {
                            textBoxScr.Text = textBoxScr.Text + line + Environment.NewLine;
                        }
                    }
                    if (lines.Length == GLG_LEN)
                    {
                        if (lines[GLG_SQL].Equals(SQL_OPEN))
                        {
                            string fs = string.Empty;
                            long f;
                            if (Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator.Equals(","))
                            {
                                fs = lines[GLG_FREQ].Replace('.', ',');
                            }
                            if (long.TryParse(fs, out f))
                            {
                                _freq.Value = f * 100;
                                _detectorType.Value = lines[GLG_MODE];
                                if (_detectorType.Value.Equals(FM))
                                {
                                    _detectorType.Value = NFM;
                                }
                                if (_detectorType.Value.Equals(WFMB))
                                {
                                    _detectorType.Value = WFM;
                                }
                                string detectorJSON = new JavaScriptSerializer().Serialize(_detectorType);
                                //string centerFreqJSON = new JavaScriptSerializer().Serialize(_centerFreq);
                                string freqJSON = new JavaScriptSerializer().Serialize(_freq);
                                textBoxScr.Text = textBoxScr.Text + detectorJSON + Environment.NewLine;
                                //textBoxScr.Text = textBoxScr.Text + centerFreqJSON + Environment.NewLine;
                                textBoxScr.Text = textBoxScr.Text + freqJSON + Environment.NewLine;

                                if ((tcpConnect) && (!freq.Equals(fs)))
                                {
                                    freq = fs;
                                    ASCIIEncoding ascii = new ASCIIEncoding();
                                    byte[] msg = ascii.GetBytes(detectorJSON);
                                    try
                                    {
                                        NetworkStream stream = tcpClient.GetStream();
                                        stream.Write(msg, 0, msg.Length);
                                        msg = ascii.GetBytes(freqJSON);
                                        stream.Write(msg, 0, msg.Length);
                                    }
                                    catch (Exception ex)
                                    {
                                        textBoxScr.Text = textBoxScr.Text + ex.Message + Environment.NewLine;
                                    }
                                }
                            }
                        }
                    }
                }
                dataRX = string.Empty;
                serialPort1.Write(UNIDEN_CMD);
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            textBoxScr.Text = string.Empty;
            dataRX = string.Empty;
            freq = string.Empty;
            try
            {
                if (!serialPort1.IsOpen)
                {
                    serialPort1.Open();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), APP_TITLE, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            tcpConnect = true;
            try
            {
                if (!tcpClient.Client.Connected)
                {
                    tcpClient.Connect(IP, Convert.ToInt32(numericUpDownTcpPort.Value));
                }
            }
            catch (Exception ex)
            {
                tcpConnect = false;
                MessageBox.Show(ex.ToString(), APP_TITLE, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            numericUpDownTcpPort.Enabled = false;
            buttonStart.Enabled = false;
            buttonStop.Enabled = true;
            rx = true;
            serialPort1.Write(UNIDEN_CMD);
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            rx = false;
            serialPort1.DiscardOutBuffer();
            serialPort1.DiscardInBuffer();
            tcpConnect = false;
            textBoxScr.Text = string.Empty;
            numericUpDownTcpPort.Enabled = true;
            buttonStart.Enabled = true;
            buttonStop.Enabled = false;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WindowsFormsApp3
{
    public class Freq
    {
        public string Command;
        public string Method;
        public long Value;
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WindowsFormsApp3
{
    public class DetectorType
    {
        public string Command;
        public string Method;
        public string Value;
    }
}

 

 

 

torstai, 8. elokuu 2019

Uniden UBC-3500XLT squelch notify in remote control

UBC-3300XLT remote control protocol could notify host when squelch is open. Command QU = inspect/set squelch notify mode. In the UBC-3500XLT remote control protocol there's no command for squelch notify. Similar function is possible by using STS or GLG command.

<COMMAND STS>
Get Current Status
=====================================================================================
Controller → Radio
① STS[\r]
Radio → Controller
① STS,[DSP_FORM],[L1_CHAR],[L1_MODE],[L2_CHAR],[L2_MODE],[L3_CHAR],[L3_MODE],
[L4_CHAR],[L4_MODE],・・・・,[L8_CHAR],[L8_MODE],[SQL],[MUT],[BAT],[WAT][\r]
[DSP_FORM] : Display Form (4 - 8dight:########)
(each # is 0 or 1)
0 means Small Font
1 means Large Font
[L1_CHAR] : Line1 Characters 16char (fixed length)
[L1_MODE] : Line1 Display Mode 16char
[L2_CHAR] : Line2 Characters 16char (fixed length)
[L2_MODE] : Line2 Display Mode 16char
[L3_CHAR] : Line3 Characters 16char (fixed length)
[L3_MODE] : Line3 Display Mode 16char
[L4_CHAR] : Line4 Characters 16char (fixed length)
[L4_MODE] : Line4 Display Mode 16char
:
:
[L8_CHAR] : Line8 Characters 16char (fixed length)
[L8_MODE] : Line8 Display Mode 16char
[SQL] : Squelch Status (0:CLOSE / 1:OPEN)
[MUT] : Mute Status (0:OFF / 1:ON)
[BAT] : Battery Low Status (0:No Alert / 1:Alert)
[WAT] : Weather Alert Status (0:No Alert / 1: Alert)
$$$: Alert SAME CODE (SAME EVENT CODE)

<COMMAND GLG>
Get Reception Status
=====================================================================================
Controller → Radio
① GLG[\r]
Radio → Controller
① GLG,[FRQ/TGID],[MOD],[ATT],[CTCSS/DCS],[NAME1],[NAME2],[NAME3],[SQL],[MUT][\r]
GLG,,,,,,,,,[\r]
[FRQ/TGID] : Frequency or TGID
[MOD] : Modulation (AM/FM/NFM/WFM)
[ATT] : Attenuation (0:OFF / 1:ON)
[CTCSS/DCS] : CTCSS/DCS Status (0-231: see CTCSS/DCS Code List)
[NAME1] : System or Search Name
[NAME2] : Group Name
[NAME3] : Channel Name
[SQL] : Squelch Status (0:CLOSE / 1:OPEN)
[MUT] : Mute Status (0:OFF / 1:ON)
Get reception status.
The Scanner returns GLG,,,,,,,,,[\r] until it detects a frequency or a TGID.

C# code for STS continuous polling

uniden3500-sts.jpg

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        string dataRX = string.Empty;
        const string CMD_STS = "STS\r";
        bool rx = false;

        private delegate void SetTextDeleg(string text);

        public Form1()
        {
            InitializeComponent();
        }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            dataRX = dataRX + serialPort1.ReadExisting();
            if (rx)
            {
                if (dataRX.Contains('\r'))
                {
                    this.BeginInvoke(new SetTextDeleg(ser_DataReceived), new object[] { dataRX });
                }
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                rx = false;
                serialPort1.DiscardInBuffer();
                serialPort1.DataReceived -= serialPort1_DataReceived;
            }
        }

        private void ser_DataReceived(string data)
        {
            textBoxScr.Text = string.Empty;
            if (rx)
            {
                string[] lines = data.Split(',');
                foreach (var line in lines)
                {
                    if (line.Trim().Length > 0)
                    {
                        textBoxScr.Text = textBoxScr.Text + line + Environment.NewLine;
                    }
                }
                dataRX = string.Empty;
                serialPort1.Write(CMD_STS);
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            textBoxScr.Text = string.Empty;
            dataRX = string.Empty;
            if (!serialPort1.IsOpen)
            {
                serialPort1.Open();
            }
            rx = true;
            serialPort1.DiscardInBuffer();
            serialPort1.Write(CMD_STS);
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            rx = false;
            textBoxScr.Text = string.Empty;
        }
    }
}

 

tiistai, 6. elokuu 2019

Clear PDUs - 2 - updated

SSI:nnnnnnn Call ID:nnnn Disconnect_cause:Requested_service_not_available D_Release
SSI:nnnnnnn Call ID:nnn D_Setup Transmission Granted_to_another_user Mode_of_Operation:Simplex Hook_method:Hook on/Hook off signalling Basic_service:Group_call E2EE High_Protect_24_1 Call time-out:Xmin Modify:Simplex requested
SSI:nnnnnnn Call ID:nnnnn Disconnect_cause:Call_restoration_of_the_other_user_failed D_Release
MS request for registration REJECTED for SSI: nnnnnnn - Periodic location updating CAUSE: Requested cipher key type not available (cell rejection)
SSI:nnnnnnn Call ID:nnnnn Disconnect_cause:Unknown_TETRA_identity D_Disconnect
SSI:nnnnnnn D_SDS_Data Type:UDT-1 Length:16 Data:nnnnn
SSI:nnnnnnn Call ID:nnnnn D_Info Call status:Hang time expired

SSI:nnnnnnn Call ID:nnnn D_Call_Restore Transmission Granted_to_another_user Modify_Mode_of_Operation:Duplex Notification_Indicator:Call_put_on_hold_remote_hold Modify_Basic_service:Point_to_multipoint_Acknowledged E2EE Speech_TCH_S New call ID:nnnn Call_time-out:60s

SSI:nnnnnnn Call ID:nnnn D_Alert Mode_of_Operation:Simplex T301_T302:2s

SSI:nnnnnnn Call ID:nnnn D_Connect Transmission Request_queued Mode_of_Operation:Simplex Hook_method:No hook signalling (direct through-connect) Notification_Indicator:SS_CFB_invoked Basic_service:Point_to_multipoint_Acknowledged E2EE Low_Protect_48_8 Call_time-out:Reserved

SSI:nnnnnnn Call ID:nnnnn D_Setup Transmission Request_queued Mode_of_Operation:Simplex Hook_method:Hook on/Hook off signalling Basic_service:Group_call Clear Speech_TCH_S Call_time-out:tt min

SSI:nnnnnnn Call ID:nnnn Disconnect_cause:Called_party_does_not_support_encryption D_Release

SSI:nnnnnnn Call ID:nnnnn D_TX_Continue Notification_Indicator:Reserved34

SSI:nnnnnnn Call ID:nnn D_Info Call status:Requested subscriber is paged T301_T302:10s Call_time-out:t min

trtt.jpg

tiistai, 30. heinäkuu 2019

Uniden Bearcat UBC-3500XLT windows voltage

WIN *Get Windows Voltage
Remote control protocol manual:
<COMMAND WIN>
*Get Window Voltage
Controller --> Radio
WIN[\r]
Radio --> Controller
WIN,###,[FRQ][\r]     : A/D Value (0 - 255)
Returns current window voltage and its frequency.

However, the A/D value can be higher than 255, probably the ADC resolution is 10-bit.
uniden3500win-volt.jpguniden3500ver.jpg

u35-aten232uc1.jpg

Aten UC-232A USB-2-Serial converter works with Windows 10.