Send SMS with USB GSM modem when alarm triggered

It’s really quick and dirty, but it works. Open the following code in a text editor, edit the lines with the ‘define’ to match your serial device and the phone number you want to send your sms to (in full format, including the + and country code). Then save it as a file called send_sms.cpp

// --------------------------------------------------------------------------------
// Send SMS V0.1
// --------------------------------------------------------------------------------

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/times.h>
#include <fcntl.h>
#include <termios.h> 
#include <string>
#include <bits/stdc++.h>
#include <algorithm>
#include <sys/file.h>


// --------------------------------------------------------------------------------
// Configuration
// --------------------------------------------------------------------------------

#define SIM800_DEVICE		"/dev/ttyUSB0"
#define DESTINATION_NBR		"+33673xxxxxx"


// --------------------------------------------------------------------------------
// Generic monotonic system clock, second granularity
// --------------------------------------------------------------------------------

unsigned long long GetClock(void)
{
	static struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return( ts.tv_sec );
}


// --------------------------------------------------------------------------------
// Wait for SIM800 to reply to a command, timeout after 60 seconds
// --------------------------------------------------------------------------------

bool WaitForReply(int sfd, std::string *out = nullptr)
{
	const unsigned long long timeout = 60;
	
	char Buffer[201] = { 0 };
	int p = 0;
	
	unsigned long long t = GetClock();
	
	int result = 0;
	
	while( !result ) {
		char c;
		int count = read(sfd, &c, 1);
		if( count ) {
			if( c != '\r' ) {
				Buffer[p++] = c;
				Buffer[p] = 0;
				if( strstr(Buffer, "OK\n") ) result = 1;
				if( strstr(Buffer, "ERROR\n") ) result = 2;
				if( p == 200 ) result = 2;
			}
			t = GetClock();
		}
		if( GetClock() - t > timeout ) result = 2;
	}
	
	if( out ) *out = Buffer;
	
	return( result == 1 );
}


// --------------------------------------------------------------------------------
// Reply parsing
// --------------------------------------------------------------------------------

std::vector<std::string> ParseReply(const std::string &s)
{
	std::stringstream ss(s);
	std::string part;
	std::vector<std::string> out;
	while( std::getline(ss, part, '\n') ) out.push_back(part);
	return( out );
}

std::string SearchReplyLine(const char *token, const std::vector<std::string> &out)
{
	for( auto &i : out ) {
		if( strstr(i.c_str(), token) ) return( i );
	}
	return( "" );
}


// --------------------------------------------------------------------------------
// Flush the UART read cache
// --------------------------------------------------------------------------------

void FlushAll(int sfd)
{
	while( 1 ) {
		char c;
		int count = read(sfd, &c, 1);
		if( !count ) break;
	}
}


// --------------------------------------------------------------------------------
// SMS send command
// --------------------------------------------------------------------------------

int main(int argc, char *argv[])
{
	if( argc != 2 ) {
		printf("Incorrect parameters (%d)\n", argc);
		printf("Usage: send_sms <text to send>\n", argc);
		return( 10 );
	}
	
	std::string smstext = argv[1];
	
	// Turn %20 into spaces
	while( smstext.size() ) {
		size_t i = smstext.find("%20");
		if( i == std::string::npos ) break;
		smstext.erase(i, 3);
		smstext.insert(i, " ");
	}
	
	// Open SIM800
	int sfd = open(SIM800_DEVICE, O_RDWR | O_NOCTTY); 
	
	if( sfd != -1 ) {
		
		// Lock access to the device, if another process is using it, then wait for it to unlock
		int fr = flock(sfd, LOCK_EX);
		if( fr != 0 ) {
			printf("Lock did not succeed\n");
			return( 11 );
		}
	
		// Configure UART
		struct termios options;
	
		tcgetattr(sfd, &options);
	
		cfsetspeed(&options, B9600);
	
		options.c_cflag &= ~CSTOPB;	
		options.c_cflag |= CLOCAL;
		options.c_cflag |= CREAD;
		
		cfmakeraw(&options);
	
		options.c_cc[VTIME] = 5;
		options.c_cc[VMIN] = 0;
		
		options.c_lflag &= ~ECHO;
	
		tcsetattr(sfd, TCSANOW, &options);
		
		printf("OK, serial open\n");
	
	} else {
	
		printf("Can't open serial connection, error code %d\n", errno);
		
		return( 1 );
		
	}
	
	int exitCode = 0;
	
	try {
		
		bool result;
		
		// Clear the UART
		FlushAll(sfd);

		// Check if modem responds to AT commands
		dprintf(sfd, "AT\n");
		fsync(sfd);
		result = WaitForReply(sfd);
		if( !result ) throw(2);
		
		// Reject all incoming calls
		dprintf(sfd, "AT+GSMBUSY=1\n");
		fsync(sfd);
		result = WaitForReply(sfd);
		if( !result ) throw(2);
	
		// Check if registered to a network
		dprintf(sfd, "AT+CREG?\n");
		fsync(sfd);
		std::string s;
		result = WaitForReply(sfd, &s);
		if( result ) {
			std::vector<std::string> out = ParseReply(s);
			s = SearchReplyLine("+CREG:", out);
			if( s.empty() ) throw(3);
			if( !s.size() > 0 || (s[s.size() - 1] != '5' && s[s.size() - 1] != '1')) throw(3);
			printf("Registered to network: OK\n");
		} else 
			throw(2);
		
		// Set SMS format to text
		dprintf(sfd, "AT+CMGF=1\n");
		fsync(sfd);
		result = WaitForReply(sfd);
		if( !result ) throw(2);

		printf("Sending <%s>\n", smstext.c_str());
		
		// Send SMS
		dprintf(sfd, "AT+CMGS=\"" DESTINATION_NBR "\"\n");
		fsync(sfd);
		usleep(250 * 1000);	
		dprintf(sfd, smstext.c_str());
		dprintf(sfd, "\x1A");
		fsync(sfd);
		
		printf("SMS sent, waiting for ACK\n");

		// Wait for network to ACK the SMS
		result = WaitForReply(sfd, &s);
		if( result ) {
			std::vector<std::string> out = ParseReply(s);
			s = SearchReplyLine("+CMGS:", out);
			if( s.empty() ) throw(4);
			s.erase(0, 7);
			printf("ACK received, message ID is %s\n", s.c_str());
		} else
			throw(4);
		
		// Clear the UART
		FlushAll(sfd);
	
	}
	
	catch( int c ) {
		// 2 = modem not responding to AT commands
		// 3 = Not registered on network
		// 4 = SMS not sent
		printf("ERROR code %d\n", c);
		exitCode = c;
	}

	close(sfd);

	return( exitCode );
}

Next open a terminal, go to the folder where you saved the file and compile it with the following command:

g++ -o send_sms ./send_sms.cpp

This will only work on Linux and you will need the base development tools installed.

This will create an executable called send_sms. To test, call it from the terminal with a message to send, like this:

./send_sms "Hello there"

This will send the text Hello there as an sms to to the phone number you specified earlier. If that works you can then integrate it into HA as a shell command.

1 Like

Thanks, will give it a try in 1 of the coming days.

Hi, I have started a PR with the required support for ANSI messages.
Support ANSI SMS by ocalvo · Pull Request #76733 · home-assistant/core (github.com)

1 Like

Please note the breaking change in the sms integration in 2022.9

Yaml notify is no longer needed. You can specify the phone number using the target parameter.

1 Like

Hello

It works with one target but it seems not possible to put a list of targets in a single command.

Flash SMS?
Would it be possible to add flash sms as an option. Gammu has an option [-flash] for text messages. The result is a “normal” SMS but it always pops up a “sticky” notification on the phone. Impossible to miss or ignore. Very handy for urgent notification like Fire! etc

Do you know how integrate now with automation? I have over 50 automation with notify via SMS and all of them I need to somehow adjust. Previous was easy request service and sent SMS via configured service in configuration.yaml now after this “new” change I have no idea how separate this between the numbers. :frowning:

Yes, we updated the docs to address this issue:

Let me know if you need more help.

Wierd, I tested this and it works for me.
Can you replace + by 00 and try again?

Can you point me to this in the Gammu docs?

target: 123456, 123457, 123458 …

doesn’t work (?)

Thats invalid yaml syntax, you need to specify:
target:

  • 122344
  • 123523

No commas.

1 Like

https://docs.gammu.org/gammu/#cmdoption-gammu-flash

-flash
Class 0 SMS (should be displayed after receiving on recipients’ phone display after receiving without entering Inbox)

I have juste made a test, this works :

service: notify.sms
data:
  target:
    12345
  message: "Hello"

this doesn’t work :

service: notify.sms
data:
  target:
    12345
    12346
  message: "Hello"

You are missing the dashes:
- 1234
- 1234

1 Like

Broken in 2022.9.4, about 1 piece of error per minutes (even after the integration had been disabled and deleted):

Logger: homeassistant.components.sms.gateway
Source: components/sms/gateway.py:173
Integration: SMS notifications via GSM-modem (documentation, issues)
First occurred: 19:39:05 (13 occurrences)
Last logged: 19:55:05

Failed to initialize, error ERR_DEVICENOTEXIST

hassos 9.0, what’s wrong with it?

it had been quite good for years before, but broken from yesterday, after upgraded to hassos 9.0/core 2022.9.4, neither reboot nor re-config could make help.

1 Like

after OS 9.0 upgrade my HA would not even start (the usb modem blocks eth connection)

What model of modem you have?

Can you list the hardware devices reported by the os?

Thanks for replying.
It’s a Huawei 3G usb stick, model E180. Yes, it’s reported by the os, as /dev/ttyUSB0~2 (sometimes 1~3).

I have utilized this modem with SMS notifications via GSM-modem integration for several years.

btw, I always adopt the port /dev/serial/by-id/usb-HUAWEI_Technology_HUAWEI_Mobile-if00-port0, since the numbers behind /dev/ttyUSB may changes by itself sometimes.

Can you try to remove the integration and add it again?