Skip to content

Commit a25e5bb

Browse files
committed
Add ListMessages() and SupportedStorageAreas().
1 parent 1153e9a commit a25e5bb

File tree

6 files changed

+273
-13
lines changed

6 files changed

+273
-13
lines changed

examples/receive.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Example of receiving messages.
2+
package main
3+
4+
import (
5+
"fmt"
6+
"log"
7+
8+
"github.com/barnybug/gogsmmodem"
9+
"github.com/tarm/goserial"
10+
)
11+
12+
func main() {
13+
conf := serial.Config{Name: "/dev/ttyUSB1", Baud: 115200}
14+
modem, err := gogsmmodem.Open(&conf, true)
15+
if err != nil {
16+
panic(err)
17+
}
18+
19+
for packet := range modem.OOB {
20+
log.Printf("Received: %#v\n", packet)
21+
switch p := packet.(type) {
22+
case gogsmmodem.MessageNotification:
23+
log.Println("Message notification:", p)
24+
msg, err := modem.GetMessage(p.Index)
25+
if err == nil {
26+
fmt.Printf("Message from %s: %s\n", msg.Telephone, msg.Body)
27+
modem.DeleteMessage(p.Index)
28+
}
29+
}
30+
}
31+
}

examples/stored.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Example of retrieving stored messages.
2+
package main
3+
4+
import (
5+
"fmt"
6+
7+
"github.com/barnybug/gogsmmodem"
8+
"github.com/tarm/goserial"
9+
)
10+
11+
func main() {
12+
conf := serial.Config{Name: "/dev/ttyUSB1", Baud: 115200}
13+
modem, err := gogsmmodem.Open(&conf, true)
14+
if err != nil {
15+
panic(err)
16+
}
17+
18+
li, _ := modem.ListMessages("ALL")
19+
fmt.Printf("%d stored messages\n", len(*li))
20+
for _, msg := range *li {
21+
fmt.Println(msg.Index, msg.Status, msg.Body)
22+
}
23+
}

gsm.go

+100-7
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (self *Modem) Close() error {
6060

6161
// Commands
6262

63+
// GetMessage by index n from memory.
6364
func (self *Modem) GetMessage(n int) (*Message, error) {
6465
packet, err := self.send("+CMGR", n)
6566
if err != nil {
@@ -71,6 +72,44 @@ func (self *Modem) GetMessage(n int) (*Message, error) {
7172
return nil, errors.New("Message not found")
7273
}
7374

75+
// ListMessages stored in memory. Filter should be "ALL", "REC UNREAD", "REC READ", etc.
76+
func (self *Modem) ListMessages(filter string) (*MessageList, error) {
77+
packet, err := self.send("+CMGL", filter)
78+
if err != nil {
79+
return nil, err
80+
}
81+
res := MessageList{}
82+
if _, ok := packet.(OK); ok {
83+
// empty response
84+
return &res, nil
85+
}
86+
87+
for {
88+
if msg, ok := packet.(Message); ok {
89+
res = append(res, msg)
90+
if msg.Last {
91+
break
92+
}
93+
} else {
94+
return nil, errors.New("Unexpected error")
95+
}
96+
97+
packet = <-self.rx
98+
}
99+
return &res, nil
100+
}
101+
102+
func (self *Modem) SupportedStorageAreas() (*StorageAreas, error) {
103+
packet, err := self.send("+CPMS", "?")
104+
if err != nil {
105+
return nil, err
106+
}
107+
if msg, ok := packet.(StorageAreas); ok {
108+
return &msg, nil
109+
}
110+
return nil, errors.New("Unexpected response type")
111+
}
112+
74113
func (self *Modem) DeleteMessage(n int) error {
75114
_, err := self.send("+CMGD", n)
76115
return err
@@ -100,11 +139,20 @@ func lineChannel(r io.Reader) chan string {
100139
var reQuestion = regexp.MustCompile(`AT(\+[A-Z]+)`)
101140

102141
func parsePacket(status string, header string, body string) Packet {
142+
if header == "" && (status == "OK" || status == "ERROR") {
143+
if status == "OK" {
144+
return OK{}
145+
} else {
146+
return ERROR{}
147+
}
148+
}
149+
103150
ls := strings.SplitN(header, ":", 2)
104151
if len(ls) != 2 {
105152
return UnknownPacket{header, []interface{}{}}
106153
}
107-
args := unquotes(strings.TrimSpace(ls[1]))
154+
uargs := strings.TrimSpace(ls[1])
155+
args := unquotes(uargs)
108156
switch ls[0] {
109157
case "+ZUSIMR":
110158
// message storage unset nag, ignore
@@ -120,6 +168,46 @@ func parsePacket(status string, header string, body string) Packet {
120168
case "+CMGR":
121169
return Message{Status: args[0].(string), Telephone: args[1].(string),
122170
Timestamp: parseTime(args[3].(string)), Body: body}
171+
case "+CMGL":
172+
return Message{
173+
Index: args[0].(int),
174+
Status: args[1].(string),
175+
Telephone: args[2].(string),
176+
Timestamp: parseTime(args[4].(string)),
177+
Body: body,
178+
Last: status != "",
179+
}
180+
case "+CPMS":
181+
s := uargs
182+
if strings.HasPrefix(s, "(") {
183+
// query response
184+
// ("A","B","C"),("A","B","C"),("A","B","C")
185+
s = strings.TrimPrefix(s, "(")
186+
s = strings.TrimSuffix(s, ")")
187+
areas := strings.SplitN(s, "),(", 3)
188+
return StorageAreas{
189+
stringsUnquotes(areas[0]),
190+
stringsUnquotes(areas[1]),
191+
stringsUnquotes(areas[2]),
192+
}
193+
} else {
194+
// set response
195+
// 0,100,0,100,0,100
196+
// get ints
197+
var iargs []int
198+
for _, arg := range args {
199+
if iarg, ok := arg.(int); ok {
200+
iargs = append(iargs, iarg)
201+
}
202+
}
203+
if len(iargs) != 6 {
204+
break
205+
}
206+
207+
return StorageInfo{
208+
iargs[0], iargs[1], iargs[2], iargs[3], iargs[4], iargs[5],
209+
}
210+
}
123211
case "":
124212
if status == "OK" {
125213
return OK{}
@@ -139,7 +227,13 @@ func (self *Modem) listen() {
139227
if line == echo {
140228
continue // ignore echo of command
141229
} else if last != "" && startsWith(line, last) {
230+
if header != "" {
231+
// first of multiple responses (eg CMGL)
232+
packet := parsePacket("", header, body)
233+
self.rx <- packet
234+
}
142235
header = line
236+
body = ""
143237
} else if line == "OK" || line == "ERROR" {
144238
packet := parsePacket(line, header, body)
145239
self.rx <- packet
@@ -207,13 +301,13 @@ func (self *Modem) init() error {
207301
return err
208302
}
209303
log.Println("Echo off")
210-
// set SMS storage
211-
// note: seems to deliver to SM (SIM) storage regardless, so need to set
212-
// READ to "SM" too.
213-
if _, err := self.send("+CPMS", "SM", "SM", "SM"); err != nil {
304+
// use combined storage (MT)
305+
msg, err := self.send("+CPMS", "SM", "SM", "SM")
306+
if err != nil {
214307
return err
215308
}
216-
log.Println("Set SMS Storage")
309+
sinfo := msg.(StorageInfo)
310+
log.Printf("Set SMS Storage: %d/%d used\n", sinfo.UsedSpace1, sinfo.MaxSpace1)
217311
// set SMS text mode - easiest to implement. Ignore response which is
218312
// often a benign error.
219313
self.send("+CMGF", 1)
@@ -233,6 +327,5 @@ func (self *Modem) init() error {
233327
return err
234328
}
235329
log.Println("Set SMSC to:", smsc.Args)
236-
// fmt.Println(r)
237330
return nil
238331
}

gsm_test.go

+87-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gogsmmodem
33
import (
44
"fmt"
55
"io"
6+
"log"
67
"reflect"
78
"testing"
89
"time"
@@ -16,7 +17,7 @@ var initReplay = []string{
1617
"->ATE0\r\n",
1718
"<-ATE0\n",
1819
"<-\r\nOK\r\n",
19-
"->AT+CPMS=\"SM\",\"SM\",\"SM\"\r\n",
20+
"->AT+CPMS=\"MT\",\"MT\",\"MT\"\r\n",
2021
"<-\r\n+CPMS: 50,50,50,50,50,50\r\nOK\n\n",
2122
"->AT+CMGF=1\r\n",
2223
"<-\r\nOK\r\n",
@@ -137,7 +138,7 @@ func TestGetMessage(t *testing.T) {
137138
}
138139

139140
msg, _ := modem.GetMessage(1)
140-
expected := Message{"REC UNREAD", "+441234567890", time.Date(2014, 2, 1, 15, 7, 43, 0, time.UTC), "Hi"}
141+
expected := Message{0, "REC UNREAD", "+441234567890", time.Date(2014, 2, 1, 15, 7, 43, 0, time.UTC), "Hi", false}
141142
if *msg != expected {
142143
t.Errorf("Expected: %#v, got %#v", expected, msg)
143144
}
@@ -189,3 +190,87 @@ func TestSendMessage(t *testing.T) {
189190
}
190191
modem.Close()
191192
}
193+
194+
var listMessagesReplay = []string{
195+
"->AT+CMGL=\"ALL\"\r\n",
196+
"<-\r\n+CMGL: 0,\"REC UNREAD\",\"+441234567890\",,\"14/02/01,15:07:43+00\"\r\nHi\r\n+CMGL: 1,\"REC READ\",\"+441234567890\",,\"14/02/01,15:07:43+00\"\r\nOla\r\n+CMGL: 2,\"REC UNREAD\",\"+44123456",
197+
"<-7890\",,\"14/02/01,15:07:43+00\"\r\nJa\r\n\r\nOK\r\n",
198+
}
199+
200+
func TestListMessages(t *testing.T) {
201+
OpenPort = func(config *serial.Config) (io.ReadWriteCloser, error) {
202+
replay := appendLists(initReplay, listMessagesReplay)
203+
return NewMockSerialPort(replay), nil
204+
}
205+
modem, err := Open(&serial.Config{}, true)
206+
if err != nil {
207+
t.Error("Expected: no error, got:", err)
208+
}
209+
210+
msg, _ := modem.ListMessages("ALL")
211+
expected := MessageList{
212+
Message{0, "REC UNREAD", "+441234567890", time.Date(2014, 2, 1, 15, 7, 43, 0, time.UTC), "Hi", false},
213+
Message{1, "REC READ", "+441234567890", time.Date(2014, 2, 1, 15, 7, 43, 0, time.UTC), "Ola", false},
214+
Message{2, "REC UNREAD", "+441234567890", time.Date(2014, 2, 1, 15, 7, 43, 0, time.UTC), "Ja", true},
215+
}
216+
if len(*msg) != len(expected) {
217+
t.Errorf("Expected: %#v, got %#v", expected, msg)
218+
}
219+
for i, m := range *msg {
220+
if m != expected[i] {
221+
t.Errorf("Expected: %#v, got %#v", expected, msg)
222+
}
223+
}
224+
modem.Close()
225+
}
226+
227+
var listMessagesEmptyReplay = []string{
228+
"->AT+CMGL=\"ALL\"\r\n",
229+
"<-\r\nOK\r\n",
230+
}
231+
232+
func TestListMessagesEmpty(t *testing.T) {
233+
OpenPort = func(config *serial.Config) (io.ReadWriteCloser, error) {
234+
replay := appendLists(initReplay, listMessagesEmptyReplay)
235+
return NewMockSerialPort(replay), nil
236+
}
237+
modem, err := Open(&serial.Config{}, true)
238+
if err != nil {
239+
t.Error("Expected: no error, got:", err)
240+
}
241+
242+
msg, err := modem.ListMessages("ALL")
243+
log.Println("ERROR", err)
244+
expected := MessageList{}
245+
if len(*msg) != len(expected) {
246+
t.Errorf("Expected: %#v, got %#v", expected, msg)
247+
}
248+
modem.Close()
249+
}
250+
251+
var storageAreasReplay = []string{
252+
"->AT+CPMS=?\r\n",
253+
"<-\r\n+CPMS: (\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\"),(\"ME\",\"MT\",\"SM\",\"SR\")\r\n\r\nOK\r\n",
254+
}
255+
256+
func TestSupportedStorageAreas(t *testing.T) {
257+
OpenPort = func(config *serial.Config) (io.ReadWriteCloser, error) {
258+
replay := appendLists(initReplay, storageAreasReplay)
259+
return NewMockSerialPort(replay), nil
260+
}
261+
modem, err := Open(&serial.Config{}, true)
262+
if err != nil {
263+
t.Error("Expected: no error, got:", err)
264+
}
265+
266+
msg, _ := modem.SupportedStorageAreas()
267+
expected := StorageAreas{
268+
[]string{"ME", "MT", "SM", "SR"},
269+
[]string{"ME", "MT", "SM", "SR"},
270+
[]string{"ME", "MT", "SM", "SR"},
271+
}
272+
if fmt.Sprint(*msg) != fmt.Sprint(expected) {
273+
t.Errorf("Expected: %#v, got %#v", expected, msg)
274+
}
275+
modem.Close()
276+
}

packets.go

+17
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,29 @@ type SMSCAddress struct {
2727

2828
// +CMGR
2929
type Message struct {
30+
Index int
3031
Status string
3132
Telephone string
3233
Timestamp time.Time
3334
Body string
35+
Last bool
3436
}
3537

38+
// +CPMS=?
39+
type StorageAreas struct {
40+
Received []string
41+
Sent []string
42+
New []string
43+
}
44+
45+
// +CPMS=...
46+
type StorageInfo struct {
47+
UsedSpace1, MaxSpace1, UsedSpace2, MaxSpace2, UsedSpace3, MaxSpace3 int
48+
}
49+
50+
// +CMGL
51+
type MessageList []Message
52+
3653
// Simple OK response
3754
type OK struct{}
3855

util.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ func parseTime(t string) time.Time {
2525
func quote(s interface{}) string {
2626
switch v := s.(type) {
2727
case string:
28+
if v == "?" {
29+
return v
30+
}
2831
return fmt.Sprintf(`"%s"`, v)
29-
case int:
30-
return fmt.Sprintf("%d", v)
31-
case int64:
32-
return fmt.Sprintf("%d", v)
32+
case int, int64:
33+
return fmt.Sprint(v)
3334
default:
3435
panic(fmt.Sprintf("Unsupported argument type: %T", v))
3536
}
@@ -74,6 +75,16 @@ func unquotes(s string) []interface{} {
7475
return args
7576
}
7677

78+
// Unquote a parameter list of strings
79+
func stringsUnquotes(s string) []string {
80+
args := unquotes(s)
81+
var res []string
82+
for _, arg := range args {
83+
res = append(res, fmt.Sprint(arg))
84+
}
85+
return res
86+
}
87+
7788
// A logging ReadWriteCloser for debugging
7889
type LogReadWriteCloser struct {
7990
f io.ReadWriteCloser

0 commit comments

Comments
 (0)